前言
原则
不要在 Flex widget 里放置 unbounded constraints
Column 是 Flex widget,所以在里面放 ListView 的话,系统不会答应的。
因为 Column 作为 Flex 它不知道应该如何安放一个 as big as possible 的 widget。
解决方法也很简单,只要设置 ListView 的 shrinkWrap=true 即可。这就是告诉 ListView 把自己尽可能地缩小。
可以在 Column 或 Row 里使用 Expanded,因为它是 Flexible,就应该待在 Flex 里面。
Flex/Row/Column和Expanded(弹性布局)
Flex 允许你根据子节点的放置(水平或垂直)来控制轴。这被称作主轴。
你如果事先直到具体的主轴,那么考虑使用Row
或者Column
,因为这不这么冗余。
它们都是 Flex widgets,Row 可以将 children 横着放,column 可以将 children 竖着放。
- crossAxisAlignment 表示要如何对齐另一侧,比如横着一排的 widgets,垂直方向上它们应该顶部对齐还是居中对齐呢。
- mainAxisSize 默认是 MainAxisSize.max,如果想让它变成 Row 或 Column 的真实高度,可以将它设置为 MainAxisSize.min。
Expanded
Expanded
可以展开Row
,Column
,或者Flex
的子结点。
Expanded
是继承 Flexible
。使用Flexible
小部件为Row
、Column
或Flex
的子部件提供了扩展以填充主轴中可用空间的灵活性(例如,水平地填充Row
或垂直地填充Column
),但与Expanded
不同,Flexible
不要求子部件填充可用空间。
使用Expanded
可以让Row
,Column
或Flex
的子节点展开来填充其主轴上的剩余可用空间(例如 Row
的水平空间,Column
的垂直空间)。
如果多个子结点都被展开,则根据flex
因子来分配的具体空间。
Expanded
控件必须是Row
,Column
或Flex
的后代,从控件到其封闭的Row
,Column
或Flex
的路径必须只包含StatelessWidget
s 或StatefulWidget
这些,不能是其他类型的Widget(例如RenderObjectWidget
)。
Wrap/Flow(流式布局)
Wrap
Wrap的定义:
代码语言:javascript复制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
(包括Flex
和Column
)中也有,如direction
、crossAxisAlignment
、textDirection
、verticalDirection
等,这些参数意义是相同的,我们不再重复介绍,读者可以查阅前面介绍Row
的部分。
读者可以认为Wrap
和Flex
(包括Row
和Column
)除了超出显示范围后Wrap
会折行外,其它行为基本相同。下面我们看一下Wrap
特有的几个属性:
spacing
:主轴方向子widget的间距runSpacing
:纵轴方向的间距runAlignment
:纵轴方向的对齐方式
Flow
我们一般很少会使用Flow
,因为其过于复杂,需要自己实现子widget的位置转换,在很多场景下首先要考虑的是Wrap
是否满足需求。
Flow
主要用于一些需要自定义布局策略或性能要求较高(如动画中)的场景。
Flow
有如下优点:
- 性能好;
Flow
是一个对子组件尺寸以及位置调整非常高效的控件,Flow
用转换矩阵在对子组件进行位置调整的时候进行了优化:在Flow
定位过后,如果子组件的尺寸或者位置发生了变化,在FlowDelegate
中的paintChildren()
方法中调用context.paintChild
进行重绘,而context.paintChild
在重绘时使用了转换矩阵,并没有实际调整组件位置。 - 灵活;由于我们需要自己实现
FlowDelegate
的paintChildren()
方法,所以我们需要自己计算每一个组件的位置,因此,可以自定义布局策略。
缺点:
- 使用复杂。
- 不能自适应子组件大小,必须通过指定父容器大小或实现
TestFlowDelegate
的getSize
返回固定大小。
示例:
我们对六个色块进行自定义流式布局:
代码语言:javascript复制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:
代码语言:javascript复制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;
}
}
运行效果见图4-8:
可以看到我们主要的任务就是实现paintChildren
,它的主要任务是确定每个子widget位置。
由于Flow不能自适应子widget的大小,我们通过在getSize
返回一个固定大小来指定Flow的大小。
Stack和Positioned(层叠布局)
Stack 有点像 css 的绝对布局,可以在上面盖一些 widgets,比如 profile 页的背景图上放一些个人信息。
Stack 的 children 如果没有用 Positioned 修饰的话,就会用 Stack 的 fit 和 alighment 来帮它们找到合适的位置。
代码语言:javascript复制Stack(
fit: StackFit.loose,
alignment: Alignment.center,
children: [
Text('world'),
Positioned(
bottom: 10,
child: Text('hello'),
)
],
),
fit
的默认值StackFit.loose
的意思是,如果 child size 不比 Stack 的大,就用 child 的 size。
而如果设置为 StackFit.expand
则会让所有非 Positioned
的 widgets 使用 Stack 的 size。
Aligin(对齐与相对定位)
Align
组件可以调整子组件的位置,并且可以根据子组件的宽高来确定自身的的宽高,定义如下:
Align({
Key key,
this.alignment = Alignment.center,
this.widthFactor,
this.heightFactor,
Widget child,
})
alignment
: 需要一个AlignmentGeometry
类型的值,表示子组件在父组件中的起始位置。AlignmentGeometry
是一个抽象类,它有两个常用的子类:Alignment
和FractionalOffset
widthFactor
和heightFactor
是用于确定Align
组件本身宽高的属性;它们是两个缩放因子,会分别乘以子元素的宽、高,最终的结果就是Align
组件的宽高。如果值为null
,则组件的宽高将会占用尽可能多的空间。
示例
我们先来看一个简单的例子:
代码语言:javascript复制Container(
height: 120.0,
width: 120.0,
color: Colors.blue[50],
child: Align(
alignment: Alignment.topRight,
child: FlutterLogo(
size: 60,
),
),
)