背景
嵌套层级深的问题让众多刚接触Flutter的同学感到困扰,它不仅是看起来让人感到不适,还非常影响编码体验。
大佬们会告诉你应该拆分自己的嵌套代码(自定义widget或者抽取build方法)来减少嵌套层级。这确实是个行之有效的方法,除此之外,还有没有别的方法呢,本文将向您介绍另一种减少嵌套层级的方法。
嵌套过深影响代码的视觉观感
这段代码演示了什么叫做:嵌套地狱
class Test extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Demo'),),
body: Container(
child: Offstage(
offstage: false,
child: ListView(
children: <Widget>[
Container(
color: Colors.white,
padding: EdgeInsets.all(20),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Icon(Icons.phone),
Text("amy"),
],
),
),
Container(
color: Colors.white,
padding: EdgeInsets.all(20),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Icon(Icons.phone),
Text("billy"),
],
),
),
],
),
),
),
);
}
}
提取build方法后,嵌套层级得到了明显的改善
class Test extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Demo'),),
body: Container(
child: Offstage(
offstage: false,
child: ListView(
children: <Widget>[
buildItem("amy"),
buildItem("billy"),
],
),
),
),
);
}
Container buildItem(String name) {
return Container(
color: Colors.white,
padding: EdgeInsets.all(20),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Icon(Icons.phone),
Text(name),
],
),
);
}
}
还能不能继续优化呢?
自定义扩展函数
举个例子:想要给下面这段代码中的第2个Textwidget加上marginTop:10属性
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(10),
child: Column(
children: <Widget>[
Text('billy'),
Text('say hello'), //add margin top??
],
),
);
}
此时,我内心希望可以这样写:
显然,flutter不支持这么写,幸运的是:dart2.7发布时正式宣布支持扩展函数(Extension Methods)
实际上从dart 2.6.0就开始支持扩展函数了
如果pubspec.yaml中设置的dart版本低于2.6.0则会出现警告提示
如:
environment:
sdk: ">=2.1.0 <3.0.0"
警告提示:
Extension methods weren’t supported until version 2.6.0
先来定义一个扩展函数
extension WidgetExt on Widget {
Container intoContainer({
//复制Container构造函数的所有参数(除了child字段)
Key key,
AlignmentGeometry alignment,
EdgeInsetsGeometry padding,
Color color,
Decoration decoration,
Decoration foregroundDecoration,
double width,
double height,
BoxConstraints constraints,
EdgeInsetsGeometry margin,
Matrix4 transform,
}) {
//调用Container的构造函数,并将当前widget对象作为child参数
return Container(
key: key,
alignment: alignment,
padding: padding,
color: color,
decoration: decoration,
foregroundDecoration: foregroundDecoration,
width: width,
height: height,
constraints: constraints,
margin: margin,
transform: transform,
child: this,
);
}
}
现在,所有widget对象都多了一个intoContainer(...)扩展函数,而且参数与Container构造方法一致,于是,我们就可以这样写了:
除了Container,其它容器也可以通过同样的方式来扩展。于是,编程体验大大提升。
还可以支持链式调用:
Text("billy")
.intoExpanded(flex: 1)
.intoContainer(color: Colors.white)
有些widget有多个子widget (children), 可以添加如下的扩展函数:
extension WidgetExt on Widget {
//添加一个相邻的widget,返回List<Widget>
List<Widget> addNeighbor(Widget widget) {
return <Widget>[this, widget];
}
//添加各种单child的widget容器
//如:Container、Padding等...
}
extension WidgetListExt<T extends Widget> on List<T> {
//子List<Widget>列表中再添加一个相邻的widget,并返回当前列表
List<Widget> addNeighbor(Widget widget) {
return this..add(widget);
}
Row intoRow({
Key key,
MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start,
MainAxisSize mainAxisSize = MainAxisSize.max,
CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.center,
TextDirection textDirection,
VerticalDirection verticalDirection = VerticalDirection.down,
TextBaseline textBaseline,
}) {
return Row(
key: key,
mainAxisAlignment: mainAxisAlignment,
mainAxisSize: mainAxisSize,
crossAxisAlignment: crossAxisAlignment,
textDirection: textDirection,
verticalDirection: verticalDirection,
textBaseline: textBaseline,
children: this,
);
}
//添加其它多child的widget容器
//如:Column、ListView等...
}
使用扩展函数解决嵌套过深的问题
回到本文最初的嵌套地狱,现在我们的代码可以写成这样
class Test extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Demo'),),
body: buildItem("amy")
.addNeighbor(buildItem("billy"),)
.intoListView()
.intoOffstage(offstage: false)
.intoContainer()
);
}
Container buildItem(String name) {
return Icon(Icons.phone)
.addNeighbor(Text(name))
.intoRow(crossAxisAlignment: CrossAxisAlignment.center,)
.intoContainer(color: Colors.white, padding: EdgeInsets.all(20),);
}
}
为了让我们的代码更加符合链式编程风格,再定义一个静态方法吧
class WidgetChain {
static Widget addNeighbor(Widget widget) {
return widget;
}
}
另外,再定义一个从数据到widget的映射扩展方法
extension ListExt<T> on List<T> {
List<Widget> buildAllAsWidget(Widget Function(T) builder) {
return this.map<Widget>((item) {
return builder(item);
}).toList();
}
}
现在,代码是这样的:
class Test extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Demo'),),
body: ["amy", "billy"]
.buildAllAsWidget((name) =>
WidgetChain
.addNeighbor(Icon(Icons.phone))
.addNeighbor(Text(name))
.intoRow(crossAxisAlignment: CrossAxisAlignment.center,)
.intoContainer(color: Colors.white, padding: EdgeInsets.all(20),)
)
.intoListView()
.intoOffstage(offstage: false)
.intoContainer()
);
}
}
值得指出的是,扩展函数(无嵌套)跟构造函数(有嵌套)是可以混用的。上面的代码也可以写成这样(Container和Offstage这2层改成了构造函数):
class Test extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Demo'),),
body: Container(
child: Offstage(
offstage: false,
child: ["amy", "billy"]
.buildAllAsWidget((name) =>
WidgetChain
.addNeighbor(Icon(Icons.phone))
.addNeighbor(Text(name))
.intoRow(crossAxisAlignment: CrossAxisAlignment.center,)
.intoContainer(color: Colors.white, padding: EdgeInsets.all(20),)
)
.intoListView()
),
),
);
}
}
这样的扩展函数你想不想试试呢?我已经替大家封装好了常用Widget对应的into扩展函数,可以直接食用:
dependencies:
widget_chain: ^0.1.0
导入:
import 'package:widget_chain/widget_chain.dart';
然后就可以起飞了!
Github源码地址: widget_chain 【就算你现在用不上,也可以先star收藏之,今后如果有人向你吐槽flutter嵌套深,除了叫他拆分封装之外,还可以把widget_chain甩给他 😄 】
总结
本文介绍了Flutter中的嵌套地狱,并使用扩展函数的方式来解决flutter的嵌套地狱问题。
由于大篇幅的扩展函数调用会影响代码的阅读体验(因为intoXxx函数的调用顺序与widget层级是相反的),需要保留关键嵌套层级结构以使得布局的层级结构保持清晰,文中的扩展函数支持与构造函数混用,具体使用到什么程度,就看大家自己的选择了
2019-12-30编辑后补充说明
本文初次发布时包含了使用扩展函数提升编码体验的相关内容。因为我之前编写flutter代码时添加父容器的方式为:
- 先剪切要添加父容器的widget代码
- 在剪切处编写父容器及其属性
- 在父容器节点下添加child,粘贴刚才剪切的内容作为child的值
IDE中其实提供了添加父容器的快捷键(光标定位在Widget上,然后Alt + Enter,选择需要添加的父容器即可),非常方便。
作者:齐翊