美文网首页Flutter
Flutter自定义之刻度尺

Flutter自定义之刻度尺

作者: 一笑轮回吧 | 来源:发表于2021-06-08 12:24 被阅读0次
    效果图
    ruler.gif
    案例分析
    1、效果功能分析
    • 滑动选择刻度尺
    • 支持中间选择刻度值
    • 支持设置最大最小值
    • 支持设置默认值
    • 支持设置大刻度的子刻度数
    • 支持设置步长
    • 支持设置刻度尺、数字的颜色及大小
    • 支持滑动选中回调
    • 支持刻度尺回弹效果
    2、功能拆解
    • 自定义Widget(继承StatefulWidget)。
    • 使用ListView实现水平滑动效果(3个子Widget,左右为空白,中间为刻度尺)。
    • 绘制刻度尺Widget(刻度线、刻度值)。
    • 监听滑动获取中间值并回调。
    • 手指抬起滑动停止粘性回弹。
    3、功能参数
    • 默认值
    • 最小值
    • 最大值
    • 步长
    • 刻度尺的宽高
    • 大刻度子子刻度数
    • 单刻度宽度
    • 刻度线颜色及宽度
    • 刻度尺数值颜色及宽度
    • 中间刻度线颜色
    • 选择回调
    4、功能代码实现

    小知识点:
    NotificationListener:

    if (notification is ScrollStartNotification) {
      print('滚动开始');
    }
    if (notification is ScrollUpdateNotification) {
      print('滚动中');
    }
    if (notification is ScrollEndNotification) {
      print('停止滚动');
      if (_scrollController.position.extentAfter == 0) {
        print('滚动到底部');
      }
      if (_scrollController.position.extentBefore == 0) {
        print('滚动到头部');
      }
    }
    
    完整代码
    import 'package:flutter/material.dart';
    import 'package:flutter/rendering.dart';
    
    ///自定义尺子
    class RulerView extends StatefulWidget {
      //默认值
      final int value;
    
      //最小值
      final int minValue;
    
      //最大值
      final int maxValue;
    
      //步数 一个刻度的值
      final int step;
    
      //尺子的宽度
      final int width;
    
      //尺子的高度
      final int height;
    
      //每个大刻度的子刻度数
      final int subScaleCountPerScale;
    
      //每一刻度的宽度
      final int subScaleWidth;
    
      //左右空白间距宽度
      double paddingItemWidth;
    
      //刻度尺选择回调
      final void Function(int) onSelectedChanged;
    
      //刻度颜色
      final Color scaleColor;
    
      //指示器颜色
      final Color indicatorColor;
    
      //刻度文字颜色
      final Color scaleTextColor;
    
      //刻度文字的大小
      final double scaleTextWidth;
    
      //刻度线的大小
      final double scaleWidth;
    
      //计算总刻度数
      int totalSubScaleCount;
    
      RulerView({
        Key key,
        this.value = 10,
        this.minValue = 0,
        this.maxValue = 100,
        this.step = 1,
        this.width = 200,
        this.height = 60,
        this.subScaleCountPerScale = 10,
        this.subScaleWidth = 8,
        this.scaleColor = Colors.black,
        this.scaleWidth = 2,
        this.scaleTextColor = Colors.black,
        this.scaleTextWidth = 15,
        this.indicatorColor = Colors.red,
        @required this.onSelectedChanged,
      }) : super(key: key) {
        //检查最大数-最小数必须是步数的倍数
        if ((maxValue - minValue) % step != 0) {
          throw Exception("(maxValue - minValue)必须是 step 的整数倍");
        }
        //默认值 不能低于最小值 或者大于最大值
        if (value < minValue || value > maxValue) {
          throw Exception(
              "value 必须在minValue和maxValue范围内(minValue<=value<=maxValue)");
        }
        //总刻度数
        totalSubScaleCount = (maxValue - minValue) ~/ step;
    
        //检查总刻度数必须是大刻度子刻度数的倍数
        if (totalSubScaleCount % subScaleCountPerScale != 0) {
          throw Exception(
              "(maxValue - minValue)~/step 必须是 subScaleCountPerScale 的整数倍");
        }
        //空白item的宽度
        paddingItemWidth = width / 2;
      }
    
      @override
      State<StatefulWidget> createState() {
        return RulerState();
      }
    }
    
    class RulerState extends State<RulerView> {
      ScrollController _scrollController;
    
      @override
      void initState() {
        super.initState();
        _scrollController = ScrollController(
          //初始位置
          initialScrollOffset:
              // ((默认值-最小值)/步长 )=第几个刻度,再乘以每个刻度的宽度就是初始位置
              (widget.value - widget.minValue) / widget.step * widget.subScaleWidth,
        );
      }
    
      @override
      Widget build(BuildContext context) {
        return Container(
          width: widget.width.toDouble(),
          height: widget.height.toDouble(),
          child: Stack(
            alignment: Alignment.topCenter,
            children: <Widget>[
              NotificationListener(
                onNotification: _onNotification,
                child: ListView.builder(
                  physics: ClampingScrollPhysics(),
                  padding: EdgeInsets.all(0),
                  controller: _scrollController,
                  scrollDirection: Axis.horizontal,
                  itemCount: 3,
                  itemBuilder: (BuildContext context, int index) {
                    //2边的空白控件
                    if (index == 0 || index == 2) {
                      return Container(
                        width: widget.paddingItemWidth,
                        height: 0,
                      );
                    } else {
                      //刻度尺
                      return Container(
                        child: RealRulerView(
                          subGridCount: widget.totalSubScaleCount,
                          subScaleWidth: widget.subScaleWidth,
                          step: widget.step,
                          minValue: widget.minValue,
                          height: widget.height,
                          scaleColor: widget.scaleColor,
                          scaleWidth: widget.scaleWidth,
                          scaleTextWidth: widget.scaleTextWidth,
                          scaleTextColor: widget.scaleTextColor,
                          subScaleCountPerScale: widget.subScaleCountPerScale,
                        ),
                      );
                    }
                  },
                ),
              ),
              //指示器
              Container(
                width: 2,
                height: widget.height / 2,
                color: widget.indicatorColor,
              ),
            ],
          ),
        );
      }
    
      ///监听刻度尺滚动通知
      bool _onNotification(Notification notification) {
        //ScrollNotification是基类 (ScrollStartNotification/ScrollUpdateNotification/ScrollEndNotification)
        if (notification is ScrollNotification) {
          print("-------metrics.pixels-------${notification.metrics.pixels}");
          //距离widget中间最近的刻度值
          int centerValue = widget.minValue +
              //notification.metrics.pixels水平滚动的偏移量
              //先计算出滚动偏移量是滚动了多少个刻度,然后取整,在乘以每个刻度的刻度值就是当前选中的值
              (notification.metrics.pixels / widget.subScaleWidth).round() *
                  widget.step;
    
          // 选中值回调
          if (widget.onSelectedChanged != null) {
            widget.onSelectedChanged(centerValue);
          }
          //如果是否滚动停止,停止则滚动到centerValue
          if (_scrollingStopped(notification, _scrollController)) {
            select(centerValue);
          }
        }
        return true; //停止通知
      }
    
      ///判断是否滚动停止
      bool _scrollingStopped(
        Notification notification,
        ScrollController scrollController,
      ) {
        return
            //停止滚动
            notification is UserScrollNotification
                //没有滚动正在进行
                &&
                notification.direction == ScrollDirection.idle &&
                scrollController.position.activity is! HoldScrollActivity;
      }
    
      ///选中值
      void select(int centerValue) {
        //根据(中间值-最小值)/步长=第几个刻度,然后第几个刻度乘以每个刻度的宽度就是移动的宽度
        double x =
            (centerValue - widget.minValue) / widget.step * widget.subScaleWidth;
        _scrollController.animateTo(x,
            duration: Duration(milliseconds: 200), curve: Curves.decelerate);
      }
    }
    
    ///真实刻度尺View
    class RealRulerView extends StatelessWidget {
      const RealRulerView({
        Key key,
        this.subGridCount,
        this.subScaleWidth,
        this.minValue,
        this.height,
        this.step,
        this.scaleColor,
        this.scaleWidth,
        this.scaleTextColor,
        this.scaleTextWidth,
        this.subScaleCountPerScale,
      }) : super(key: key);
    
      //刻度总数
      final int subGridCount;
    
      //每个刻度的宽度
      final int subScaleWidth;
    
      //刻度尺的高度
      final int height;
    
      //刻度尺最小值
      final int minValue;
    
      //每个大刻度的小刻度数
      final int subScaleCountPerScale;
    
      //步长 一刻度的值
      final int step;
    
      //刻度尺颜色
      final Color scaleColor;
    
      //刻度尺宽度
      final double scaleTextWidth;
    
      //刻度线宽度
      final double scaleWidth;
    
      //数字颜色
      final Color scaleTextColor;
    
      @override
      Widget build(BuildContext context) {
        double rulerWidth = (subScaleWidth * subGridCount).toDouble();
        double rulerHeight = this.height.toDouble();
        return CustomPaint(
          size: Size(rulerWidth, rulerHeight),
          painter: RulerViewPainter(
            this.subScaleWidth,
            this.step,
            this.minValue,
            this.scaleColor,
            this.scaleWidth,
            this.scaleTextColor,
            this.scaleTextWidth,
            this.subScaleCountPerScale,
          ),
        );
      }
    }
    
    class RulerViewPainter extends CustomPainter {
      final int subScaleWidth;
    
      final int step;
    
      final int minValue;
    
      final Color scaleColor;
    
      final Color scaleTextColor;
    
      final double scaleTextWidth;
    
      final int subScaleCountPerScale;
    
      final double scaleWidth;
    
      Paint linePaint;
    
      TextPainter textPainter;
    
      RulerViewPainter(
        this.subScaleWidth,
        this.step,
        this.minValue,
        this.scaleColor,
        this.scaleWidth,
        this.scaleTextColor,
        this.scaleTextWidth,
        this.subScaleCountPerScale,
      ) {
        //刻度尺
        linePaint = Paint()
          ..isAntiAlias = true
          ..style = PaintingStyle.stroke
          ..strokeWidth = scaleWidth
          ..color = scaleColor;
    
        //数字
        textPainter = TextPainter(
          textAlign: TextAlign.center,
          textDirection: TextDirection.ltr,
        );
      }
    
      @override
      void paint(Canvas canvas, Size size) {
        //绘制线
        drawLine(canvas, size);
        //绘制数字
        drawNum(canvas, size);
      }
    
      ///绘制线
      void drawLine(Canvas canvas, Size size) {
        //绘制横线
        canvas.drawLine(
          Offset(0, 0 + scaleWidth / 2),
          Offset(size.width, 0 + scaleWidth / 2),
          linePaint,
        );
        //第几个小格子
        int index = 0;
        //绘制竖线
        for (double x = 0; x <= size.width; x += subScaleWidth) {
          if (index % subScaleCountPerScale == 0) {
            canvas.drawLine(
                Offset(x, 0), Offset(x, size.height * 3 / 8), linePaint);
          } else {
            canvas.drawLine(Offset(x, 0), Offset(x, size.height / 4), linePaint);
          }
          index++;
        }
      }
    
      ///绘制数字
      void drawNum(Canvas canvas, Size size) {
        canvas.save();
        //坐标移动(0,0)点
        canvas.translate(0, 0);
        //每个大格子的宽度
        double offsetX = (subScaleWidth * subScaleCountPerScale).toDouble();
        int index = 0;
        //绘制数字
        for (double x = 0; x <= size.width; x += offsetX) {
          textPainter.text = TextSpan(
            text: "${minValue + index * step * subScaleCountPerScale}",
            style: TextStyle(color: scaleTextColor, fontSize: scaleTextWidth),
          );
          textPainter.layout();
          textPainter.paint(
            canvas,
            new Offset(
              -textPainter.width / 2,
              size.height - textPainter.height,
            ),
          );
          index++;
          canvas.translate(offsetX, 0);
        }
        canvas.restore();
      }
    
      @override
      bool shouldRepaint(CustomPainter oldDelegate) => false;
    }
    
    

    github:https://github.com/yixiaolunhui/my_flutter
    好了,话不多说,一笑轮回~~~~~

    相关文章

      网友评论

        本文标题:Flutter自定义之刻度尺

        本文链接:https://www.haomeiwen.com/subject/atsceltx.html