前言
本文仅关注InheritedWidget的使用方式, 理论部分可以自行查阅资料。
InheritedWidget用来跨Widget实现数据的共享,避免无穷无尽的构造函数和回调方法,在Widget嵌套越来越深的时候就能派上用场。
本文覆盖两个场景
数据只读
只读数据比较简单,遵从三步实现。
源码:gitee.com
- 新建
InheritedWidget子类- 在子Widget中使用
InheritedWidget的数据- 将
InheritedWidget设置为子视图的祖先Widget
- 新建继承
InheritedWidget的子类ReadOnlyInheritedWidget
/// 新建类继承`InheritedWidget`, 将需要使用的数据包装进来
/// 子视图通过`ReadOnlyInheritedWidget.of(context).name`获取共享的数据
class ReadOnlyInheritedWidget extends InheritedWidget {
/// 封装需要共享的数据
final String name;
final String sex;
ReadOnlyInheritedWidget({this.name, this.sex, Key key, Widget child})
: super(child: child, key: key);
/// 不需要更新, 直接返回false
@override
bool updateShouldNotify(covariant ReadOnlyInheritedWidget oldWidget) => false;
/// 使用类方法, 供子子孙孙快速找到本类
static ReadOnlyInheritedWidget of(BuildContext context) =>
context.findAncestorWidgetOfExactType<ReadOnlyInheritedWidget>();
}
- 子widget通过类方法
ReadOnlyInheritedWidget.of(context)使用共享的数据
/// 显示名字
class NameWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Row(children: [
Text('name: '),
Text('${ReadOnlyInheritedWidget.of(context).name}')
]);
}
}
/// 显示性别
class SexWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Row(children: [
Text('sex: '),
Text('${ReadOnlyInheritedWidget.of(context).sex}')
]);
}
}
- 将
InheritedWidget设置为需要使用共享数据视图的祖先
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('只读InheritedWidget')),
body: ReadOnlyInheritedWidget(
name: 'Bill Gate', sex: 'Male', child: UserInfoWidget()));
}
}
/// 显示用户信息
class UserInfoWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MeanlessWidget();
}
}
/// 无意义的中间件, 用来说明Inherited不需要构造函数来传递共享参数
class MeanlessWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Column(
children: [
NameWidget(),
SexWidget(),
],
);
}
}
更新数据
更新数据比较复杂,
InheritedWidget本身没有state, 所以也没法触发页面更新。
解决方案其实是曲线救国,将InheritedWidget设置为某个StatefullWidget的子Widget,
通过StatefullWidget的setStates()方法触发build(),从而更新InheritedWidget
既然都包裹在StatefullWidget里,所以我的想法是也没必要让外部知道InheritedWidget的存在
源码:gitee.com
实现步骤:
- 新建
StatefullWidget类UserInfoWidget- 新建
_UserInfoInheritWidget继承InheritedWidget, 将StatefullWidget的state类和共享的数据加为属性- 在子Widget中使用
UserInfoWidget.of()方法读取和更新数据- 将子Widget加入到视图中
- 新建
StatefullWidget类
/// `UserInfoWidget`替代`InheritedWidget`来实现数据共享和更新
// ignore: must_be_immutable
class UserInfoWidget extends StatefulWidget {
/// 作为InheritedWidget.child
final Widget child;
/// 待共享和更新的数据
UserInfo _userInfo;
UserInfoWidget({UserInfo userInfo, Key key, this.child})
: assert(userInfo != null),
_userInfo = userInfo,
super(key: key);
@override
UserInfoWidgetState createState() => UserInfoWidgetState();
/// 类方法, 供子子孙孙获取到当前Widget对应的state
/// `[rebuild]==true`表示子类会监听变化并刷新界面
/// 否则不刷新界面(用于某些不变的属性, 如本例中的`name`)
static UserInfoWidgetState of(BuildContext context, {bool rebuild = false}) {
return rebuild
? (context
.dependOnInheritedWidgetOfExactType<_UserInfoInheritWidget>()
.state)
: (context
.findAncestorWidgetOfExactType<_UserInfoInheritWidget>()
.state);
}
}
class UserInfoWidgetState extends State<UserInfoWidget> {
@override
Widget build(BuildContext context) {
print('Build UserInfoWidgetState');
/// UserInfoWidget充当一个容器类, 本身没有视图
return _UserInfoInheritWidget(widget._userInfo, this);
}
/// 更新共享的`count`值
void plusCount() {
// 错误用法: 会将`_UserInfoInheritWidget`的userInfo的count也更新了, 导致
// `updateShouldNotify()`会永远返回`true`
// widget._userInfo.count = widget._userInfo.count + 1;
// 正确用法:
widget._userInfo = UserInfo(
name: widget._userInfo.name, count: widget._userInfo.count + 1);
setState(() {});
}
/// 获取共享名称
String get name => widget._userInfo.name;
/// 获取共享的count
int get count => widget._userInfo.count;
}
- 新建
InheritedWidget
class _UserInfoInheritWidget extends InheritedWidget {
final UserInfo userInfo;
final UserInfoWidgetState state;
_UserInfoInheritWidget(this.userInfo, this.state, {Key key})
: super(key: key, child: state.widget.child);
/// 是否需要通知子类更新
@override
bool updateShouldNotify(covariant _UserInfoInheritWidget oldWidget) {
return userInfo.name != oldWidget.userInfo.name ||
userInfo.count != oldWidget.userInfo.count;
}
}
- 子Widget使用和更新数据
class ShowInfoWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
print('Build ShowInfoWidget');
return Text(
'${UserInfoWidget.of(context).name} ${UserInfoWidget.of(context, rebuild: true).count}');
}
}
class CountWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
print('Build _CountWidgetState');
return Row(mainAxisAlignment: MainAxisAlignment.center, children: [
IconButton(
icon: Icon(Icons.add),
onPressed: () => UserInfoWidget.of(context).plusCount()),
]);
}
}
- 加入Widget
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
print('Build MyHomePage');
return Scaffold(
appBar: AppBar(title: Text('Write InheritedWidget')),
body: Center(
child: UserInfoWidget(
userInfo: UserInfo(name: 'bill', count: 0),
child: Column(
children: [Meanless1Widget(), Meanless2Widget()],
),
),
),
);
}
}
// 无意义的中间节点,用于证明不需要构造函数来传递数据
class Meanless1Widget extends StatelessWidget {
@override
Widget build(BuildContext context) {
print('Build Meanless1Widget');
return Row(children: [ShowInfoWidget()]);
}
}
// 无意义的中间节点,用于证明不需要构造函数来传递数据
class Meanless2Widget extends StatelessWidget {
@override
Widget build(BuildContext context) {
print('Build Meanless2Widget');
return Row(
children: [CountWidget()],
);
}
}
参考
参考1写的不错,不过我觉得例子不太好,而且文章跟实际github上的例子又有差异
参考2的例子实践意义有点低。