Flutter之Stepper源码浅析

在博客《Flutter之Stepper简单应用》一文中简单介绍了Stepper的使用方法,现在趁热打铁,就其实现原理来解析一波,算是加深对Flutter相关知识的学习。
在开篇之前需要简单的了解Flutter的如下知识储备:
Row组件:用来水平排列一个布局
Column组件:用来竖直排列一个布局。

下面就来详细分析Stepper的实现原理,我们知道Stepper运行效果如下所示(图1):
Flutter之Stepper源码浅析_第1张图片
观察其源码可知,整个跟布局是一个ListView,在_StepperState整个类的build(BuildContext context)方法里可以看出端倪:

  @override
  Widget build(BuildContext context) {
    switch (widget.type) {
      case StepperType.vertical://构建竖直方向的Stepper列表:如上图
        return _buildVertical();
      case StepperType.horizontal:
        return _buildHorizontal();//构建水平方向的Stepper列表
    }
    return null;
  }

本片博文为了方便就分析_buildVertical方法,即对竖直方向的Stepper源码作分析。而_buildVertical方法返回的就是一个listView(ListView的简单使用可点此了解详情):

Widget _buildVertical() {
      ///省略childrend的构建代码
      return ListView(
         shrinkWrap: true,
          children: children,///核心
    )
}

先注意上面的children变量,这个变量是构建上图所示列表的关键。看下图某一个展开的Step(图2):
Flutter之Stepper源码浅析_第2张图片

上面这个图是Stepper的一个Step,确切的说是上述ListView的一个item。从源码得知上图从整体上可以分为如下三个两部分(图3):
Flutter之Stepper源码浅析_第3张图片
如上图,每个Step都包含上图所示的三部分:
1)绿色矩形框部分:线条+实心圆+线条
2)红色矩形框部分:作为每个Step的Body或者Content
3)紫色矩形框部分:是一个线条,该线条跟绿色矩形框的线条粗细一样,且位置也一样确保组成一条直线,至于怎么确保是一条线的,24px这个值是关键,后面会有说明

 Step(
      title: Text('stepTitle B'),
      subtitle:Text('activie为true'),
      isActive: true,
      content:  Card(
          color:Colors.blue,
          child:SizedBox(
            child: Center( child: Text("设置isActive: true,此时步骤2为蓝色"),),
            width: 600.0,
            height: 100.0,
          )
      ),
    )

我们在使用Stepper的时候就是Step对象组成的集合List传给Stepper组件,确切地说是赋值给Stepper的steps,而后通过_buildVertical()方法对steps进行遍历,而后对每个Step都生成如图2所示的Widget的过程,详细过程如下图4):
Flutter之Stepper源码浅析_第4张图片
从上述for循环来看有如下几个关键点:
a)children这个属性是Column的集合
b)处理每个step的点击事件,是在InkWell这个组件的onTap方法做处理的(至于InkWell这个组件的作用,后面会专门研究说明)
c)构建图3绿色线条包围的部分: 圆圈+竖线+title+subTitle 是作为InkWell组件的child属性,通过_buildVerticalHeader方法来实现
d)构建图3红色矩形部分和紫色矩形框的部分,是通过_buildVerticalBody方法来实现的。通过方法名称就知道图3 红色矩形框部分是每个Step的body


_buildVerticalBody具体解析:

如下图(图5)
Flutter之Stepper源码浅析_第5张图片
通过图5可以发现body的布局也不复杂:
1、上图代码第6~13行绘制的就是图3绿色矩形框所示的线条。注意当最后一个Step的时候是不会绘制线条的。有_isLast来判断是否是最后一个Step.注意线条只设置了宽度为1.0,位于一个宽度为24.0的SizeBox的正中间;并没说有设置其高度,也就是说高度自适应。
第7行的SizeBox宽度为24px,且距离屏幕左边也是24px.而线条正位于这个24px宽度组件的正中间,这个24px是很重要的参数,确保了这个线条和图3部分圆圈上的线条位于同一条直线上(这一点下面会有所说明)
2、上图代码第16~33行是绘制图3红色矩形框所示的body部分,通过AnimateCrossFade这个组件来进行布局的切换:如果如果_isCurrent(index)则显示secondChild也就是图3红色矩形框的部分,也就是当然的Step处于打开状态;其余的Step则处于关闭状态,关闭状态就是显示firstChild,而这个child就是一个高度是0的Container来模拟关闭状态。可以看如下运行效果图图6
Flutter之Stepper源码浅析_第6张图片

