美文网首页Android学习记录
Flutter自定义可滑动尺子

Flutter自定义可滑动尺子

作者: 旺仔_100 | 来源:发表于2020-11-25 16:07 被阅读0次

    一、先上一个效果图


    Ruler-flutter.png

    二、说明一下
    很简单的一个自定义view:就是绘制一个小三角,一顿计算画线,绘制数字。可向左滑动尺子回调小三角指定的当前值。

    三、具体实现思路说明

    • 先讲一下静态的尺子绘制
      1.绘制三角形,使用path,不熟悉path的同学可以先学习下path。因为绘制起点是原点,为了给三角形一点空间,可以移动一下画布。
    canvas.translate(widget.leftPadding - widget.sanWidth / 2, 3);
    

    三角形其实也很好绘制,就是等边三角形的三个点,依次relativeLineTo进行连接。

     Path path = Path();
        path
          ..moveTo(0, 0)
          ..relativeLineTo(widget.sanWidth, 0)
          ..relativeLineTo(-widget.sanWidth / 2, sqrt(3) * widget.sanWidth / 2)
          ..close();
    
        canvas.drawPath(path, Paint()..color = Colors.purple);
    

    2.绘制尺子,其实就是 canvas.drawLine(),计算一下在那个位置需要划线,依次划线。每10线的时候需要用粗的画笔,然后还需要在线的下面画文字。每5个线的时候需要用第二短的线 画线。其他的都用最短最细的线 划线。

    for (int i = 0; i <= widget.maxScale; i++) {
          if (i % 10 == 0) {
            canvas.drawLine(
                Offset(i * 5.0, 0),
                Offset(i * 5.0, widget.line3Height),
                Paint()
                  ..color = widget.lineColor
                  ..strokeWidth = widget.lineWidth);
            drawText(i, canvas);
          } else if (i % 5 == 0) {
            canvas.drawLine(
                Offset(i * 5.0, 0),
                Offset(i * 5.0, widget.line2Height),
                Paint()
                  ..color = widget.lineColor
                  ..strokeWidth = widget.lineWidth);
          } else {
            canvas.drawLine(
                Offset(i * 5.0, 0),
                Offset(i * 5.0, widget.line1Height),
                Paint()
                  ..color = widget.lineColor
                  ..strokeWidth = widget.line1Width);
          }
        }
    

    3.绘制文字,flutter的绘制文字比Android稍微复杂一点点,但是可配置性也更加好一点。

      void drawText(int i, Canvas canvas) {
        var textPainter = TextPainter(
            text: TextSpan(
                text: "$i", style: TextStyle(fontSize: 12, color: Colors.black)),
            textDirection: TextDirection.ltr,
            textAlign: TextAlign.left);
        textPainter.layout();
        textPainter.paint(
            canvas, Offset(i * 5.0 - 2, widget.line3Height + widget.paddingText));
      }
    

    到此为止,静态的尺子就已经绘制完成。下面说一下,尺子的滚动,以及对应的值回调。

    4.GestureDetector是处理事件的widget,它有一个回调onPanEnd,里面可以拿到DragEndDetails 这个可以拿到很多东西,例如当前点击位置的x,y轴信息,滑动的增量值等。我们使用的就是增量值,就是本次滑动多远DragUpdateDetails.delta.dx。当然我们需要对增量值进行处理,例如不允许右滑,或者往左边只允许滑动80%等处理。然后把处理后的值给 ValueNotifier<double>,它可以触发自定义的widget重新绘制。

    class RulerState extends State<RulerWidget> {
      ValueNotifier<double> _dx = ValueNotifier(0.0);
    
      @override
      Widget build(BuildContext context) {
        return Container(
          child: GestureDetector(
            onPanUpdate: parse,
            child: CustomPaint(
              size: Size(
                  widget.maxScale * widget.unit +
                      widget.leftPadding * 2 +
                      widget.maxScale * widget.lineWidth,
                  widget.rulerHeight),
              painter: RulerCustomPainter(widget, _dx),
            ),
          ),
        );
      }
    
      double dx = 0;
    
      parse(DragUpdateDetails details) {
        dx += details.delta.dx;
        if (dx > 0) {
          dx = 0;
        }
    
        if (dx < -80 * widget.unit) {
          dx = -80.0 * widget.unit;
        }
        _dx.value = dx;
    
        if (widget.onChanged != null) {
          widget.onChanged(dx);
        }
      }
    }
    

    上面的onChanged会回调对应的像素,稍微处理就是当前的刻度值。

    滑动的原理实际上就是把画布向右边移动,关键代码

        ///重新绘制的时候
        canvas.translate(dx.value, 0);
    

    在自定义的widget中,一定记得把ValueNotifier<double> dx;传递给父类,否则不会重绘

      RulerCustomPainter(this.widget, this.dx) : super(repaint: dx);
    

    到此为止就完了,其实还是很简单的。附上完整代码。

    
    class GestureDemo extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: "gestureDemo",
          home: Scaffold(
            appBar: AppBar(),
            body: RulerWidget(onChanged: (double dx) {
    //              print("$dx");
              print("当前刻度值是${dx / 5}");
            }),
          ),
        );
      }
    }
    
    ///绘制一个尺子
    ///
    class RulerWidget extends StatefulWidget {
      ///尺子距离左边的距离
      double leftPadding;
    
      ///尺子的线宽
      double lineWidth;
      double line1Width;
    
      ///尺子的线的高度
      double line1Height;
    
      ///尺子第二高度
      double line2Height;
    
      ///尺子的第三高度
      double line3Height;
      Color lineColor;
      Color indicationColor;
    
      ///文字样式
      TextStyle style;
    
      ///尺子的最大刻度
      double maxScale;
    
      /// 5个dp对应一个刻度
      int unit;
    
      ///尺子的高度
      double rulerHeight;
    
      ///刻度和尺子的距离
      double paddingText;
    
      ///等边三角形的宽度
      double sanWidth;
    
      final void Function(double) onChanged;
    
      RulerWidget(
          {this.leftPadding = 5,
          this.lineWidth = 2,
          this.line1Width = 1,
          this.line1Height = 10,
          this.line2Height = 15,
          this.line3Height = 20,
          this.lineColor = Colors.blue,
          this.indicationColor = Colors.purple,
          this.maxScale = 100.0,
          this.unit = 5,
          this.rulerHeight = 50,
          this.paddingText = 5,
          this.sanWidth = 5,
          @required this.onChanged,
          this.style});
    
      @override
      State<StatefulWidget> createState() {
        return RulerState();
      }
    }
    
    class RulerState extends State<RulerWidget> {
      ValueNotifier<double> _dx = ValueNotifier(0.0);
    
      @override
      Widget build(BuildContext context) {
        return Container(
          child: GestureDetector(
            onPanUpdate: parse,
            child: CustomPaint(
              size: Size(
                  widget.maxScale * widget.unit +
                      widget.leftPadding * 2 +
                      widget.maxScale * widget.lineWidth,
                  widget.rulerHeight),
              painter: RulerCustomPainter(widget, _dx),
            ),
          ),
        );
      }
    
      double dx = 0;
    
      parse(DragUpdateDetails details) {
        dx += details.delta.dx;
        if (dx > 0) {
          dx = 0;
        }
    
        if (dx < -80 * widget.unit) {
          dx = -80.0 * widget.unit;
        }
        _dx.value = dx;
    
        if (widget.onChanged != null) {
          widget.onChanged(dx);
        }
      }
    }
    
    class RulerCustomPainter extends CustomPainter {
      RulerWidget widget;
      ValueNotifier<double> dx;
    
      RulerCustomPainter(this.widget, this.dx) : super(repaint: dx);
    
      @override
      void paint(Canvas canvas, Size size) {
        /// 当刻度是10的倍数的时候绘制最长刻度,并在刻度下面绘制文字
        /// 当刻度是5的倍数的时候绘制绘制第二长刻度
        /// 其他情况绘制一般的刻度
    
        canvas.clipRect(Offset.zero & size);
    
        canvas.save();
        canvas.translate(widget.leftPadding - widget.sanWidth / 2, 3);
        Path path = Path();
        path
          ..moveTo(0, 0)
          ..relativeLineTo(widget.sanWidth, 0)
          ..relativeLineTo(-widget.sanWidth / 2, sqrt(3) * widget.sanWidth / 2)
          ..close();
    
        canvas.drawPath(path, Paint()..color = Colors.purple);
        canvas.restore();
    
        canvas.translate(widget.leftPadding, widget.leftPadding + 3);
    
        ///重新绘制的时候
        canvas.translate(dx.value, 0);
    
        for (int i = 0; i <= widget.maxScale; i++) {
          if (i % 10 == 0) {
            canvas.drawLine(
                Offset(i * 5.0, 0),
                Offset(i * 5.0, widget.line3Height),
                Paint()
                  ..color = widget.lineColor
                  ..strokeWidth = widget.lineWidth);
            drawText(i, canvas);
          } else if (i % 5 == 0) {
            canvas.drawLine(
                Offset(i * 5.0, 0),
                Offset(i * 5.0, widget.line2Height),
                Paint()
                  ..color = widget.lineColor
                  ..strokeWidth = widget.lineWidth);
          } else {
            canvas.drawLine(
                Offset(i * 5.0, 0),
                Offset(i * 5.0, widget.line1Height),
                Paint()
                  ..color = widget.lineColor
                  ..strokeWidth = widget.line1Width);
          }
        }
      }
    
      void drawText(int i, Canvas canvas) {
        var textPainter = TextPainter(
            text: TextSpan(
                text: "$i", style: TextStyle(fontSize: 12, color: Colors.black)),
            textDirection: TextDirection.ltr,
            textAlign: TextAlign.left);
        textPainter.layout();
        textPainter.paint(
            canvas, Offset(i * 5.0 - 2, widget.line3Height + widget.paddingText));
      }
    
      @override
      bool shouldRepaint(RulerCustomPainter oldDelegate) {
        return dx != oldDelegate.dx;
      }
    }
    
    

    相关文章

      网友评论

        本文标题:Flutter自定义可滑动尺子

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