前言
学习Flutter大概有一年了,没啥太大的成就,一开始是通过玩安卓开发的API配合着别人写好的Demo,自己写了一个玩安卓的App,说写,不如说借鉴。 而后就是年末乘着有时间,自己把公司项目的页面通过Flutter写了一大部分。
其实对于写Flutter,如果注意技术博客的话,大佬们经常会谈这样一个问题:不要有事没事通过setState刷新界面,因为成本高,消耗性能,如果只是页面的某一个控件需要重新绘制,那么就应该精细化控制,进行页面重绘。
然而,我想一直都不得其解,我行我素的使用setState进行页面刷新,说真的,我觉得也挺好的呀。
其实根本原因是我不会Provider,看了半天也没入门。
直到我学习了ValueListenableBuilder。
嗯,上周五写的时候,没注意阅读体验,尽是代码块,我重新编辑了一下,尽量用最少的代码说明业务。
ValueListenableBuilder
纸上得来终觉浅,我们还是从一个简单的例子入手:
一个页面,有一个输入框,和一个按钮,当输入框输入的字符的长度等于6时,按钮改变颜色,并且点击才有响应。 备注一下: 为了避免无用代码过多,我会把公共部分代码放在文章最后,这样避免过多的非主题代码影响阅读。 关键代码的注释我写在代码块中了。
我们先来一个setState版本:
class ExampleWithSetState extends StatefulWidget {
@override
_ExampleWithSetStateState createState() => _ExampleWithSetStateState();
}
class _ExampleWithSetStateState extends State<ExampleWithSetState> {
var _isOK = false;
@override
Widget build(BuildContext context) {
return GestureDetector(
child: Scaffold(
appBar: AppBar(
title: Text("一个输入绑定一个按钮"),
),
body: Container(
margin: const EdgeInsets.fromLTRB(16, 40, 16, 0),
child: ListView(
children: [
_textField(
title: "输入字符串",
limit: 6,
onChanged: (inputString) {
/// 通过setState进行整体页面刷新
setState(() {
_isOK = inputString.length == 6;
});
}),
Container(
margin: const EdgeInsets.fromLTRB(0, 60, 0, 0),
child: RaisedButton(
color: _buttonColor(_isOK),
shape: BeveledRectangleBorder(
borderRadius: BorderRadius.circular(0),
),
child: Container(
child: Center(
child: Text(
"确认",
style: TextStyle(
fontSize: 16,
color: _isOK ? Colors.white : Color(0xFFA3A4A4),
),
),
),
height: 48,
),
onPressed: () {
if (_isOK) {
print("按钮可以用了");
}
},
),
),
],
),
),
),
onTap: () => print("键盘收起方法"),
);
}
}
然后是ValueListenableBuilder版本:
class ExampleWithValueListenableBuilder extends StatelessWidget {
final _isOKNotifier = ValueNotifier(false);
ExampleWithValueListenableBuilder({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return GestureDetector(
child: Scaffold(
appBar: AppBar(
title: Text("ValueListenableBuilder一个输入绑定一个按钮"),
),
body: Container(
margin: const EdgeInsets.fromLTRB(16, 40, 16, 0),
child: ListView(
children: [
_textField(
title: "输入字符串",
limit: 6,
onChanged: (inputString) {
/// 通过对_isOKNotifier进行赋值局部更新ValueListenableBuilder里面的Widget
_isOKNotifier.value = inputString.length == 6;
}),
ValueListenableBuilder(
valueListenable: _isOKNotifier,
builder: (context, isOK, child) {
return Container(
margin: const EdgeInsets.fromLTRB(0, 60, 0, 0),
child: RaisedButton(
color: _buttonColor(isOK),
shape: BeveledRectangleBorder(
borderRadius: BorderRadius.circular(0),
),
child: Container(
child: Center(
child: Text(
"确认",
style: TextStyle(
fontSize: 16,
color: isOK ? Colors.white : Color(0xFFA3A4A4),
),
),
),
height: 48,
),
onPressed: () {
if (isOK) {
print("按钮可以用了");
}
},
),
);
},
),
],
),
),
),
onTap: () => print("键盘收起方法"),
);
}
}
我强烈建议不是很了解ValueListenableBuilder使用的大佬们CV运行跑一把,你会发现他们两实现的功能是一样 而两个例子最大的区别在哪里,ExampleWithSetState继承是StatefulWidget,而ExampleWithValueListenableBuilder继承的是StatelessWidget。 ExampleWithValueListenableBuilder的优势有下面两点:
- 不可变比可变要可靠的多(这是Swift那一套)。
- 通过仅对button做变化的精细化控制,我们让性能上去了。
虽然看起来这点性能可有可无,不过在思路上的扩展才是最重要的,不是所有的数据更新都必须用setState来自己主动控制。
有关ValueListenableBuilder的使用,我个人建议看看大佬的文章与使用,肯定比我的一言半语强很多:Flutter 组件 | ValueListenableBuilder 局部刷新小能手。
这里例子里面,我们仅仅是一个输入框与一个按钮进行绑定。
不过日常开发中,多个输入与一个按钮绑定的业务场景比比皆是,甚至更复杂的业务场景,如果我们一味使用setState,一来会很复杂,二来消耗性能,而使用ValueListenableBuilder,貌似只能一个变量绑定一个Widget。
不过其实ValueListenableBuilder是可以进行嵌套的,下面的例子就是如此:
class ExampleWithTwoValueListenableBuilder extends StatelessWidget {
final _isOK1Notifier = ValueNotifier(false);
final _isOK2Notifier = ValueNotifier(false);
ExampleWithTwoValueListenableBuilder({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return GestureDetector(
child: Scaffold(
appBar: AppBar(
title: Text("两个输入绑定一个按钮"),
),
body: Container(
margin: const EdgeInsets.fromLTRB(16, 40, 16, 0),
child: ListView(
children: [
_textField(
title: "输入11位字符串",
limit: 11,
onChanged: (inputString) {
_isOK1Notifier.value = inputString.length == 11;
}),
_textField(
title: "输入6位字符串",
limit: 6,
onChanged: (inputString) {
_isOK2Notifier.value = inputString.length == 6;
}),
ValueListenableBuilder(
valueListenable: _isOK1Notifier,
builder: (context, isOK1, child) {
return ValueListenableBuilder(
valueListenable: _isOK2Notifier,
builder: (context, isOK2, child) {
final isOK = isOK1 && isOK2;
return Container(
margin: const EdgeInsets.fromLTRB(0, 60, 0, 0),
child: RaisedButton(
color: _buttonColor(isOK),
shape: BeveledRectangleBorder(
borderRadius: BorderRadius.circular(0),
),
child: Container(
child: Center(
child: Text(
"确认",
style: TextStyle(
fontSize: 16,
color:
isOK ? Colors.white : Color(0xFFA3A4A4),
),
),
),
height: 48,
),
onPressed: () {
if (isOK) {
print("按钮可以用了");
}
},
),
);
},
);
},
),
],
),
),
),
onTap: () => print("键盘收起方法"),
);
}
}
嵌套代码关键在于下面这段:
/// 在ValueListenableBuilder中又嵌套了一个ValueListenableBuilder
/// 保证2个需要监听的变量在最里层的builder中可以合成需要两个变量绑定的Widget
ValueListenableBuilder(
valueListenable: _isOK1Notifier,
builder: (context, isOK1, child) {
return ValueListenableBuilder(
valueListenable: _isOK2Notifier,
builder: (context, isOK2, child) {
final isOK = isOK1 && isOK2;
return Container(
margin: const EdgeInsets.fromLTRB(0, 60, 0, 0),
child: RaisedButton(
color: _buttonColor(isOK),
shape: BeveledRectangleBorder(
borderRadius: BorderRadius.circular(0),
),
child: Container(
child: Center(
child: Text(
"确认",
style: TextStyle(
fontSize: 16,
color:
isOK ? Colors.white : Color(0xFFA3A4A4),
),
),
),
height: 48,
),
onPressed: () {
if (isOK) {
print("按钮可以用了");
}
},
),
);
},
);
},
),
你可以试想一下,如果有三个或者四个输入需要和一个按钮进行绑定,那么你可能就会陷入无限嵌套的地狱之中。这个时候Provider就该出场了。
到这里,我们可以先总结一下ValueListenableBuilder的使用场景:
- 通过定义ValueNotifier与ValueListenableBuilder传参关联,可以进行数据与组件的绑定。
- 绑定关系在使用的时候可以做精细化控制,可以仅在需要的地方进行重绘,进而到达性能上的提升。
- 一般情况下,ValueListenableBuilder一对一的绑定关系是比较好控制与书写代码的,如果涉及到多对一的绑定,ValueListenableBuilder会陷入嵌套地狱。
抽出来的非业务代码
将下面这些代码CV到每个例子的类中就可以了。
Widget _textField({
String title,
int limit,
ValueChanged<String> onChanged,
}) {
return Column(
children: [
TextField(
inputFormatters: [LengthLimitingTextInputFormatter(limit)],
decoration: InputDecoration(
hintText: title,
hintStyle: TextStyle(
color: Color(0xFFA3A4A4),
fontSize: 14,
),
enabledBorder: UnderlineInputBorder(
// 不是焦点的时候颜色
borderSide: BorderSide(color: Color(0xFF303131)),
),
focusedBorder: UnderlineInputBorder(
// 焦点集中的时候颜色
borderSide: BorderSide(color: Color(0xFFC3B5AB)),
),
),
keyboardType: TextInputType.number,
onChanged: onChanged,
),
_spacer23(),
],
);
}
Widget _spacer23() {
return SizedBox(
height: 23,
);
}
Color _buttonColor(bool value) {
if (value) {
return Color(0xFFC3B5AB);
} else {
return Color(0xFF303131);
}
}
下节我们先从MultiProvider开始说起,至于什么全局的状态管理,我看我够不够勤快吧。。。今天先撤退。
另外,ValueNotifier总是让我联想到了SwiftUI中的ObservableObject协议,唔,脑壳疼。
本文正在参与「掘金 2021 春招闯关活动」, 点击查看活动详情