Colección de dibujos Flutter | Dibujo del segundo dial

¡Trabajar juntos para crear y crecer juntos! Este es el quinto día de mi participación en el "Nuggets Daily New Plan·Desafío de actualización de agosto", haz clic para ver los detalles del evento


prefacio

El propósito de este artículo es dibujar ejercicios, que se incluirán en la colección de dibujos de FlutterUnit . Además, hay un capítulo en "Flutter Grammar Basics - The Land of Dreams" que necesita usar este dial, pero no quiere involucrar demasiados conocimientos de dibujo, por lo que se implementa aquí. El efecto es el siguiente, el anillo exterior es un dial rodeado de líneas, un pequeño círculo en el interior indica la posición actual y el texto de información se muestra en el medio.


1. Análisis de la demanda

Lo que aquí se dibuja es 秒表表盘que un círculo es 1 分种, y hay 3cuadrículas , es decir, hay un total 180de cuadrículas, y el ángulo entre cada cuadrícula es . Echemos un vistazo a los parámetros requeridos en el proceso de dibujo Primero, necesitamos un Durationobjeto que represente la hora actual del cronómetro. Además, el ángulo del círculo pequeño se puede calcular según el tiempo.

Parámetros que se pueden configurar al dibujar, como radio, color de escala, color de texto, estilo, etc. Además, se pueden calcular parámetros más detallados como la longitud, el grosor y el radio del círculo pequeño de la escala de acuerdo con el radio.


2. Dibujo a escala

El cuadrado que se muestra a continuación es el área de dibujo, y el ancho de la escala izquierda está determinado scaleLineWidthpor el ancho y la _kScaleWidthRateescala .

const double _kScaleWidthRate = 0.4/10;
final double scaleLineWidth = size.width*_kScaleWidthRate;
复制代码

Usa una línea recta cuando dibujes la escala, siempre y cuando determines las coordenadas de los puntos izquierdo y derecho. tag1Las coordenadas de la escala se pueden calcular fácilmente moviendo el origen de las coordenadas al centro del área como tag2se muestra a continuación.

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

Dibujemos el dial a continuación. Hemos analizado un total 180de , que se pueden dibujar atravesando y rotando. De la siguiente manera, atraviese la barra de escala sobre los 180tiempos , y el lienzo gira después de completar cada dibujo , de modo que después de los 180tiempos , el lienzo volverá 360°a su posición original. El contenido dibujado en la interfaz se muestra de la siguiente manera:

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. Dibujo de texto

可以看出这里的文字有两种样式,毫秒数颜色跟随主题色。在 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;
  }
}
复制代码

StopWatchWidgetHeredado de StatelessWidget, en sí mismo no asume la capacidad de cambiar de estado. Es decir, su contenido de presentación solo está relacionado con la información de configuración pasada por el usuario y no cambia activamente el efecto de presentación. La mesa de trabajo dibujada se muestra a través del CustomPaintcomponente StopWatchPainter.

Además, preste atención a algunos pequeños detalles. Los组件 parámetros de construcción son similares a los de , pero los componentes están en contacto directo con y. Teniendo en cuenta su facilidad de uso, es necesario proporcionar algunos parámetros por defecto, u obtener alguna información de acuerdo con el tema actual. El objeto de mesa de trabajo es responsable de la creación, los roles que enfrentan son diferentes y las consideraciones para la encapsulación también son diferentes.画板使用者创造者

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

Eso es todo por este artículo, y el próximo artículo se basará en este componente de dibujo para realizar la función de iniciar y pausar el segundo dial. Gracias por mirar ~

Supongo que te gusta

Origin juejin.im/post/7137081179315896350
Recomendado
Clasificación