Flutter 绘制集录 | 画个表 (上)

一、本作介绍和准备

1. 效果图

如下图,通过 Flutter 的 Canvas 绘制如下的 静态 表面。

本文知识点

1】. Flutter 中绘制旋转型的元素
2】. Flutter 中弧线的方式
3】. Flutter 中绘制文字的方式
4】. Canvas.save 和 Canvas.restore 的使同。

2.资源准备

绘制文字时,可以指定文字的字体,如下在 assets/fonts 中放入对应字体。

pubspec.yaml 中配置后即可使用。

3.程序入口

程序入口如下,通过 ClockWidget 组件来绘制表盘。本篇   ClockWidget 只是进行静态效果展现,只需继承 StatelessWidget 即可。在 300*300 的区域内通过 ClockPainter 进行绘制。

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        title: 'Flutter Demo',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: Scaffold(
          body: Center(child: ClockWidget()),
        ));
  }
}

class ClockWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return CustomPaint(
      size: Size(300300),
      painter: ClockPainter(),
    );
  }
}

一、表盘外围绘制

1.画板的定义

这个绘制中的旋转操作很多,为了方便处理,可以将画布的中心移到画板区域的中心。这样绘制时原点就会在中心。

class ClockPainter extends CustomPainter {
  Paint _paint = Paint();

  @override
  void paint(Canvas canvas, Size size) {
    canvas.translate(size.width / 2, size.height / 2);
    drawHelp(canvas,size);
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    return false;
  }

  void drawHelp(Canvas canvas,Size size){
  canvas.drawPoints(PointMode.lines, [
    Offset( -size.width/2,0),Offset( size.width/2,0),
    Offset( 0,-size.height/2),Offset(0,size.height/2),
  ], Paint());
}
}

2.绘制外圈

首先分析一下外圈的绘制,这里只要运用 绘制圆弧画布旋转 。如下图,左侧是一个弧,然后通过三次旋转画布,再绘制即可的得到右图。代码如下,在每次绘制完后,将画布旋转 90° ,由于涉及局部的旋转操作,需要保存之前的画布状态,局部变换完后,恢复到之前的状态。(其实这里恰好转了360°,已复原,保不保存无所谓,但如果非 360°,如果不保存和恢复,变换将会影响之后的绘制)

四分之一 旋转绘制

void drawOuterCircle(Canvas canvas, Size size) {
  final offsetAngle = 5// 圆弧顶点和轴的夹角
  _paint ..strokeWidth = 4
    ..color = Color(0xffD5D5D5)
    ..style = PaintingStyle.stroke;
  canvas.save();
  for (int i = 0; i < 4; i++) {
    canvas.drawArc(
        Rect.fromPoints(Offset(-size.width / 2, -size.height / 2),
            Offset(size.width / 2, size.height / 2)),
        offsetAngle * pi / 180,
        pi / 2 - 2 * offsetAngle * pi / 180,
        false, _paint);
    canvas.rotate(pi / 2);
  }
  canvas.restore(); 
}

3.外圈格点的绘制

如下 drawDot 方法,count 变量表示格点的个数,每次遍历之后,通过 canvas.rotate(perAngle) 进行画布旋转。判断 i % 5 == 0 时绘制较粗的格线,其他的是普通格线。这就是在遍历时根据情况进行绘制的方式,在一些刻度类型的绘制中很常用。

void drawDot(Canvas canvas) {
  canvas.save();
  _paint
    ..strokeCap = StrokeCap.round
    ..style = PaintingStyle.fill;
  double count = 60;
  double perAngle = 2 * pi / count;
  for (int i = 0; i < count; i++) {
    if (i % 5 == 0) {
      _paint
        ..strokeWidth = 3
        ..color = Colors.blue;
      canvas.drawLine(Offset(1200), Offset(1350), _paint);
      canvas.drawCircle(Offset(1150), 2, _paint..color = Colors.orange);
    } else {
      _paint
        ..strokeWidth = 1.5
        ..color = Colors.black;
      canvas.drawLine(Offset(1250), Offset(1350), _paint);
    }
    canvas.rotate(perAngle);
  }
  canvas.restore();
}

4.绘制文字