3)图5第23行的_buildVerticalControls()方法就是用来布局上面的Continue和Cancel按钮的。看图6的效果,每个Step都有Continue和cancel赶按钮,不是很美观,能否去掉这两个按钮呢?答案是能!先来看看_buildVerticalControls具体都做了什么事儿:

Widget _buildVerticalControls() { 
    //自定义取消和继续按钮
    if (widget.controlsBuilder != null)
      return widget.controlsBuilder(context, onStepContinue: widget.onStepContinue, onStepCancel: widget.onStepCancel);
     //省略部分与博文无关代码
    return Container(child: ConstrainedBox(
        child: Row
          children: [
            FlatButton((//构建continue按钮
              onPressed: widget.onStepContinue,//相应onStepContinue方法
               ///省略部分代码
              child: Text(localizations.continueButtonLabel),
            ),
            Container(//构建cancel按钮
              margin: const EdgeInsetsDirectional.only(start: 8.0),
              child: FlatButton(
                onPressed: widget.onStepCancel,//响应onStepCancel方法
               ///省略部分代码
                child: Text(localizations.cancelButtonLabel),
              ),
            ),
          ],
        ),
      ),
    );
  }

显而易见,如果我们为Stepper配置了controlsBuilder 这个变量,就使用了我们自定义的Continue和cancel按钮。否则Stepper就为我们自动生成了这个按钮。所以取消这两个按钮的的方法就很简单了:我们设置一个高度是0的Widget即可,代码如下:

Stepper(
       controlsBuilder:
           (BuildContext context, {VoidCallback onStepContinue, VoidCallback onStepCancel}) {
             return Container(height: 0.0,);//返回一个高度是0的组件
           },
           ///省略其余代码
           )

运行效果如下所示图7
Flutter之Stepper源码浅析_第7张图片
_buildVerticalHeader具体解析:

上面讲了_buildVerticalBody方法,下面就来详细讲讲图3中的绿色矩形框的部分是怎么实现的(图8),具体就是通过_buildVerticalHeader方法来实现的:
Flutter之Stepper源码浅析_第8张图片
让我们看看_buildVerticalHeader都做了些什么:
Flutter之Stepper源码浅析_第9张图片
注意_buildVerticalHeader返回的Container的leftMargin和rightMargin为24px,这样的话就跟前文在分析_buildVerticalBody的时候那些粉红色字体的文字,这个24px确保了_buildVerticalBody的SizeBox和_buildVerticalHeader返回的Container的leftMargin都是一样的,均为24px。
然后看_buildVerticalHeader中有一个Column,分别用来绘制线条+圆行+线条,如下:

 Column(
                  children: [
                    _buildLine(!_isFirst(index)),//绘制圆行上面的线条
                    _buildIcon(index),//绘制实心圆或者三角形
                    _buildLine(!_isLast(index)),//绘制圆行下面的线条
                  ]
              ),

绘制线条_buildLine的也很简单,就是返回一个宽度是1.0px高度是16px的Container:

//绘制线条
 Widget _buildLine(bool visible) {
    return Container(
      width: visible ? 1.0 : 0.0,
      height: 16.0,
      color: Colors.grey.shade400,
    );
  }

注意这个_buildIcon,根据运行效果来看这个buildIcon根据StepStage来判断是否绘制圆行或者三角形。另外需要注意的是圆行的直径也是24px,这就确保了跟_buildVerticalBody绘制的那条直线完美的重合,不信的话你可以改改Stepper的源码,随便设置一个非24px值,会发现错位现象,为了实验博主将圆行的大小由24px改成38px,运行如下图:
Flutter之Stepper源码浅析_第10张图片

至于怎么绘制_buildIcon的,在这里就不在赘述了,本来Stepper的源码也不难懂,感兴趣的话读者可以自行查阅,博主偷个懒就不写了

你可能感兴趣的:(flutter,Flutter)