Flutter Drawing Collection | Dessin du deuxième cadran

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 3grilles , c'est-à-dire qu'il y a un total 180de grilles, et l'angle entre chaque grille est . Examinons les paramètres requis dans le processus de dessin. Tout d'abord, nous avons besoin d'un Durationobjet 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 scaleLineWidthpar 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. tag1Les 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 tag2indiqué 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 180d' , qui peuvent être dessinées en traversant et en tournant. Comme suit, parcourez l'échelle de la barre au-dessus des 180temps et la toile tourne une fois chaque dessin terminé , de sorte qu'après les 180temps , 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 。所以可以展示富文本,如下代码展示 commonStylehighlightStyle 两种样式的文字。

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 弧度,不过要注意,为了避免这个旋转变换对其他绘制的影响,需要通过 saverestore 方法进行处理。

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;
  }
}
复制代码

StopWatchWidgetHé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 CustomPaintcomposant 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é ~

Je suppose que tu aimes

Origine juejin.im/post/7137081179315896350
conseillé
Classement