41、Flutter之widgets 响应式布局LayoutBuilder组件
响应式应用指的是应用的 UI 会随着屏幕或窗口的改变而改变,当同一个应用需要运行在不同种类的设备的时候(比如手表、手机、平板、笔记本或台式机电脑),当用户在笔记本或台式机上调整窗口大小,或者改变了手机或者平板的方向时,你的应用都需要相应的重新调整界面来做出响应。
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<=393.0, 0.0<=h<=Indinity)
可以看到 Text("xx") 的显示空间最大宽度为 393,最大高度为 Indinity 。
项目实用1
如果图片在使用过程中,不要求图片的完整性,可以进行拉伸,我们就会使用其属性fit为BoxFit.fill,使用这个属性后图片就可以进行拉伸处理。不过如果我们需求是宽占满整个屏幕,高度为自定义的高度,则简单的代码就很难实现了,如下:
Center(
child: Container(
child: Column(
children: <Widget>[
Image.network(
"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1563774129262&di=a62f1daccb204945eafcfd5082b4ce98&imgtype=0&src=http%3A%2F%2Fimages.ali213.net%2Fpicfile%2Fpic%2F2012-11-27%2F927_one_piece18.jpg",
fit: BoxFit.fill,
height: 100,
),
Text("图片"),
],
),
),
),
运行效果:
此时需要知道父级的宽度即可,此时便可以用LayoutBuilder进行包含:
LayoutBuilder(
builder: (context, constraints) {
return Center(
child: Container(
child: Column(
children: <Widget>[
Image.network(
"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1563774129262&di=a62f1daccb204945eafcfd5082b4ce98&imgtype=0&src=http%3A%2F%2Fimages.ali213.net%2Fpicfile%2Fpic%2F2012-11-27%2F927_one_piece18.jpg",
fit: BoxFit.fill,
height: 100,
width: constraints.maxWidth,
),
Text("图片"),
],
),
),
);
},
),
builder: (context, constraints){}中,context是父级的上下文,constraints为BoxConstraints类型,maxWidth为父级宽度,maxHeight为父级高度。
项目实用2
LayoutBuilder组件根据不同的屏幕尺寸显示不同的效果,如竖屏和横屏显示的样式不一样。
代码:
class WyLayoutBuilder extends StatelessWidget {
const WyLayoutBuilder({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: getAppBar("横竖屏输配"),
body: LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
Color color = Colors.red;
double width = 100;
if (constraints.maxWidth > 414) {
color = Colors.blue;
width = 300;
}
return Container(
width: width,
height: 50,
color: color,
);
},
)
,
);
}
}
转载自:https://juejin.cn/post/7169396691085295629