flutter实现数字刻度尺

5,181 阅读2分钟

前言

flutter实现一个刻度尺,和大家一起学习探讨。

先上效果图:

概述

这个demo主要用到了flutter的自定义组件,无论是vue,android亦或者是flutter,
前端自定义组件的思路大致都相同。
都是从
1.封装原有组件
2.组合原有组件暴露方法接口  
3.利用绘制实现自定义高的组件(Flutter中的绘制也是依靠Canvas和Paint来实现的)

在Flutter中使用自绘方式自定义Widget,大致需要以下步骤:继承CustomPainter并重写paint方法和shouldRepaint方法,在写paint方法中绘制内容,使用CustomPaint来构建Widget

class MyPainter extends CustomPainter {
  final int subGridWidth;

  final String valueStr;


  //0:列表首item 1:中间item 2:尾item
  final int type;

  final Color colorType;

  Paint _linePaint;

  double _lineWidth = 2;

  MyPainter(this.subGridWidth, this.valueStr, this.type,
      this.colorType) {
    _linePaint = Paint()
      ..isAntiAlias = true
      ..style = PaintingStyle.stroke
      ..strokeWidth = _lineWidth
      ..color = colorType;
  }

  @override
  void paint(Canvas canvas, Size size) {
    drawLine(canvas, size);
    drawText(canvas, size);
  }

  void drawLine(Canvas canvas, Size size) {
    double startY, endY;
    switch (type) {
      case 0: //首元素只绘制上半部分
        startY = size.height / 2;
        endY = size.height;
        break;
      case 2: //尾元素只绘制下半部分
        startY = 0;
        endY = size.height / 2;
        break;
      default: //中间元素全部绘制
        startY = 0;
        endY = size.height;
    }

    //绘制竖线
    for (double y = startY; y <= endY; y += subGridWidth) {
      if (y == size.height / 2) {
        //中间为长刻度
        canvas.drawLine(Offset(size.width - 100, y),
            Offset(size.width - 100 + size.height * 3 / 5, y), _linePaint);
      } else {
        //其他为短刻度
        canvas.drawLine(Offset(size.width - 100, y),
            Offset(size.width - 100 + size.height / 3, y), _linePaint);
      }
    }
  }

  void drawText(Canvas canvas, Size size) {
    //文字水平方向居中对齐,竖直方向底对齐
    ui.Paragraph p = _buildText(valueStr, size.width);
    //获得文字的宽高
    double halfWidth = p.minIntrinsicWidth / 2;
    double halfHeight = p.height / 2;
    canvas.drawParagraph(
        p, Offset(size.width - 160, size.height / 2 - halfHeight));
  }

  ui.Paragraph _buildText(String content, double maxWidth) {
    ui.ParagraphBuilder paragraphBuilder =
        ui.ParagraphBuilder(ui.ParagraphStyle());
    paragraphBuilder.pushStyle(
      ui.TextStyle(
        fontSize: 14,
        color: colorType,
        //fontFamily: "Montserrat",
      ),
    );
    paragraphBuilder.addText(content);

    ui.Paragraph paragraph = paragraphBuilder.build();
    paragraph.layout(ui.ParagraphConstraints(width: maxWidth));

    return paragraph;
  }


  @override
  bool shouldRepaint(CustomPainter oldDelegate) => false;
}

其中

    _linePaint = Paint()
      //是否启动抗锯齿
      ..isAntiAlias = true
      //绘画风格,默认为填充,有fill和stroke两种
      ..style = PaintingStyle.stroke
      //画笔宽度
      ..strokeWidth = _lineWidth
      // 画笔颜色
      ..color = colorType;

通过创建一个段落构建器ui.ParagraphBuilder绘制字符串部分

    //字体样式
    paragraphBuilder.pushStyle(
      ui.TextStyle(
        fontSize: 14,
        color: colorType,
        //fontFamily: "Montserrat",
      ),
    );
    paragraphBuilder.addText(content);

    ui.Paragraph paragraph = paragraphBuilder.build();
    //限制绘制字符串宽度
    paragraph.layout(ui.ParagraphConstraints(width: maxWidth));

标尺一共有十个刻度,但是绘制的时候绘制11个刻度。第一个和最后一个只绘制一半的刻度。其他的全部绘制。目的将刻度尺的数值点(如:10分)对应的长刻度放在item的中间,便于绘制。

    switch (type) {
      case 0: //首元素只绘制上半部分
        startY = size.height / 2;
        endY = size.height;
        break;
      case 2: //尾元素只绘制下半部分
        startY = 0;
        endY = size.height / 2;
        break;
      default: //中间元素全部绘制
        startY = 0;
        endY = size.height;
    }

利用ListView绘制11个自定义组件打到10个刻度单位的标尺。

          ListView.builder(
            physics: ClampingScrollPhysics(),
            padding: EdgeInsets.all(0),
            scrollDirection: Axis.vertical,
            itemCount: 11,
            itemBuilder: (BuildContext context, int index) {
              //首尾空白元素
              int type;
              Color colorType;
              //第一个普通元素
              if (index == 0) {
                type = 0;
                //最后一个普通元素
              } else if (index == 10) {
                type = 2;
                //中间普通元素
              } else {
                type = 1;
              }
              if (index < 5) {
                colorType = Color(0xFF55D160);
              } else if (index > 6) {
                colorType = Color(0xFFFF2C2C);
              } else {
                colorType = Color(0xFFFFA82C);
              }

              return Container(
                child: NumberPickerItem(
                  subGridCount: 10,
                  subGridWidth: 5,
                  itemWidth: widget.width.toInt(),
                  valueStr: ((10 - index) * 10).toString() + '分',
                  type: type,
                  colorType: colorType,
                ),
              );
            },
          ),


欢迎大家和我一起学习分享flutter,项目会持续更新新的学习demo

此项目的github地址:项目地址

下面是我们的公众号:flutter编程笔记(code9871)

公众号 不定期分享自己的学习想法

饮水思源,本demo参考了HorizontalNumberPicker项目。