Travailler ensemble pour créer et grandir ensemble ! C'est le 5ème jour de ma participation au "Nuggets Daily New Plan · August Update Challenge", cliquez pour voir les détails de l'événement
avant-propos
Le but de cet article est de dessiner des exercices, qui seront inclus dans la FlutterUnit Drawing Collection. De plus, il y a un chapitre dans "Flutter Grammar Basics - The Land of Dreams" qui doit utiliser ce cadran, mais ne veut pas impliquer trop de connaissances en dessin, il est donc implémenté ici. L'effet est le suivant, l'anneau extérieur est un cadran entouré de lignes, un petit cercle à l'intérieur indique la position actuelle et un texte d'information est affiché au milieu.
1. Analyse de la demande
Ce qui est dessiné ici est 秒表表盘
qu'un cercle est 1 分种
, et il y a des 3
grilles , c'est-à-dire qu'il y a un total 180
de grilles, et l'angle entre chaque grille est 2°
. Examinons les paramètres requis dans le processus de dessin. Tout d'abord, nous avons besoin d'un Duration
objet qui représente l'heure actuelle du chronomètre. De plus, l'angle du petit cercle peut être calculé en fonction du temps.
Paramètres configurables lors du dessin, tels que le rayon, la couleur de l'échelle, la couleur du texte, le style, etc. De plus, des paramètres plus détaillés tels que la longueur, l'épaisseur et le rayon du petit cercle de l'échelle peuvent être calculés en fonction du rayon.
2. Dessin à l'échelle
Le carré illustré ci-dessous est la zone de dessin et la largeur de l'échelle de gauche est déterminée scaleLineWidth
par la largeur et l' _kScaleWidthRate
échelle .
const double _kScaleWidthRate = 0.4/10;
final double scaleLineWidth = size.width*_kScaleWidthRate;
复制代码
Utilisez une ligne droite lorsque vous dessinez l'échelle, tant que vous déterminez les coordonnées des points gauche et droit. tag1
Les coordonnées de l'échelle peuvent être facilement calculées en déplaçant l'origine des coordonnées au centre de la zone comme tag2
indiqué ci-dessous.
final Paint scalePainter = Paint();
@override
void paint(Canvas canvas, Size size) {
canvas.translate(size.width/2, size.height/2); // tag1
scalePainter..color = Colors.red..style=PaintingStyle.stroke;
final double scaleLineWidth = size.width*_kScaleWidthRate;
canvas.drawLine(
Offset(size.width/2, 0), // tag2
Offset(size.width/2 - scaleLineWidth, 0),
scalePainter
);
}
复制代码
Dessinons le cadran ci-dessous. Nous avons analysé un total 180
d' , qui peuvent être dessinées en traversant et en tournant. Comme suit, parcourez l'échelle de la barre au-dessus des 180
temps et la toile tourne une fois chaque dessin terminé 2°
, de sorte qu'après les 180
temps , la toile 360°
revienne à sa position d'origine. Le contenu dessiné sur l'interface s'affiche comme suit :
for(int i = 0; i < 180 ; i++){
canvas.drawLine(
Offset(size.width/2, 0),
Offset(size.width/2 - scaleLineWidth, 0),
scalePainter
);
canvas.rotate(pi/180*2);
}
复制代码
3. Dessin de texte
可以看出这里的文字有两种样式,毫秒数颜色跟随主题色。在 Canvas
文字绘制时可以通过 TextPainter
对象完成。使用该对象必须指定 textDirection
,表示文字的排布方向。
TextPainter textPainter = TextPainter(
textAlign: TextAlign.center,
textDirection: TextDirection.ltr,
);
复制代码
textPainter
绘制内容通过 text
成员指定,该成员类型是 InlineSpan
。所以可以展示富文本,如下代码展示 commonStyle
和 highlightStyle
两种样式的文字。
void drawText(Canvas canvas){
textPainter.text = TextSpan(
text: '00:04',
style: commonStyle,
children: [TextSpan(text: ".65", style: highlightStyle)]);
textPainter.layout(); // 进行布局
final double width = textPainter.size.width;
final double height = textPainter.size.height;
textPainter.paint(canvas, Offset(-width / 2, -height / 2));
}
复制代码
然后,可以根据一个 duration
对象来得到需要展示的文字:
final Duration duration = Duration(minutes: 0, seconds: 4, milliseconds: 650);
int minus = duration.inMinutes % 60;
int second = duration.inSeconds % 60;
int milliseconds = duration.inMilliseconds % 1000;
String commonStr = '${minus.toString().padLeft(2, "0")}:${second.toString().padLeft(2, "0")}';
String highlightStr = ".${(milliseconds ~/ 10).toString().padLeft(2, "0")}";
复制代码
4.绘制指示器
圆的指示器的半径也是根据大圆半径计算的,然后根据时长计算出偏转角度即可。
final double scaleLineWidth = size.width * _kScaleWidthRate;
final double indicatorRadius = size.width * _kIndicatorRadiusRate;
canvas.drawCircle(
Offset(0,-size.width/2+scaleLineWidth+indicatorRadius,),
indicatorRadius/2,
indicatorPainter
);
复制代码
下面来算个简单的数学题,已知当前时长,如何求得该时长在表盘的旋转角度?
只要算出当前分钟内毫秒数
对 一分钟毫秒数(60 * 1000)
占比即可。在绘制指示器时,将画布进行旋转 radians
弧度,不过要注意,为了避免这个旋转变换对其他绘制的影响,需要通过 save
和 restore
方法进行处理。
int second = duration.inSeconds % 60;
int milliseconds = duration.inMilliseconds % 1000;
double radians = (second * 1000 + milliseconds) / (60 * 1000) * 2 * pi;
canvas.save();
canvas.rotate(radians);
// 绘制...
canvas.restore();
复制代码
这样,给出一个 Duration
对象,就能线数处正确的文字及指示器位置:
5. 组件的封装
组件的封装是为了更简洁的使用,如下通过为 StopWatchWidget
组件提供配置即可呈现出对应的绘制效果。就可以将绘制的细节封装起来使用者不需要了解具体是怎么画出来的,只要用 StopWatchWidget
组件即可。
StopWatchWidget(
duration: Duration(minutes: 0, seconds: 24, milliseconds: 850),
radius: 100,
),
复制代码
如下在 StopWatchPainter
中封装了4
个可配置的参数,在 shouldRepaint
方法中,当这四者其中之一发生变化时都允许进行重绘。
class StopWatchPainter extends CustomPainter {
final Duration duration; // 时长
final Color themeColor; // 主题色
final Color scaleColor; // 刻度色
final TextStyle textStyle; // 文本样式
StopWatchPainter({
required this.duration,
required this.themeColor,
required this.scaleColor,
required this.textStyle,
})
// 绘制略...
@override
bool shouldRepaint(covariant StopWatchPainter oldDelegate) {
return oldDelegate.duration != duration ||
oldDelegate.textStyle != textStyle ||
oldDelegate.themeColor != themeColor||
oldDelegate.scaleColor != scaleColor;
}
}
复制代码
StopWatchWidget
Hérité de StatelessWidget
, lui-même n'entreprend pas la possibilité de changer d'état. C'est-à-dire que son contenu de présentation est uniquement lié aux informations de configuration transmises par l'utilisateur et ne modifie pas activement l'effet de présentation. Le plan de travail dessiné est affiché via le CustomPaint
composant StopWatchPainter
.
De plus, faites attention à quelques petits détails. Les组件
paramètres de construction sont similaires à ceux de , mais les composants sont en contact direct avec et. Compte tenu de sa facilité d'utilisation, il est nécessaire de fournir certains paramètres par défaut, ou d'obtenir des informations selon le thème actuel. L'objet plan de travail est responsable de la création, les rôles auxquels ils sont confrontés sont différents et les considérations pour l'encapsulation sont également différentes.画板
使用者
创造者
class StopWatchWidget extends StatelessWidget {
final double radius;
final Duration duration;
final Color? themeColor;
final TextStyle? textStyle;
final Color scaleColor;
const StopWatchWidget({
Key? key,
required this.radius,
required this.duration,
this.scaleColor = const Color(0xffDADADA),
this.textStyle,
this.themeColor
}) : super(key: key);
TextStyle get commonStyle => TextStyle(
fontSize: radius/3,
fontWeight: FontWeight.w200,
color: const Color(0xff343434),
);
@override
Widget build(BuildContext context) {
TextStyle style = textStyle??commonStyle;
Color themeColor = this.themeColor??Theme.of(context).primaryColor;
return CustomPaint(
painter: StopWatchPainter(
duration: duration,
themeColor: themeColor,
scaleColor: scaleColor,
textStyle: style),
size: Size(radius * 2, radius * 2),
);
}
}
复制代码
C'est tout pour cet article, et le prochain article se basera sur ce composant de dessin pour réaliser la fonction de démarrage et de pause du deuxième cadran. Merci d'avoir regardé ~