赵走x博客
网站访问量:151892
首页
书籍
软件
工具
古诗词
搜索
登录
75、插件开发:iOS端API实现
64、文件操作
85、图片加载原理与缓存
84、Flutter运行机制-从启动到显示
83、RenderObject和RenderBox
82、Element与BuildContext
81、Flutter UI系统
80、国际化常见问题
79、使用Intl包
78、实现Localizations
77、让App支持多语言
76、Texture和PlatformView
73、开发Flutter插件
72、插件开发:平台通道简介
71、开发Package
70、Json转Dart Model类
69、使用Socket API
68、使用WebSockets
67、实例:Http分块下载
66、Http请求-Dio http库
65、通过HttpClient发起HTTP请求
63、自绘实例:圆形背景渐变进度条
62、自绘组件 (CustomPaint与Canvas)
61、组合实例:TurnBox
60、组合现有组件
59、自定义组件方法简介
58、动画过渡组件
57、通用"切换动画"组件(AnimatedSwitcher)
56、交织动画
55、Hero动画
54、自定义路由切换动画
53、动画基本结构及状态监听
51、动画
50、通知(Notification)
49、全局事件总线
48、手势识别
47、原始指针事件处理
46、事件处理与通知
45、对话框详解
44、异步UI更新(FutureBuilder、StreamBuilder)
43、颜色和主题
42、跨组件状态共享(Provider)
41、数据共享(InheritedWidget)
40、导航返回拦截(WillPopScope)
39、功能型Widget简介
38、滚动监听及控制
37、CustomScrollView
36、GridView
35、ListView
34、SingleChildScrollView
33、可滚动组件简介
32、剪裁(Clip)
31、Scaffold、TabBar、底部导航
30、Container
29、变换(Transform)
28、装饰容器DecoratedBox
27、尺寸限制类容器
26、填充(Padding)
25、容器类Widget
24、对齐与相对定位(Align)
23、层叠布局 Stack、Positioned
22、流式布局
21、弹性布局(Flex)
20、线性布局(Row和Column)
19、布局类组件简介
18、进度指示器
17、输入框及表单
16、单选开关和复选框
15、图片及ICON
14、按钮
13、文本、字体样式
12、状态管理
11、Widget简介
10、Flutter异常捕获
9、调试Flutter应用
8、资源管理
7、包管理
6、路由管理
5、第一个Flutter应该:计数器应用示例
4、Dart语言简介
3、搭建Flutter开发环境
2、初识Flutter
1、移动开发技术简介
41、数据共享(InheritedWidget)
资源编号:76193
Flutter实战
书籍
热度:116
InheritedWidget是Flutter中非常重要的一个功能型组件,它提供了一种数据在widget树中从上到下传递、共享的方式,比如我们在应用的根widget中通过InheritedWidget共享了一个数据,那么我们便可以在任意子widget中来获取该共享的数据!这个特性在一些需要在widget树中共享数据的场景中非常方便!如Flutter SDK中正是通过InheritedWidget来共享应用主题(Theme)和Locale (当前语言环境)信息的。
InheritedWidget是Flutter中非常重要的一个功能型组件,它提供了一种数据在widget树中从上到下传递、共享的方式,比如我们在应用的根widget中通过InheritedWidget共享了一个数据,那么我们便可以在任意子widget中来获取该共享的数据!这个特性在一些需要在widget树中共享数据的场景中非常方便!如Flutter SDK中正是通过InheritedWidget来共享应用主题(Theme)和Locale (当前语言环境)信息的。 InheritedWidget和React中的context功能类似,和逐级传递数据相比,它们能实现组件跨级传递数据。InheritedWidget的在widget树中数据传递方向是从上到下的,这和通知Notification(将在下一章中介绍)的传递方向正好相反。 # 1、didChangeDependencies 在之前介绍StatefulWidget时,我们提到State对象有一个didChangeDependencies回调,它会在“依赖”发生变化时被Flutter Framework调用。而这个“依赖”指的就是子widget是否使用了父widget中InheritedWidget的数据!如果使用了,则代表子widget依赖有依赖InheritedWidget;如果没有使用则代表没有依赖。这种机制可以使子组件在所依赖的InheritedWidget变化时来更新自身!比如当主题、locale(语言)等发生变化时,依赖其的子widget的didChangeDependencies方法将会被调用。 下面我们看一下之前“计数器”示例应用程序的InheritedWidget版本。需要说明的是,本示例主要是为了演示InheritedWidget的功能特性,并不是计数器的推荐实现方式。 首先,我们通过继承InheritedWidget,将当前计数器点击次数保存在ShareDataWidget的data属性中: ``` class ShareDataWidget extends InheritedWidget { ShareDataWidget({ @required this.data, Widget child }) :super(child: child); final int data; //需要在子树中共享的数据,保存点击次数 //定义一个便捷方法,方便子树中的widget获取共享数据 static ShareDataWidget of(BuildContext context) { return context.inheritFromWidgetOfExactType(ShareDataWidget); } //该回调决定当data发生变化时,是否通知子树中依赖data的Widget @override bool updateShouldNotify(ShareDataWidget old) { //如果返回true,则子树中依赖(build函数中有调用)本widget //的子widget的`state.didChangeDependencies`会被调用 return old.data != data; } } ``` 然后我们实现一个子组件`_TestWidget`,在其build方法中引用ShareDataWidget中的数据。同时,在其didChangeDependencies() 回调中打印日志: ``` class _TestWidget extends StatefulWidget { @override __TestWidgetState createState() => new __TestWidgetState(); } class __TestWidgetState extends State<_TestWidget> { @override Widget build(BuildContext context) { //使用InheritedWidget中的共享数据 return Text(ShareDataWidget .of(context) .data .toString()); } @override void didChangeDependencies() { super.didChangeDependencies(); //父或祖先widget中的InheritedWidget改变(updateShouldNotify返回true)时会被调用。 //如果build中没有依赖InheritedWidget,则此回调不会被调用。 print("Dependencies change"); } } ``` 最后,我们创建一个按钮,每点击一次,就将ShareDataWidget的值自增: ``` class InheritedWidgetTestRoute extends StatefulWidget { @override _InheritedWidgetTestRouteState createState() => new _InheritedWidgetTestRouteState(); } class _InheritedWidgetTestRouteState extends State
{ int count = 0; @override Widget build(BuildContext context) { return Center( child: ShareDataWidget( //使用ShareDataWidget data: count, child: Column( mainAxisAlignment: MainAxisAlignment.center, children:
[ Padding( padding: const EdgeInsets.only(bottom: 20.0), child: _TestWidget(),//子widget中依赖ShareDataWidget ), RaisedButton( child: Text("Increment"), //每点击一次,将count自增,然后重新build,ShareDataWidget的data将被更新 onPressed: () => setState(() => ++count), ) ], ), ), ); } } ``` 运行后界面如图7-1所示:  每点击一次按钮,计数器就会自增,控制台就会打印一句日志: ``` I/flutter ( 8513): Dependencies change ``` 可见依赖发生变化后,其didChangeDependencies()会被调用。但是读者要注意,如果`_TestWidget`的build方法中没有使用ShareDataWidget的数据,那么它的didChangeDependencies()将不会被调用,因为它并没有依赖ShareDataWidget。例如,我们将`__TestWidgetState`代码该为下面这样,didChangeDependencies()将不会被调用: ``` class __TestWidgetState extends State<_TestWidget> { @override Widget build(BuildContext context) { // 使用InheritedWidget中的共享数据 // return Text(ShareDataWidget // .of(context) // .data // .toString()); return Text("text"); } @override void didChangeDependencies() { super.didChangeDependencies(); // build方法中没有依赖InheritedWidget,此回调不会被调用。 print("Dependencies change"); } } ``` 上面的代码中,我们将build()方法中依赖ShareDataWidget的代码注释掉了,然后返回一个固定Text,这样一来,当点击Increment按钮后,ShareDataWidget的data虽然发生变化,但由于`__TestWidgetState`并未依赖ShareDataWidget,所以`__TestWidgetState`的didChangeDependencies方法不会被调用。其实,这个机制很好理解,因为在数据发生变化时只对使用该数据的Widget更新是合理并且性能友好的。 >思考题:Flutter framework是怎么知道子widget有没有依赖InheritedWidget的? ### 应该在didChangeDependencies()中做什么? 一般来说,子widget很少会重写此方法,因为在依赖改变后framework也都会调用build()方法。但是,如果你需要在依赖改变后执行一些昂贵的操作,比如网络请求,这时最好的方式就是在此方法中执行,这样可以避免每次build()都执行这些昂贵操作。 # 2、深入了解InheritedWidget 现在来思考一下,如果我们只想在`__TestWidgetState`中引用ShareDataWidget数据,但却不希望在ShareDataWidget发生变化时调用`__TestWidgetState`的didChangeDependencies()方法应该怎么办?其实答案很简单,我们只需要将ShareDataWidget.of()的实现改一下即可: ``` //定义一个便捷方法,方便子树中的widget获取共享数据 static ShareDataWidget of(BuildContext context) { //return context.inheritFromWidgetOfExactType(ShareDataWidget); return context.ancestorInheritedElementForWidgetOfExactType(ShareDataWidget).widget; } ``` 唯一的改动就是获取ShareDataWidget对象的方式,把inheritFromWidgetOfExactType()方法换成了context.ancestorInheritedElementForWidgetOfExactType(ShareDataWidget).widget,那么他们到底有什么区别呢,我们看一下这两个方法的源码(实现代码在Element类中,Context和Element的关系我们将在后面专门介绍): ``` @override InheritedElement ancestorInheritedElementForWidgetOfExactType(Type targetType) { final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[targetType]; return ancestor; } @override InheritedWidget inheritFromWidgetOfExactType(Type targetType, { Object aspect }) { final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[targetType]; //多出的部分 if (ancestor != null) { assert(ancestor is InheritedElement); return inheritFromElement(ancestor, aspect: aspect); } _hadUnsatisfiedDependencies = true; return null; } ``` 我们可以看到,inheritFromWidgetOfExactType() 比 ancestorInheritedElementForWidgetOfExactType()多调了inheritFromElement方法,inheritFromElement源码如下: ``` @override InheritedWidget inheritFromElement(InheritedElement ancestor, { Object aspect }) { //注册依赖关系 _dependencies ??= HashSet
(); _dependencies.add(ancestor); ancestor.updateDependencies(this, aspect); return ancestor.widget; } ``` 可以看到inheritFromElement方法中主要是注册了依赖关系!看到这里也就清晰了,调用inheritFromWidgetOfExactType() 和 ancestorInheritedElementForWidgetOfExactType()的区别就是前者会注册依赖关系,而后者不会,所以在调用inheritFromWidgetOfExactType()时,InheritedWidget和依赖它的子孙组件关系便完成了注册,之后当InheritedWidget发生变化时,就会更新依赖它的子孙组件,也就是会调这些子孙组件的didChangeDependencies()方法和build()方法。而当调用的是 ancestorInheritedElementForWidgetOfExactType()时,由于没有注册依赖关系,所以之后当InheritedWidget发生变化时,就不会更新相应的子孙Widget。 注意,如果将上面示例中ShareDataWidget.of()方法实现改成调用ancestorInheritedElementForWidgetOfExactType(),运行示例后,点击"Increment"按钮,会发现__TestWidgetState的didChangeDependencies()方法确实不会再被调用,但是其build()仍然会被调用!造成这个的原因其实是,点击"Increment"按钮后,会调用_InheritedWidgetTestRouteState的setState()方法,此时会重新构建整个页面,由于示例中,__TestWidget 并没有任何缓存,所以它也都会被重新构建,所以也会调用build()方法。 那么,现在就带来了一个问题:实际上,我们只想更新子树中依赖了ShareDataWidget的组件,而现在只要调用_InheritedWidgetTestRouteState的setState()方法,所有子节点都会被重新build,这很没必要,那么有什么办法可以避免呢?答案是缓存!一个简单的做法就是通过封装一个StatefulWidget,将子Widget树缓存起来,具体做法下一节我们将通过实现一个Provider Widget 来演示如何缓存,以及如何利用InheritedWidget 来实现Flutter全局状态共享。