美文网首页Flutter圈子前端文章收集Flutter中文社区
简单实现一下Flutter的Stepper做一个侧边进度条

简单实现一下Flutter的Stepper做一个侧边进度条

作者: 七分小熊猫 | 来源:发表于2019-03-22 14:33 被阅读26次

    因为 flutter 提供的 Stepper 无法满足业务需求,于是只好自己实现一个了

    flutter Stepper 的样式

    原生 Stepper

    我实现的 Stepper

    我实现的 Stepper

    这个或许根本不叫 Stepper 吧,也没有什么步骤,只是当前的配送进度,不需要数字步骤,希望所有内容都能显示出来,原生的则是有数字表示第几步,把当前步骤外的其他的内容都隐藏了。

    那么开始进行分析,整个需求中,有点难度的也就是这个左边的进度线了。我们把进度看做一个 ListView ,每条进度都是一个 Item

    item

    先来看怎么布局这个Item,一开始我是想在最外层做成一个 Row 布局,像这样

    image.png

    左边是圆和线,右边是内容,然而我太天真了,左边的 线 高度没法跟随右边的高度,即右边有多高,左边就有多高。也就是我必须给左边的View设置一个高度,否则就没法显示出来。。。绝望ing,如果我左边写死了高度,右边的内容因为用户字体过大而高度超过左边的线,那么两个 Item 之间的线就没法连在一起了。

    然后我看到了 Flutter 的 Stepper ,虽然不符合需求,但是人家左边的线是 Item 和 Item 相连的,我就看了下他的源码,豁然开朗,人家的布局是个 Colum 。整体看起来是这样的。

    image.png

    这样的话,就好理解了,Colum 的第一个 child 我们称为 Head , 第二个 child 我们称为 Body 。

    Head 的布局如图是个 Row,左边是圆和线,右边是个 Text。
    Body 的布局是个 Container , 包含了一个 Column ,Column 里面就是两个Text。相信小伙伴们已经想到了,Body左边的那条线就是 Container 的 border

    圆和线我选择自己绘制,练习一下,下面是线和圆的自定义View代码

    
    class LeftLineWidget extends StatelessWidget {
      final bool showTop;
      final bool showBottom;
      final bool isLight;
    
      const LeftLineWidget(this.showTop, this.showBottom, this.isLight);
    
      @override
      Widget build(BuildContext context) {
        return Container(
          margin: EdgeInsets.symmetric(horizontal: 16),//圆和线的左右外边距
          width: 16,
          child: CustomPaint(
            painter: LeftLinePainter(showTop, showBottom, isLight),
          ),
        );
      }
    }
    
    class LeftLinePainter extends CustomPainter {
      static const double _topHeight = 16; //圆上的线高度
      static const Color _lightColor = XColors.mainColor;//圆点亮的颜色
      static const Color _normalColor = Colors.grey;//圆没点亮的颜色
    
      final bool showTop; //是否显示圆上面的线
      final bool showBottom;//是否显示圆下面的线
      final bool isLight;//圆形是否点亮
    
      const LeftLinePainter(this.showTop, this.showBottom, this.isLight);
    
      @override
      void paint(Canvas canvas, Size size) {
        double lineWidth = 2; // 竖线的宽度
        double centerX = size.width / 2; //容器X轴的中心点
        Paint linePain = Paint();// 创建一个画线的画笔
        linePain.color = showTop ? Colors.grey : Colors.transparent;
        linePain.strokeWidth = lineWidth;
        linePain.strokeCap = StrokeCap.square;//画线的头是方形的
        //画圆上面的线
        canvas.drawLine(Offset(centerX, 0), Offset(centerX, _topHeight), linePain);
        //依据下面的线是否显示来设置是否透明
        linePain.color = showBottom ? Colors.grey : Colors.transparent;
        // 画圆下面的线
        canvas.drawLine(
            Offset(centerX, _topHeight), Offset(centerX, size.height), linePain);
        // 创建画圆的画笔
        Paint circlePaint = Paint();
        circlePaint.color = isLight ? _lightColor : _normalColor;
        circlePaint.style = PaintingStyle.fill;
        // 画中间的圆
        canvas.drawCircle(Offset(centerX, _topHeight), centerX, circlePaint);
      }
    
      @override
      bool shouldRepaint(CustomPainter oldDelegate) {
        if(oldDelegate is LeftLinePainter){
          LeftLinePainter old = oldDelegate;
          if(old.showBottom!=showBottom){
            return true;
          }
          if(old.showTop!=showTop){
            return true;
          }
          if(old.isLight!=isLight){
            return true;
          }
          return false;
        }
        return true;
      }
    }
    
    

    左侧的圆和线是3个部分,分别是圆的上面那条线,和圆,以及圆下面的那条线,
    通过 showTopshowBottom 来控制上面那条线和下面那条线是否显示。

    圆和线解决了,我就把Head组装起来

    Row(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: <Widget>[
        // 圆和线
        Container( 
          height: 32,
          child: LeftLineWidget(false, true, true),
        ),
        Expanded(child: Container(
          padding: EdgeInsets.only(top: 4),
          child: Text(
            '天天乐超市(限时降价)已取货',
            style: TextStyle(fontSize: 18),
            overflow: TextOverflow.ellipsis,
          ),
        ))
      ],
    )
    

    编译运行后截图

    image.png

    (这里截图跟之前不一样是因为我又单独建立了一个demo)

    接下来写下面的 Body

    Container(
      //这里写左边的那条线
      decoration: BoxDecoration(
        border:Border(left: BorderSide(
          width: 2,// 宽度跟 Head 部分的线宽度一致,下面颜色也是
          color: Colors.grey
        ))
      ),
      margin: EdgeInsets.only(left: 23), //这里的 left 的计算在代码块下面解释怎么来的
      padding: EdgeInsets.fromLTRB(22,0,16,16),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: <Widget>[
          Text('配送员:吴立亮 18888888888'),
          Text('时间:2018-12-17 09:55:22')
        ],
      ),
    )
    

    这里说一下 margin 的 left 参数值是怎么计算的。
    设置这个是为了 Body 的左边框跟上面 Head 的线能对齐连上,不能错开。
    首先我们的 LeftLineWidget 是有个 margin 的,他的左右外边距是16,自身的宽度是16。因为线在中间,所以宽度要除以2。那就是:左外边距+宽度除以2 left = 16 + 16/2 算出来是24。

    可是我们这里写的23,是因为边框的线的宽度是从容器的边界往里面走的。我们算出来的边距会让 Body 的容器边界在上面的线中间。看起来像这样。

    image.png

    所以还要减去线宽的一半,线宽是2,除以2等于1, 最后left = 16+(16/2)-(2/2)=23,翻译成中文 left = LeftLineWidget左边距+(LeftLineWidget宽度➗2)-(LeftLineWidget线宽➗2)

    最后看起来像这样:


    多复制几个


    image

    最后一item要隐藏边框,把边框线颜色设置为透明即可。

    渲染树是这样的

    渲染树

    最后奉上完整代码:

    import 'package:flutter/material.dart';
    
    void main() => runApp(MyApp());
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Stepper',
          home: Scaffold(
            appBar: AppBar(
              elevation: 0,
              title: Text('自定义View'),
            ),
            body: ListView(
              shrinkWrap: true,
              children: <Widget>[
                Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: <Widget>[
                    Row(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: <Widget>[
                        Container(// 圆和线
                          height: 32,
                          child: LeftLineWidget(false, true, true),
                        ),
                        Expanded(child: Container(
                          padding: EdgeInsets.only(top: 4),
                          child: Text(
                            '天天乐超市(限时降价)已取货',
                            style: TextStyle(fontSize: 18),
                            overflow: TextOverflow.ellipsis,
                          ),
                        ))
                      ],
                    ),
                    Container(
                      decoration: BoxDecoration(
                        border:Border(left: BorderSide(
                          width: 2,
                          color: Colors.grey
                        ))
                      ),
                      margin: EdgeInsets.only(left: 23),
                      padding: EdgeInsets.fromLTRB(22,0,16,16),
                      child: Column(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: <Widget>[
                          Text('配送员:吴立亮 18888888888'),
                          Text('时间:2018-12-17 09:55:22')
                        ],
                      ),
                    )
                  ],
                ),
                Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: <Widget>[
                    Row(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: <Widget>[
                        Container(// 圆和线
                          height: 32,
                          child: LeftLineWidget(true, true, false),
                        ),
                        Expanded(child: Container(
                          padding: EdgeInsets.only(top: 4),
                          child: Text(
                            '天天乐超市(限时降价)已取货',
                            style: TextStyle(fontSize: 18),
                            overflow: TextOverflow.ellipsis,
                          ),
                        ))
                      ],
                    ),
                    Container(
                      decoration: BoxDecoration(
                          border:Border(left: BorderSide(
                              width: 2,
                              color: Colors.grey
                          ))
                      ),
                      margin: EdgeInsets.only(left: 23),
                      padding: EdgeInsets.fromLTRB(22,0,16,16),
                      child: Column(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: <Widget>[
                          Text('配送员:吴立亮 18888888888'),
                          Text('时间:2018-12-17 09:55:22')
                        ],
                      ),
                    )
                  ],
                ),
                Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: <Widget>[
                    Row(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: <Widget>[
                        Container(// 圆和线
                          height: 32,
                          child: LeftLineWidget(true, false, false),
                        ),
                        Expanded(child: Container(
                          padding: EdgeInsets.only(top: 4),
                          child: Text(
                            '天天乐超市(限时降价)已取货',
                            style: TextStyle(fontSize: 18),
                            overflow: TextOverflow.ellipsis,
                          ),
                        ))
                      ],
                    ),
                    Container(
                      decoration: BoxDecoration(
                          border:Border(left: BorderSide(
                              width: 2,
                              color: Colors.transparent
                          ))
                      ),
                      margin: EdgeInsets.only(left: 23),
                      padding: EdgeInsets.fromLTRB(22,0,16,16),
                      child: Column(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: <Widget>[
                          Text('配送员:吴立亮 18888888888'),
                          Text('时间:2018-12-17 09:55:22')
                        ],
                      ),
                    )
                  ],
                ),
              ],
            ),
          ),
        );
      }
    }
    
    class LeftLineWidget extends StatelessWidget {
      final bool showTop;
      final bool showBottom;
      final bool isLight;
    
      const LeftLineWidget(this.showTop, this.showBottom, this.isLight);
    
      @override
      Widget build(BuildContext context) {
        return Container(
          margin: EdgeInsets.symmetric(horizontal: 16),
          width: 16,
          child: CustomPaint(
            painter: LeftLinePainter(showTop, showBottom, isLight),
          ),
        );
      }
    }
    
    class LeftLinePainter extends CustomPainter {
      static const double _topHeight = 16;
      static const Color _lightColor = Colors.deepPurpleAccent;
      static const Color _normalColor = Colors.grey;
    
      final bool showTop;
      final bool showBottom;
      final bool isLight;
    
      const LeftLinePainter(this.showTop, this.showBottom, this.isLight);
    
      @override
      void paint(Canvas canvas, Size size) {
        double lineWidth = 2;
        double centerX = size.width / 2;
        Paint linePain = Paint();
        linePain.color = showTop ? Colors.grey : Colors.transparent;
        linePain.strokeWidth = lineWidth;
        linePain.strokeCap = StrokeCap.square;
        canvas.drawLine(Offset(centerX, 0), Offset(centerX, _topHeight), linePain);
        Paint circlePaint = Paint();
        circlePaint.color = isLight ? _lightColor : _normalColor;
        circlePaint.style = PaintingStyle.fill;
        linePain.color = showBottom ? Colors.grey : Colors.transparent;
        canvas.drawLine(
            Offset(centerX, _topHeight), Offset(centerX, size.height), linePain);
        canvas.drawCircle(Offset(centerX, _topHeight), centerX, circlePaint);
      }
    
      @override
      bool shouldRepaint(CustomPainter oldDelegate) {
        return true;
      }
    }
    
    

    相关文章

      网友评论

        本文标题:简单实现一下Flutter的Stepper做一个侧边进度条

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