LayoutBuilder
通过 LayoutBuilder,我们可以在布局过程中拿到父组件传递的约束信息,然后我们可以根据约束信息动态的构建不同的布局。
比如我们实现一个响应式的 Column 组件 ResponsiveColumn,它的功能是当当前可用的宽度小于 200 时,将子组件显示为一列,否则显示为两列。简单来实现一下:
class ResponsiveColumn extends StatelessWidget {
const ResponsiveColumn({Key? key, required this.children}) : super(key: key);
final List<Widget> children;
@override
Widget build(BuildContext context) {
// 通过 LayoutBuilder 拿到父组件传递的约束,然后判断 maxWidth 是否小于200
return LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
if (constraints.maxWidth < 200) {
// 最大宽度小于200,显示单列
return Column(children: children, mainAxisSize: MainAxisSize.min);
} else {
// 大于200,显示双列
var _children = <Widget>[];
for (var i = 0; i < children.length; i += 2) {
if (i + 1 < children.length) {
_children.add(Row(
children: [children[i], children[i + 1]],
mainAxisSize: MainAxisSize.min,
));
} else {
_children.add(children[i]);
}
}
return Column(children: _children, mainAxisSize: MainAxisSize.min);
}
},
);
}
}
class LayoutBuilderRoute extends StatelessWidget {
const LayoutBuilderRoute({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
var _children = List.filled(6, Text("A"));
// Column在本示例中在水平方向的最大宽度为屏幕的宽度
return Column(
children: [
// 限制宽度为190,小于 200
SizedBox(width: 190, child: ResponsiveColumn(children: _children)),
ResponsiveColumn(children: _children),
LayoutLogPrint(child:Text("xx")) // 下面介绍
],
);
}
}
可以发现 LayoutBuilder 的使用很简单,但是不要小看它,因为它非常实用且重要,它主要有两个使用场景:
- 可以使用 LayoutBuilder 来根据设备的尺寸来实现响应式布局。
- LayoutBuilder 可以帮我们高效排查问题。比如我们在遇到布局问题或者想调试组件树中某一个节点布局的约束时 LayoutBuilder 就很有用。
打印布局时的约束信息
为了便于排错,我们封装一个能打印父组件传递给子组件约束的组件:
class LayoutLogPrint <T>extends StatelessWidget {
final Widget child;
final T? tag;
const LayoutLogPrint({Key? key, required this.child, this.tag}) : super(key: key);
@override
Widget build(BuildContext context) {
return LayoutBuilder(builder: (_,constraints){
// assert在编译release版本时会被去除
assert(() {
print('${tag ?? key ?? child}: $constraints');
return true;
}());
return child;
});
}
}
这样,我们就可以使用 LayoutLogPrint 组件树中任意位置的约束信息,比如:
LayoutLogPrint(child:Text("xx"))
控制台输出:
flutter: Text("xx"): BoxConstraints(0.0<=w<=428.0, 0.0<=h<=823.0)
可以看到 Text("xx") 的显示空间最大宽度为 428,最大高度为 823 。
获取父组件大小并布局容器LayoutBuilder
LayoutBuilder(
builder: (context,constraints){
context为父级上下文
constraints.biggest.height; 获取组件在父组件所能设置的最大高度
contraints.maxWidth; 获取父组件宽度,高度同理
return 组件
}
)
代码示例:
LayoutBuilder(
builder: (context,constraints){
final endHeight=constraints.biggest.height;
return GestureDetector(
onVerticalDragDown: (text){ //当点击时会获取点击坐标
print(endHeight);
print(constraints.maxHeight);
print(constraints.maxWidth);
},
onVerticalDragEnd: (text){
print(text);
},
onVerticalDragCancel: (){
print("取消");
},
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Icon(Icons.search,size: 15,),
GestureDetector(
child:Text('A') ,
onTap: (){
scroll.animateTo(312, duration: Duration(milliseconds: 200), curve: Curves.easeIn);
},
),
GestureDetector(
child:Text('B') ,
onTap: (){
scroll.animateTo(478, duration: Duration(milliseconds: 200), curve: Curves.easeIn);
},
),
GestureDetector(
child:Text('C') ,
onTap: (){
scroll.animateTo(575, duration: Duration(milliseconds: 200), curve: Curves.easeIn);
},
),
GestureDetector(
child:Text('D') ,
onTap: (){
scroll.animateTo(741, duration: Duration(milliseconds: 200), curve: Curves.easeIn);
},
),
Text('E'),
Text('F'),
Text('G'),
Text('H'),
Text('I'),
Text('J'),
Text('K'),
Text('L'),
Text('M'),
Text('N'),
Text('O'),
Text('P'),
Text('Q'),
Text('R'),
Text('S'),
Text('T'),
Text('U'),
Text('V'),
Text('W'),
Text('X'),
Text("Y"),
Text("#")
],
),
);
},
)
使用LayoutBuilder 用来获取父布局的尺寸大小
本文章的效果如下:
1 核心代码是通过 LayoutBuilder来获取父窗口尺寸
LayoutBuilder(builder: (BuildContext context, BoxConstraints constraints) {
double maxWidth = constraints.maxWidth;
double minWidth = constraints.minWidth;
double maxHeight = constraints.maxHeight;
double minHeight = constraints.minHeight;
if(maxWidth>400) {
return buildRow();
}
return buildColumn();
},)
2 全部代码实现
class LayoutBuilderPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
double maxWidth = constraints.maxWidth;
double minWidth = constraints.minWidth;
double maxHeight = constraints.maxHeight;
double minHeight = constraints.minHeight;
if (maxWidth > 400) {
return buildRow();
}
return buildColumn();
},
),
),
);
}
Widget buildRow() {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(onPressed: () {}, child: Text("右侧按钮")),
SizedBox(
width: 60,
),
ElevatedButton(onPressed: () {}, child: Text("左侧按钮")),
],
);
}
Widget buildColumn() {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(onPressed: () {}, child: Text("右侧按钮")),
SizedBox(
height: 60,
),
ElevatedButton(onPressed: () {}, child: Text("左侧按钮")),
],
);
}
}
使用 LayoutBuilder 频繁Build
return LayoutBuilder(builder: (ctx, constraints) {
/// 这里返回出去的Widget,如果超出显示大小,一旦刷新时,这里就会频繁build。
/// 解决方案:
/// 第一次Build的时候,把constraints,保存为成员变量。
/// 第二次不再通过LayoutBuilder获取constraints。
});
解决方案:
return LayoutBuilder(builder: (ctx, constraints) {
this.constraints = constraints;
return Container(color:Colors.black);
});