Flutter:从ValueListenableBuilder到Provider(二)|技术点评
上一节简单回顾
在上一篇文章里面,主要是讲的ValueListenableBuilder的基本使用,我个人认为ValueNotifier与ValueListenableBuilder在一对一的绑定(一个变量对于一个Widget)中,简单易用,容易理解,而如果在多对一的绑定中,使用ValueListenableBuilder可能会陷入嵌套的地狱之中,所以这个时候就是MultiProvider登场了。
往期文章:
MultiProvider
说到MultiProvider,它其实是provider库中,针对多状态绑定一个Widget的一个组件。 说到provider库我们总会与全局状态管理联系在一起,孰不知,其实provider在单页面管理上面也非常好的,甚至可以说,理解了provider在单页面管理,那么全局管理不在话下。
那么我们还是先接着上一节的文末最后的例子,接着写代码。 需求如下:两个输入框,第一个输入框输入11位手机号并且第二个输入框输入6位验证码时,按钮变色且响应点击事件。
在写组件代码之前,我们先定义下面两个模型:
class IsRightPhoneNumber extends ChangeNotifier {
bool _isOK = false;
bool get isOK => _isOK;
set isOK(bool newValue) {
_isOK = newValue;
notifyListeners();
}
}
class IsRightCode extends ChangeNotifier {
bool _isOK = false;
bool get isOK => _isOK;
set isOK(bool newValue) {
_isOK = newValue;
notifyListeners();
}
}
也许你会好奇,为啥要写这两个模型,这个两个模型又有何用?上一节里面,我们并没有深入说ValueNotifier的源码,那么我们现在去看看ValueNotifier的源码:
class ValueNotifier<T> extends ChangeNotifier implements ValueListenable<T> {
/// Creates a [ChangeNotifier] that wraps this value.
ValueNotifier(this._value);
/// The current value stored in this notifier.
///
/// When the value is replaced with something that is not equal to the old
/// value as evaluated by the equality operator ==, this class notifies its
/// listeners.
@override
T get value => _value;
T _value;
set value(T newValue) {
if (_value == newValue)
return;
_value = newValue;
notifyListeners();
}
@override
String toString() => '${describeIdentity(this)}($value)';
}
如果你仔细看,就会发现,我们定义的模型和ValueNotifier用异曲同工之妙,都继承了ChangeNotifier,而ChangeNotifier中最最重要的方法就是notifyListeners()
,它干嘛呢?它就是通知告诉界面,值变化了,要刷新界面了。
可以说我们自定义的模型是ValueNotifier的简化与特化版,简化是因为我们并没有遵守ValueListenable
协议,特化是因为我们将ValueNotifier中泛型指定成为我们定义的类型。
当然自定义的模型直接继承ValueNotifier来进行编写也是可以的。
下面进入界面的主要代码:
和上一节一样,其他的非主题逻辑的代码我会附在最后。
/// 定义两个需要监听的全局变量
final _isRightPhoneNumber = IsRightPhoneNumber();
final _isRightCode = IsRightCode();
@override
Widget build(BuildContext context) {
/// 构建页面的顶层我们使用了MultiProvider组件,并注册监听_isRightPhoneNumber与_isRightCode
return MultiProvider(
providers: [
ChangeNotifierProvider(
create: (context) => _isRightPhoneNumber,
),
ChangeNotifierProvider(
create: (context) => _isRightCode,
),
],
child: GestureDetector(
child: Scaffold(
appBar: AppBar(
title: Text("MultiProvider两个输入绑定一个按钮"),
),
body: Container(
margin: const EdgeInsets.fromLTRB(16, 40, 16, 0),
child: ListView(
children: [
_textField(
title: "输入11位字符串",
limit: 11,
onChanged: (inputString) {
_isRightPhoneNumber.isOK = inputString.length == 11;
}),
_textField(
title: "输入6位字符串",
limit: 6,
onChanged: (inputString) {
_isRightCode.isOK = inputString.length == 6;
}),
/// 使用provider库中的Selector组件
/// 首先不要被<IsRightPhoneNumber, IsRightCode, bool>这三个泛型瞎坏了,
/// 它实际是定义两个入参分别为IsRightPhoneNumber和IsRightCode类型,返回bool类型,在Selector2中的selecto进行使用
Selector2<IsRightPhoneNumber, IsRightCode, bool>(
selector: (context, value1, value2) =>
value1.isOK && value2.isOK,
builder: (context, isAllRight, child) {
return Container(
margin: const EdgeInsets.fromLTRB(0, 30, 0, 0),
child: RaisedButton(
color: _buttonColor(isAllRight),
shape: BeveledRectangleBorder(
borderRadius: BorderRadius.circular(0),
),
child: Container(
child: Center(
child: Text(
"Selector2+自定义模型 确认修改",
style: TextStyle(
fontSize: 16,
color: isAllRight
? Colors.white
: DSColor.colorA3A4A4,
),
),
),
height: 48,
),
onPressed: () {
if (isAllRight) {}
},
),
);
},
),
],
),
),
),
onTap: () => print("键盘收起方法"),
),
);
}
代码解读:
一个关键点是构建页面的顶层我们使用了MultiProvider组件,并注册监听_isRightPhoneNumber与_isRightCode;
另一个关键是Selector2组件的使用,首先不要被<IsRightPhoneNumber, IsRightCode, bool>这三个泛型吓坏了,它实际是定义两个入参分别为IsRightPhoneNumber和IsRightCode类型,返回bool类型,在Selector2中的selector进行使用,而Selector2中的builder其实和ValueListenableBuilder的builder非常相似,就是通过变量去创建Widget,并且在变量变化的时候去改变Widget,而这个变量实际是由selector进行控制,selector的在本例子中本质类型是一个bool Function(IsRightPhoneNumber, IsRightCode)
的函数而已。
Selector与Consumer
Selector2是专门处理有两个入参,合并为一个新的出参的组件,另还有Selector3、Selector4、Selector5、Selector6这样的组件可以使用,分别对应的意思通过类名最后追加的数字应该就可以理解吧。
除了Selector系列组件,还有一个Consumer组件,我理解它其实就是Selector简化版本,入参少了selector和shouldRebuild参数,它的builder方法中会直接上按顺序定义好的泛型参数,进行Widget的创建与更新。这里我们的按钮也完全可以使用Consumer2来进行构建,其代码如下:
Consumer2<IsRightPhoneNumber, IsRightCode>(
builder: (context, IsRightPhoneNumber value1, IsRightCode value2, child) {
final isAllRight = value1.isOK && value2.isOK;
return Container(
margin: const EdgeInsets.fromLTRB(0, 30, 0, 0),
child: RaisedButton(
color: _buttonColor(isAllRight),
shape: BeveledRectangleBorder(
borderRadius: BorderRadius.circular(0),
),
child: Container(
child: Center(
child: Text(
"Consumer2+自定义模型 确认修改",
style: TextStyle(
fontSize: 16,
color: isAllRight
? Colors.white
: DSColor.colorA3A4A4,
),
),
),
height: 48,
),
onPressed: () {
if (isAllRight) {}
},
),
);
},
),
整体而言,不管是Selector还是Consumer,在使用方法上和ValueListenableBuilder基本一致,而Selector的精细程度上更上一层楼,除了入参有selector方法,用来展平多入参统一到唯一变量进行Widget的更新外,另外还有shouldRebuild参数来判断新值与旧值是否真的需要进行Widget更新,整体而言,对于单页面的多参数控制Widget,无论是从编码与易用性上看,对于开发者都是十分友好的。
抽出来的非业务代码
将下面这些代码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);
}
}
下一节,聊一下我对全局管理的理解吧,嗯,之前掉坑里去了,现在也没爬起来。。。
参考文档:
转载自:https://juejin.cn/post/6937890162219155469