¡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 3
cuadrículas , es decir, hay un total 180
de cuadrículas, y el ángulo entre cada cuadrícula es 2°
. Echemos un vistazo a los parámetros requeridos en el proceso de dibujo Primero, necesitamos un Duration
objeto 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 scaleLineWidth
por el ancho y la _kScaleWidthRate
escala .
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. tag1
Las coordenadas de la escala se pueden calcular fácilmente moviendo el origen de las coordenadas al centro del área como tag2
se 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 180
de , que se pueden dibujar atravesando y rotando. De la siguiente manera, atraviese la barra de escala sobre los 180
tiempos , y el lienzo gira después de completar cada dibujo 2°
, de modo que después de los 180
tiempos , 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
。所以可以展示富文本,如下代码展示 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
Heredado 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 CustomPaint
componente 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 ~