文字通过 TextPainter 对象进行绘制,如下 _drawCircleText 方法绘制圆框中的四个文字。 _drawLogoText 绘制 CHOPS 字体的文字。这样,主要的外框就绘制完毕。

final TextPainter _textPainter = TextPainter(
    textAlign: TextAlign.center, textDirection: TextDirection.ltr);

void drawText(Canvas canvas) {
  _drawCircleText(canvas, 'Ⅸ', offsetX: -150);
  _drawCircleText(canvas, 'Ⅲ', offsetX: 150);
  _drawCircleText(canvas, 'Ⅵ', offsetY: 150);
  _drawCircleText(canvas, 'Ⅻ', offsetY: -150);
  _drawLogoText(canvas, offsetY: -80);
}

_drawCircleText(Canvas canvas, String text,
    {double offsetX = 0double offsetY = 0}) {
  _textPainter.text = TextSpan(
      text: text, style: TextStyle(fontSize: 20, color: Colors.blue));
  _textPainter.layout();
  _textPainter.paint(
      canvas,
      Offset.zero.translate(-_textPainter.size.width / 2 + offsetX,
          -_textPainter.height / 2 + offsetY));
}

_drawLogoText(Canvas canvas, {double offsetX = 0double offsetY = 0}) {
  _textPainter.text = TextSpan(
      text: 'Toly',
      style:
          TextStyle(fontSize: 30, color: Colors.blue, fontFamily: 'CHOPS'));
  _textPainter.layout();
  _textPainter.paint(
      canvas,
      Offset.zero.translate(-_textPainter.size.width / 2 + offsetX,
          -_textPainter.height / 2 + offsetY));
}

三、表盘指针绘制

1.时针绘制

如下,通过 drawH(canvas, 120); 绘制120° 偏转的时针。注意,此角度是与横轴正向的夹角。

void drawH(Canvas canvas, double deg) {
  canvas.save();
  canvas.rotate(deg / 180 * pi);
  _paint
    ..strokeWidth = 3
    ..color = Color(0xff8FC552)
    ..strokeCap = StrokeCap.round;
  canvas.drawLine(Offset.zero, Offset(600), _paint);
  canvas.restore();
}

2.分针绘制

同理,如下,通过 drawM(canvas, 0); 绘制 0° 偏转的分针。

void drawM(Canvas canvas, double deg) {
  canvas.save();
  canvas.rotate(deg / 180 * pi);
  _paint
    ..strokeWidth = 2
    ..color = Color(0xff87B953)
    ..strokeCap = StrokeCap.round;
  canvas.drawLine(Offset.zero, Offset(800, ), _paint);
  canvas.restore();
}

3.秒针绘制

如下,通过 drawS(canvas, 90); 绘制 90° 偏转的秒针。

void drawS(Canvas canvas, double deg) {
  _paint..strokeWidth = 2.5
    ..color = Color(0xff6B6B6B)
    ..strokeCap = StrokeCap.square
    ..style = PaintingStyle.stroke;

  Path path = Path();
  canvas.save();
  canvas.rotate(deg / 180 * pi);
  canvas.save();
  canvas.rotate((360 - 270) / 2 / 180 * pi);
  path.addArc(Rect.fromPoints(Offset(-9-9), Offset(99)), 0270 / 180 * pi);
  canvas.drawPath(path, _paint);
  canvas.restore();

  _paint..strokeCap = StrokeCap.round;
  canvas.drawLine(Offset(-90), Offset(-200), _paint);
  _paint..strokeWidth = 1..color = Colors.black;
  canvas.drawLine(Offset(00), Offset(1000), _paint);

  _paint..strokeWidth = 3..color = Color(0xff6B6B6B);
  canvas.drawCircle(Offset.zero, 5, _paint);

  _paint..color = Color(0xff8FC552)..style = PaintingStyle.fill;
  canvas.drawCircle(Offset.zero, 4, _paint);
  canvas.restore();
}

这里指针指示进行了简单的绘制,你可以在对应的方法中进行自己的处理。这样通过角度的设置就可以将指针进行变动,加上定时器,或者动画就可以形成动态的表面。这些处理我们在下篇进行讲述,谢谢观看 ~