文章已同步至掘金:https://juejin.cn/post/6844903956825505799
欢迎访问😃,有任何问题都可留言评论哦~
前面的文章,我们说了:
线性布局—Flutter 布局控件篇–>Row、Column
弹性布局—Flutter 布局控件篇–>Flex、Expanded
接下来我们说一下流式布局
在线性布局Row
中,如果子widget超出屏幕范围,则会报溢出错误,如:
Row(
children: <Widget>[
Text("xxx"*100)
],
);
运行效果:
那么有人会说了,怎么不用Text
组件中的maxLines
和overflow
呢?
很显然,Row
组件的主轴是水平方向的,是让其内容水平排布的,Row
默认只有一行,如果超出屏幕不会折行。这两个属性加上的话,是没有效果的
但是如果把Row
换成Column
,那么这两个属性就会起作用了
但是我们如果用上面两个属性来指定的话,这样也不好,因为你并不确定有多少内容,所以我们想让他如果超出屏幕显示范围的话,让他自动折行,这种布局称之为流式布局。
Flutter中通过Wrap
和Flow
来支持流式布局
Wrap
流式布局组件,如果内容宽度超出屏幕宽度,会自动折行显示
源码示例
Wrap
的构造函数:
Wrap({
...
this.direction = Axis.horizontal,
this.alignment = WrapAlignment.start,
this.spacing = 0.0,
this.runAlignment = WrapAlignment.start,
this.runSpacing = 0.0,
this.crossAxisAlignment = WrapCrossAlignment.start,
this.textDirection,
this.verticalDirection = VerticalDirection.down,
List<Widget> children = const <Widget>[],
})
属性解释
我们可以看到Wrap
的很多属性在Row
、Column
和Flex
中也有,
所以重复的属性就不说了,如果不知道的话,请移步:Flutter 布局控件篇–>Row、Column
这里说Wrap
特有的几个属性
spacing
主轴方向子组件的间距
相信学过HTML和CSS的话,应该不陌生
在这里就是组件与组件之间的间隙
例如:
Wrap(
spacing: 20,
children: <Widget>[
Text("xxx"),
Text('yyy')
],
)
spacing: 20,
表示的是xxx
与yyy
之间的距离
runSpacing
组件纵轴方向的间距,不做过多解释
alignment
纵轴方向的对齐方式
例如:
Wrap(
spacing: 20, //主轴(水平)方向间距
runSpacing: 50, //纵轴方向间距
alignment:WrapAlignment.center , //沿主轴方向居中
children: <Widget>[
Text("xxx"*20),
Text('yyy')
],
)
运行效果:
Flow
一般很少使用Flow
,因为太复杂了。Flow
主要用于一些需要自定义布局策略或性能要求较高(如动画中)的场景。
优点:
-
性能好;
Flow
是一个对子组件尺寸以及位置调整非常高效的控件,Flow
用转换矩阵在对子组件进行位置调整的时候进行了优化:在Flow
定位过后,如果子组件的尺寸或者位置发生了变化,在FlowDelegate
中的paintChildren()
方法中调用context.paintChild
进行重绘,而context.paintChild
在重绘时使用了转换矩阵,并没有实际调整组件位置。 -
灵活;由于我们需要自己实现
FlowDelegate
的paintChildren()
方法,所以我们需要自己计算每一个组件的位置,因此,可以自定义布局策略。
缺点:
-
使用复杂。
-
不能自适应子组件大小,必须通过指定父容器大小或实现
TestFlowDelegate
的getSize
返回固定大小。
例子:
对六个色块进行自定义流式布局:
Flow(
delegate: TestFlowDelegate(margin: EdgeInsets.all(10.0)),
children: <Widget>[
new Container(width: 80.0, height:80.0, color: Colors.red,),
new Container(width: 80.0, height:80.0, color: Colors.green,),
new Container(width: 80.0, height:80.0, color: Colors.blue,),
new Container(width: 80.0, height:80.0, color: Colors.yellow,),
new Container(width: 80.0, height:80.0, color: Colors.brown,),
new Container(width: 80.0, height:80.0, color: Colors.purple,),
],
)
实现TestFlowDelegate
:
class TestFlowDelegate extends FlowDelegate {
EdgeInsets margin = EdgeInsets.zero;
TestFlowDelegate({this.margin});
@override
void paintChildren(FlowPaintingContext context) {
var x = margin.left;
var y = margin.top;
//计算每一个子widget的位置
for (int i = 0; i < context.childCount; i++) {
var w = context.getChildSize(i).width + x + margin.right;
if (w < context.size.width) {
context.paintChild(i,
transform: new Matrix4.translationValues(
x, y, 0.0));
x = w + margin.left;
} else {
x = margin.left;
y += context.getChildSize(i).height + margin.top + margin.bottom;
//绘制子widget(有优化)
context.paintChild(i,
transform: new Matrix4.translationValues(
x, y, 0.0));
x += context.getChildSize(i).width + margin.left + margin.right;
}
}
}
@override
getSize(BoxConstraints constraints){
//指定Flow的大小
return Size(double.infinity,200.0);
}
@override
bool shouldRepaint(FlowDelegate oldDelegate) {
return oldDelegate != this;
}
}
运行效果:
可以看到我们主要的任务就是实现paintChildren
,它的主要任务是确定每个子widget位置。由于Flow
不能自适应子widget的大小,我们通过在getSize
返回一个固定大小来指定Flow的大小。
评论区