Flutter 中有两种布局模型:
- 基于 RenderBox 的盒模型布局。
- 基于 Sliver ( RenderSliver ) 按需加载列表布局。
布局流程基本如下:
- 上层组件向下层组件传递约束(constraints)条件。
- 下层组件确定自己的大小,然后告诉上层组件。
**任何时候子组件都必须先遵守父组件的约束** - 上层组件确定下层组件相对于自身的偏移和确定自身的大小(大多数情况下会根据子组件的大小来确定自身的大小)
约束布局
ContrainedBox
// constraints: const BoxConstraints(minHeight: 100, maxHeight: 200, minWidth: 10, maxWidth: 100),
//严格约束,最大宽高最小宽高相同固定宽高
// constraints: BoxConstraints.tightFor(width: 100, height: 100),
//松散约束,限制最大宽高
// constraints: BoxConstraints.loose(const Size(100, 10)),
//限制最大宽高
//constraints: BoxConstraints.expand(width: 100, height: 100),
//无限制
// constraints: BoxConstraints.expand(width: 200),
ConstrainedBox(
constraints: BoxConstraints.tight(const Size(100, 100)),
child: Container(
color: ColorsRedcolor
width: 5,
height: 10,
),
);
SizeBox
SizedBox用于给子元素指定固定的宽高,传递给子组件的是强约束,其本身也是对 ConstrainedBox 的定制,
SizedBox(
width: 10,
height: 30,
child: Container(
color: Colors.red,
width: 100,
height: 200,
),
)
UnconstrainedBox
由于任何时候子组件都必须遵守其父组件的约束,所以当有多重约束时,最小值取大值,最大值取小值。 当有多重约束时,可能需要用 UnConstrained 去除父类约束。
ConstrainedBox(
constraints: const BoxConstraints(minWidth: 60.0, minHeight: 100.0), //父
//“去除”父级限制
child: UnconstrainedBox(
child: ConstrainedBox(
constraints: BoxConstraints(minWidth: 90.0, minHeight: 20.0), //子
child: Container(color: Colors.red),
),
),
)
线性布局
Row和Column都继承自Flex,并且属性相似。
Row和Column都只会在主轴方向占用尽可能大的空间,而纵轴的长度则取决于他们最大子元素的长度。
但Row里面嵌套Row,或者Column里面再嵌套Column,那么只有最外面的Row或Column会占用尽可能大的空间,里面Row或Column所占用的空间为实际大小。
Row/Column
Row(
//主轴尺寸,max 表最大宽度,min 子组件宽度和
//但当Flex 本身受到强约束时,该属性无效
mainAxisSize: MainAxisSize.max,
//主轴方向子组件对齐方式
mainAxisAlignment: MainAxisAlignment.start,
//纵轴方向子组件对齐方式
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text( " hello world ",
style: TextStyle(fontSize: 30.0, backgroundColor: Colors.red)),
Text(" I am Jack "),
],
)
- 主轴对齐方式:MainAxisAlignment
enum MainAxisAlignment {
start, // 主轴方向起点 (默认)
end, // 主轴方向终点
center, // 主轴方向居中
spaceBetween, //主轴方向,首尾组件分居两侧,其余组件均分空间
spaceAround, //主轴方向,中间组件均分剩余空间,首尾组件只占一半
spaceEvenly, //主轴方向,均分剩余空间
}
- 交叉轴方向:crossAxisAlignment
enum CrossAxisAlignment {
start, // 交叉轴方向起点
end, // 交叉轴方向终点
center, // 交叉轴方向中心 (默认)
stretch, // ☆ 交叉轴方向为子级施加强约束
baseline, // 交叉轴方向按基线对齐
}
弹性布局 EXPanded
弹性布局允许子组件按照一定比例来分配父容器空间,Flutter 中的弹性布局主要通过Flex和Expanded来配合实现。
Flex组件可以沿着水平或垂直方向排列子组件,如果你知道主轴方向,使用Row或Column会方便一些,因为Row和Column都继承自Flex,参数基本相同,所以能使用Flex的地方基本上都可以使用Row或Column。
Expanded 只能作为 Flex 的孩子(否则会报错),它可以按比例“扩伸”Flex子组件所占用的空间。因为 Row和Column 都继承自 Flex,所以 Expanded 也可以作为它们的孩子。
//`flex`参数为弹性系数,如果为 0 或`null`,则`child`是没有弹性的,即不会被扩伸占用的空间。
//如果大于0,所有的`Expanded`按照其 flex 的比例来分割主轴的全部空闲空间
const Expanded({
int flex = 1,
required Widget child,
})
其中Spacer(flex: 2),Expanded的封装类
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(flex: 5,child: _stackView(entity)),
SizedBox(width: 10),
Spacer(flex: 2),
Expanded(flex: 2,child: _imageView(entity.photo as String))
],
)
流式布局 Wrap
流式布局分为Wrap和Flow,但后者过于复杂,使用场景较少,这里只展示前者。
Wrap(
direction:Axis.horizontal,//主轴方向 默认横向
alignment: WrapAlignment.start, //主轴对方式
crossAxisAlignment:WrapCrossAlignment.start,//辅轴对齐方式
spacing: 8, //主轴方向间距
runSpacing: 2, //纵轴方向间距
children: const [
Chip(label: Text("A"),
avatar: CircleAvatar(backgroundColor: Colors.blue,
child: Text("Aaa")),
),
Chip(label: Text("A"),
avatar: CircleAvatar(backgroundColor: Colors.blue,
child: Text("Aaa")),
),
Chip(label: Text("A"),
avatar: CircleAvatar(backgroundColor: Colors.blue,
child: Text("Aaa")),
),
Chip(label: Text("A"),
avatar: CircleAvatar(backgroundColor: Colors.blue,
child: Text("Aaa")),
),
],
)
- 间距
spacing和runspacing
- 主轴方向的对齐模式
alignment
enum WrapAlignment {
start, // 主轴方向起点 (默认)
end, // 主轴方向终点
center, // 主轴方向居中
spaceBetween, //主轴方向,首尾组件分居两侧,其余组件均分空间
spaceAround, //主轴方向,中间组件均分剩余空间,首尾组件只占一半
spaceEvenly, //主轴方向,均分剩余空间
}
- 交叉轴方向:crossAxisAlignment 和 runAlignment
enum WrapCrossAlignment {
start, //交叉轴方向起点 (默认)
end, //交叉轴方向终点
center, //交叉轴方向中心
}
- 在
Wrap区域中交叉轴方向的对齐方式,类型也是WrapAlignment枚举.
层叠布局 Stack、Positioned
相当于iOS/Android 中的Frame 布局通过确定位置来布局。
Position 只能用于 Stack 中,并且被 Positioned 嵌套的组件,可以无视 Stack 组件的约束。另外Positioned 还可以为子组件施加宽高进行强约束,其自身约束失效。
Stack(
alignment: Alignment.topLeft, //指定未定位或部分定位widget的对齐方式
fit: StackFit.loose, //没有定位的子组件如何去适应Stack的大小
children: [
Positioned(
top: 0,
left: 0,
right: 0,
child: Text(entity.title ?? " ",
style: boldStyle,
maxLines: 2,
overflow: TextOverflow.ellipsis),
),
Positioned(
bottom: 0,
left: 0,
child: Text(entity.createTime(), style: normalStyle),
),
Positioned(
right: 20,
bottom: 0,
child: Text(entity.clicked ?? "0", style: normalStyle),
),
Positioned(
right: 0,
bottom: 0,
child: Image.asset("assets/images/news/news_list_clicked@2x.png",
width: 14,
height: 14,
),
),
],
)
Alignment本身是一个类,可以进行构造 左上角是(-1.0, -1.0),右下角是(1.0, 1.0)的坐标系,通过坐标来决定对齐的位置。
static const Alignment center = Alignment(0.0, 0.0);
static const Alignment topLeft = Alignment(-1.0, -1.0);
static const Alignment bottomRight = Alignment(1.0, 1.0);
fit属性可以对子级的约束情况进行设置
enum StackFit {
loose,
expand,
passthrough,
}
[1]: loose 下, 宽高尽可能 [放松约束]。
[2]: expand 下, 施加 [强约束],约束尺寸为自身受到约束的 [最大尺寸]。
[3]: passthrough 下, 仅 [传递约束],把自身受到的约束原封不动的施加给子级。
相对定位 Align / Center
Center继承自Align,它比Align只少了一个alignment 参数;由于Align的构造函数中alignment 值为Alignment.center,所以我们可以认为Center组件其实是对齐方式确定(Alignment.center)的Align。
//`FlutterLogo`的宽高为 60,则`Align`的最终宽高都为`2*60=120`
Align(
widthFactor: 2,
heightFactor: 2,
alignment: Alignment.topRight,
child: FlutterLogo(
size: 60,
),
),
widthFactor或heightFactor为null时组件的宽高将会占用尽可能多的空间
DecoratedBox(
decoration: BoxDecoration(color: Colors.red),
child: Center(
child: Text("xxx"),
),
),
DecoratedBox(
decoration: BoxDecoration(color: Colors.red),
child: Center(
widthFactor: 1,
heightFactor: 1,
child: Text("xxx"),
),
)
容器类组件
Padding
Padding(
//上下左右各添加16像素补白
padding: const EdgeInsets.all(16),
child: Column(
//显式指定对齐方式为左对齐,排除对齐干扰
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: const <Widget>[
Padding(
//左边添加8像素补白
padding: EdgeInsets.only(left: 8),
child: Text("Hello world"),
),
Padding(
//上下各添加8像素补白
padding: EdgeInsets.symmetric(vertical: 8),
child: Text("I am Jack"),
),
Padding(
// 分别指定四个方向的补白
padding: EdgeInsets.fromLTRB(20, 0, 20, 20),
child: Text("Your friend"),
)
],
),
)
剪裁组件
| 剪裁Widget | 默认行为 |
|---|---|
| ClipOval | 子组件为正方形时剪裁成内贴圆形;为矩形时,剪裁成内贴椭圆 |
| ClipRRect | 将子组件剪裁为圆角矩形 |
| ClipRect | 默认剪裁掉子组件布局空间之外的绘制内容(溢出部分剪裁) |
| ClipPath | 按照自定义的路径剪裁 |
Widget avatar = Image.asset("imgs/avatar.png", width: 60.0);
Center(
child: Column(
children: <Widget>[
avatar, //不剪裁
ClipOval(child: avatar), //剪裁为圆形
ClipRRect( //剪裁为圆角矩形
borderRadius: BorderRadius.circular(5.0),
child: avatar,
),
],
),
)
DecoratedBox
DecoratedBox可以在其子组件绘制前(或后)绘制一些装饰(Decoration),如背景、边框、渐变等
const DecoratedBox({
//抽象类
Decoration decoration,
//foreground background
DecorationPosition position = DecorationPosition.background,
Widget? child
})
BoxDecoration类,它是一个Decoration的子类,实现了常用的装饰元素的绘制。
BoxDecoration({
Color color, //颜色
DecorationImage image,//图片
BoxBorder border, //边框
BorderRadiusGeometry borderRadius, //圆角
List<BoxShadow> boxShadow, //阴影,可以指定多个
Gradient gradient, //渐变
BlendMode backgroundBlendMode, //背景混合模式
BoxShape shape = BoxShape.rectangle, //形状
})
示例渐变按钮
DecoratedBox(
decoration: BoxDecoration(
gradient: LinearGradient(colors:[Colors.red,Colors.orange.shade700]), //背景渐变
borderRadius: BorderRadius.circular(3.0), //3像素圆角
boxShadow: [ //阴影
BoxShadow(
color:Colors.black54,
offset: Offset(2.0,2.0),
blurRadius: 4.0
)
]
),
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 80.0, vertical: 18.0),
child: Text("Login", style: TextStyle(color: Colors.white),),
)
)
Container
Container是一个组合类容器,揉合了其它几种组件的特性,可以实现同时需要装饰、变换、限制的场景。
Container({
this.alignment,
this.padding, //容器内补白,属于decoration的装饰范围
Color color, // 背景色
Decoration decoration, // 背景装饰
Decoration foregroundDecoration, //前景装饰
double width,//容器的宽度
double height, //容器的高度
BoxConstraints constraints, //容器大小的限制条件
this.margin,//容器外补白,不属于decoration的装饰范围
this.transform, //变换
this.child,
...
})
其中属性margin的留白是在容器外部,而padding的留白是在容器内部
Container(
margin: EdgeInsets.all(20.0), //容器外补白
color: Colors.orange,
child: Text("Hello world!"),
),
Container(
padding: EdgeInsets.all(20.0), //容器内补白
color: Colors.orange,
child: Text("Hello world!"),
),