在博客《Flutter之Stepper简单应用》一文中简单介绍了Stepper的使用方法,现在趁热打铁,就其实现原理来解析一波,算是加深对Flutter相关知识的学习。
在开篇之前需要简单的了解Flutter的如下知识储备:
Row组件:用来水平排列一个布局
Column组件:用来竖直排列一个布局。
下面就来详细分析Stepper的实现原理,我们知道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):
上面这个图是Stepper的一个Step,确切的说是上述ListView的一个item。从源码得知上图从整体上可以分为如下三个两部分(图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):
从上述for循环来看有如下几个关键点:
a)children这个属性是Column的集合
b)处理每个step的点击事件,是在InkWell这个组件的onTap方法做处理的(至于InkWell这个组件的作用,后面会专门研究说明)
c)构建图3绿色线条包围的部分: 圆圈+竖线+title+subTitle 是作为InkWell组件的child属性,通过_buildVerticalHeader
方法来实现
d)构建图3红色矩形部分和紫色矩形框的部分,是通过_buildVerticalBody
方法来实现的。通过方法名称就知道图3 红色矩形框部分是每个Step的body
_buildVerticalBody
具体解析:如下图(图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:
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的组件
},
///省略其余代码
)
_buildVerticalHeader
具体解析:上面讲了_buildVerticalBody方法,下面就来详细讲讲图3中的绿色矩形框的部分是怎么实现的(图8),具体就是通过_buildVerticalHeader方法来实现的:
让我们看看_buildVerticalHeader都做了些什么:
注意_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,运行如下图:
至于怎么绘制_buildIcon的,在这里就不在赘述了,本来Stepper的源码也不难懂,感兴趣的话读者可以自行查阅,博主偷个懒就不写了