GSYFlutterBook/Flutter-N25.md

139 lines
7.7 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Flutter 小技巧之 3.10 全新的 MediaQuery 优化与 InheritedModel
关于 `MediaQuery` 我们介绍过不少,比如在之前的[《MediaQuery 和 build 优化你不知道的秘密》](https://juejin.cn/post/7114098725600903175)里就介绍过,**要慎重在 `Scaffold` 之外使用 `MediaQuery.of(context)`** ,这是因为 `MediaQuery.of``BuildContext` 的绑定可能会导致一些不必要的性能开销,例如键盘弹起时,会导致相关的 `MediaQuery.of(context)` 绑定的页面出现重构。
比如下面这个例子,我们在 `MyHomePage` 里使用了 `MediaQuery.of(context).size` 并打印输出,然后跳转到 `EditPage` 页面,弹出键盘 ,这时候会发生什么情况?
```dart
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
print("######### MyHomePage ${MediaQuery.of(context).size}");
return Scaffold(
body: Container(
alignment: Alignment.center,
child: InkWell(
onTap: () {
Navigator.of(context).push(CupertinoPageRoute(builder: (context) {
return EditPage();
}));
},
child: new Text(
"Click",
style: TextStyle(fontSize: 50),
),
),
),
);
}
}
class EditPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: new Text("ControllerDemoPage"),
),
extendBody: true,
body: Column(
children: [
new Spacer(),
new Container(
margin: EdgeInsets.all(10),
child: new Center(
child: new TextField(),
),
),
new Spacer(),
],
),
);
}
}
```
如下图 log 所示 可以看到在键盘弹起来的过程,因为 bottom 发生改变,所以 `MediaQueryData` 发生了改变,从而导致上一级的 `MyHomePage` 虽然不可见,但是在键盘弹起的过程里也被不断 build 。
![](http://img.cdn.guoshuyu.cn/20230602_N25/image1.png)
虽然在之前的[小技巧](https://juejin.cn/post/7114098725600903175)里我们介绍了解决方式,但是 3.10 开始有更优雅的做法,同时也更方便我们自足控制更细的颗粒度地去管理 `InheritedWidget` 里的绑定关系,那就是使用 `InheritedModel`
# MediaQuery
在 3.10 里 `MediaQuery` 增加了需要针对特定参数的 `****of` 方式,例如 `MediaQuery.platformBrightnessOf(context);` ,这些方法对应在 `_MediaQueryAspect` 里都有一个枚举类型,而在 Flutter Framework 里,这些参数的调用都修改成了新的 `****of` 类型方法。
![](http://img.cdn.guoshuyu.cn/20230602_N25/image2.png)
例如一开始的例子,只需要将 `MediaQuery.of(context).size` 修改为 `MediaQuery.sizeOf(context)` ,那么跳转到 `EditPage` 页面,弹出键盘 ,在键盘弹起来的过程中不会再导致 `MyHomePage` rebuild 输出 log。
而之所以会这样的原因,其实是因为这些 `MediaQuery.******Of(context);` 内部调用的是 `InheritedModel.inheritFrom` 实现。
是的3.10 开始 [`MediaQuery` 继承从 `InheritedWidget` 变成 `InheritedModel`](https://github.com/flutter/flutter/commit/73cb7c2fc5ecab0476ef5d41d1227ed09f73db56) ,而 **`InheritedModel``inheritFrom` 方法可以让开发者可以通过 `aspect` 来决定数据改变时是否调用对应更新**。
![](http://img.cdn.guoshuyu.cn/20230602_N25/image3.png)
![](http://img.cdn.guoshuyu.cn/20230602_N25/image4.png)
> 小技巧一:**3.10 现在可以通过将 `MediaQuery.of` 获取参数的方式替换成 `MediaQuery.******Of(context);` 来减少不必要的 rebuild** 。
# InheritedModel
使用 `InheritedModel` 只需要继承它就可以,之后**需要重点关注的是 `updateShouldNotifyDependent` 方法,它用于决定应该什么时候 rebuild** 。
如下图所示是 `MediaQuery` 的实现,在 `updateShouldNotifyDependent` 里我们可以通过 `dependencies` 里的类型来进行区分,比如调用时是通过 `InheritedModel.inheritFrom<MediaQuery>(context, aspect: _MediaQueryAspect.size)` 输入,那么判断时就会进入到 `_MediaQueryAspect. size` 这个 ` case`
![](http://img.cdn.guoshuyu.cn/20230602_N25/image5.png)
如果此时 `size` 参数发生了改变,就返回 `true` ,从而产生 rebuild反之返回放 false这就是 `InheritedModel` 可以根据绑定具体变量来更新页面的原因。
> 当然这里你不一定要传枚举,你喜欢的话传 String 也可以,具体可以根据你的爱好来设定。
那为什么 `InheritedModel``inheritFrom` 方法可以达到这样的效果?
我们简单看一下 `inheritFrom` 的源码实现,如下图所示:
- 在没有 `aspect` 的时候直接调用 `dependOnInheritedWidgetOfExactType()` 那就是和之前普通的 `of(context)` 没什么区别
- 在有 `aspect` 的时候,会先通过 `_findModels` 找到对应的 `InheritedElement` ,然后调用 `dependOnInheritedElement()` 绑定
![](http://img.cdn.guoshuyu.cn/20230602_N25/image6.png)
可能这时候你有些疑问,不要急慢慢来,我们先看 `dependOnInheritedWidgetOfExactType()``dependOnInheritedElement()` 其实 `dependOnInheritedWidgetOfExactType()` 内部也是调用 `dependOnInheritedElement()` 来完成绑定,那么这里前后的区别是什么?
如果你仔细看,**这里的区别在于 `dependOnInheritedElement()` 多使用了 `aspect`** ,对应到 `MediaQuery` 里就是如 `_MediaQueryAspect.size` 这个 `aspect` ,这样在后续 `updateShouldNotifyDependent` 时就会被用上。
如下图所示是 `InheritedModel``InheritedModelElement` ,可以看到 **`inheritFrom` 传入的 `aspect` 会变成 `dependencies`** ,而这个` dependencies` 就是我们在 `updateShouldNotifyDependent` 里用来判断的类型依据。
![](http://img.cdn.guoshuyu.cn/20230602_N25/image7.png)
最后,在 `notifyDependent` 方法里可以看到,只有 `updateShouldNotifyDependent` 返回 `true` 时,才会调用 `didChangeDependencies` 去更新。
所以 `inheritFrom` 的特殊之处在于:**当存在 `aspect` 时,该 `aspect` 会变成 `dependencies` 集合,然后通过 `updateShouldNotifyDependent` 来决定是否触发更新**。
至于 `_findModels` 方法其实你无需纠结,虽然它是传入一个 List但是一般情况下你只会获取到一个。什么时候会可能有多个就是你 override 了 `InheritedModel``isSupportedAspect` 方法,并且会根据 `aspect` 条件有不同判断返回时可能会有多个。
![](http://img.cdn.guoshuyu.cn/20230602_N25/image8.png)
例如都是继承 `InheritedModel` ,但是 `isSupportedAspect` 可以根据条件来决定你这个实例是否支持 `Aspect` 绑定。
```dart
@override
bool isSupportedAspect(Object aspect) {
return aspects == null || aspects!.contains(aspect);
}
```
另外 `_findModels` 里用的是 `getElementForInheritedWidgetOfExactType()`,它和 `dependOnInheritedWidgetOfExactType()` 的区别就是前者会注册依赖关系,而后者不会,所以 `_findModels` 顾名思义只是找出符合条件的 `InheritedModel`
![](http://img.cdn.guoshuyu.cn/20230602_N25/image9.png)
# 最后
最后总结一下,今天的小技巧其实很简单,**就是更新你的 `MediaQuery.of` 到对应参数的 `MediaQuery.*****of` 从而提升应用性能**,并且了解到 `InheritedModel` 的实现逻辑和自定义支持,从而学会优化你现在的 `InheritedWidget ` 的使用。
如果你还有什么问题,欢迎留言评论交流。