flutter 布局
本文最后更新于:2022年4月22日 上午
flutter 布局
布局
flutter 根部的容器大小为手机屏幕的大小,最外层无论怎么设置大小都无效,
runAPP(
Container(
width: 0.04
height: 0.04,
child: Center(
child: Container(
width: 200,
height: 200,
child: FlutterLogo(size: 9000)
)
),
);
)
如上
根root
会先向下传递约束,Container
的约束为手机屏幕大小,假如为 414 * 896
,那么宽高设置为 0.04
就是无效的,会被flutter
忽视
接着Container
会向下的 Center
传递约束,Center
的大小和Container
的大小是一致的
然后 Center
再向下的 Container
传递约束,这时候就有些不太一样了,向下的约束为 0 ≤ 宽 ≤ 414,0 ≤ 高 ≤ 896
,表示只需要子容器可以在这个范围大小内
接着 Container
接收到 Center
的约束后,告诉父级它的大小为200*200
,则表示Center
最大值和最小值都为200*200
最后 Container
向下FlutterLogo
传递约束,要求大小和Container
是一致的200 * 200
,所以FlutterLogo
将大小设置为了9000
是无效的,此时父级是不会接受这个大小的,会将FlutterLogo
的大小设置为 200 * 200
Align
可以控制子元素的位置,值为 负1 - 正1
,默认值为 0,0
表示子元素居中
Align(
alignment: Alignment(-1, 0);
)
查看布局约束 LayoutBuilder
LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints){
print(constraints); // 布局约束
return FlutterLogo(size: 80);
}
)
占满父级约束 SizeBox.expand
SizeBox.expand()
父级百分比约束 FractionallySizeBox
FractionallySizeBox(
widthFactor: .5,
heightFactor: .5,
child: Container()
)
设置约束ConstrainedBox
ConstrainedBox
可以重新设置子元素的最大和最小约束,但是这个范围也是在ConstrainedBox
父级约束的区间内,不允许超过父级对ConstrainedBox
的约束
return ConstrainedBox(
constraints: BoxConstraints(
minHeight: 60,
minWidth: 60,
maxHeight: double.infinity,
maxWidth: double.infinity,
),
child: FlutterLogo(size: 80),
);
松约束
.loosen()
会将最小值60
变成松约束,将其变成0
,Center
和Align
其实也是松约束,Container
则是紧约束
return ConstrainedBox(
constraints: BoxConstraints(
minHeight: 60,
minWidth: 60,
maxHeight: double.infinity,
maxWidth: double.infinity,
).loosen(),
child: FlutterLogo(size: 80),
);
紧约束
简单设置可以通过 SizebBox()
直接设置宽高
BoxConstraints.tightFor()
会创建一个最大值和最小值都相等的紧约束
return ConstrainedBox(
constraints: BoxConstraints.tightFor(width: 120, height: 120),
child: FlutterLogo(size: 80),
);
什么是松约束和紧约束
松约束是最小值为0
的情况
紧约束是最小值和最大值都相等的情况
如果最大值和最小值都为0
,则这个约束既是松约束,又是紧约束
阅读方向 Directionality
如果没有设置过的话,默认会从设备中读取阅读方向,一般是从左往右
Directionality(
textDirection: TextDirection.rtl
)
层叠布局 Stack
Stack
的子元素分为两类,一类是有 Positioned
的,一类是没有的
如果Stack
的子元素全部都是 Positioned
,有定位的,那么 Stack
的大小就是越大越好,会占满父元素的大小
如果Stack
中全是没有Positioned
,或者没有定位的和有定位的都有,那么Stack
的大小就是 没有设置定位的最大元素的大小
Stack 约束
StackFit.passthrough
直接将上级的约束传递给所有子元素
StackFit.loose
会变成松约束,例如上级 minWidth: 20
会将其最小约束变成0
,使子元素最小宽度可以设置小于20
的值
StackFit.expanded
设置为父级的最大约束,例如上级maxWidth: 300
,那么会 将宽度300
作为紧约束传递给子元素,让子元素必须为300
Stack 裁剪
如果有子元素超出了Stack
的尺寸,那么默认会被裁切掉,如果不想裁切掉,可以设置 clipBehavior: Clip.none
,也可以通过设置overflow
来实现,只不过这个属性是旧版的属性
如果子元素超出了Stack
,那么点击事件是没办法响应的
return ConstrainedBox(
constraints: BoxConstraints(
minHeight: 20,
minWidth: 20,
maxHeight: 300,
maxWidth: 300,
),
child: Stack(
fit: StackFit.expanded, // 占满父级的约束 300
children: [
Container(width: 0, height: 80, color: Colors.blue),
]
),
);
Placeholder 占位元素
Placeholder(
fallbackHeight: 200, // 占位高度
)
LimitedBox
当父级是宽高无限大的时候,这个元素才有效
CustomMultichildLayout 自定义布局
- LayoutId() 给每个子元素设置id
- layoutChild() 找到指定的子元素,并设置其约束
- positionChild() 将指定的子元素设置位置
import 'package:flutter/material.dart';
class CustomLayoutTest extends StatelessWidget {
const CustomLayoutTest({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('自定义布局')),
body: Padding(
padding: const EdgeInsets.all(8.0),
child: CustomMultiChildLayout(
delegate: MyDalegate(),
children: [
LayoutId(id: '1', child: Container(color: Colors.blue[500], child: Text('1'))),
LayoutId(id: '2', child: Container(color: Colors.blue[300], child: Text('2'))),
LayoutId(id: '3', child: Container(color: Colors.blue[300], child: Text('3'))),
LayoutId(id: '4', child: Container(color: Colors.blue[300], child: Text('4'))),
LayoutId(id: '5', child: Container(color: Colors.blue[300], child: Text('5'))),
LayoutId(id: '6', child: Container(color: Colors.blue[300], child: Text('6'))),
],
),
),
);
}
}
class MyDalegate extends MultiChildLayoutDelegate {
// 布局
@override
void performLayout(Size size) {
// 这个 size 是父元素的尺寸
Size? size1, size2, size3, size4, size5;
if (hasChild('1')) {
size1 = layoutChild('1', BoxConstraints.tight(Size(70, 70)));
positionChild('1', Offset(0, 0));
}
if (hasChild('2') && hasChild('3')) {
size2 = layoutChild('2', BoxConstraints.tight(Size(30, 30)));
layoutChild('3', BoxConstraints.tight(Size(30, 30)));
positionChild('2', Offset(size1!.width + 10, 0));
positionChild('3', Offset(size1.width + 10, size2.height + 10));
}
size4 = layoutChild('4', BoxConstraints.tight(Size(30, 30)));
size5 = layoutChild('5', BoxConstraints.tight(Size(30, 30)));
layoutChild('6', BoxConstraints.tight(Size(30, 30)));
positionChild('4', Offset(0, size1!.height + 10));
positionChild('5', Offset(size4.width + 10, size1.height + 10));
positionChild('6', Offset(size4.width + size5.width + 20, size1.height + 10));
}
@override
bool shouldRelayout(_) => true;
}
局限性
- 如果想让
CustomMultichildLayout
的大小紧紧包裹住子元素大小,是比较不好做的,需要手写一个RenderObject
- 没有办法无中生有,例如想要加一个下划线,则必须需要传入一个下划线
总结
向下传递约束,向上传递尺寸,最终flutter
知道每一个元素的尺寸后,再决定如何进行布局
flutter
布局的时候一定要满足上级的约束,只有满足上级约束,尺寸才会生效,否则会被自耦东修正。
可以理解为给子元素设置大小只是一个建议大小,只有满足上级约束,这个建议才会被采纳
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议,转载请注明出处。