关于 MediaQuery 咱们介绍过不少,比方在之前的《MediaQuery 和 build 优化你不知道的隐秘》里就介绍过,要慎重在 Scaffold 之外运用 MediaQuery.of(context) ,这是因为 MediaQuery.of 对 BuildContext 的绑定或许会导致一些不必要的性能开销,例如键盘弹起时,会导致相关的 MediaQuery.of(context) 绑定的页面出现重构。
比方下面这个比方,咱们在 MyHomePage 里运用了 MediaQuery.of(context).size 并打印输出,然后跳转到 EditPage 页面,弹出键盘 ,这时分会发生什么情况?
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 。
虽然在之前的小技巧里咱们介绍了解决办法,可是 3.10 开端有更高雅的做法,一起也更便利咱们自足操控更细的颗粒度地去办理 InheritedWidget 里的绑定联系,那便是运用 InheritedModel 。
MediaQuery
在 3.10 里 MediaQuery 增加了需求针对特定参数的 ****of 办法,例如 MediaQuery.platformBrightnessOf(context); ,这些办法对应在 _MediaQueryAspect 里都有一个枚举类型,而在 Flutter Framework 里,这些参数的调用都修改成了新的 ****of 类型办法。
例如一开端的比方,只需求将 MediaQuery.of(context).size 修改为 MediaQuery.sizeOf(context) ,那么跳转到 EditPage 页面,弹出键盘 ,在键盘弹起来的进程中不会再导致 MyHomePage rebuild 输出 log。
而之所以会这样的原因,其实是因为这些 MediaQuery.******Of(context); 内部调用的是 InheritedModel.inheritFrom 完成。
是的,3.10 开端 MediaQuery 承继从 InheritedWidget 变成 InheritedModel ,而 InheritedModel 的 inheritFrom 办法能够让开发者能够经过 aspect 来决议数据改动时是否调用对应更新。
小技巧一: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 。
假如此刻 size 参数发生了改动,就回来 true ,然后发生 rebuild,反之回来放 false,这便是 InheritedModel 能够依据绑定具体变量来更新页面的原因。
当然这儿你纷歧定要传枚举,你喜爱的话传 String 也能够,具体能够依据你的爱好来设定。
那为什么 InheritedModel 的 inheritFrom 办法能够达到这样的效果?
咱们简略看一下 inheritFrom 的源码完成,如下图所示:
- 在没有
aspect的时分直接调用dependOnInheritedWidgetOfExactType()那便是和之前普通的of(context)没什么差异 - 在有
aspect的时分,会先经过_findModels找到对应的InheritedElement,然后调用dependOnInheritedElement()绑定
或许这时分你有些疑问,不要急慢慢来,咱们先看 dependOnInheritedWidgetOfExactType() 和 dependOnInheritedElement() , 其实 dependOnInheritedWidgetOfExactType() 内部也是调用 dependOnInheritedElement() 来完成绑定,那么这儿前后的差异是什么?
假如你仔细看,这儿的差异在于 dependOnInheritedElement() 多运用了 aspect ,对应到 MediaQuery 里便是如 _MediaQueryAspect.size 这个 aspect ,这样在后续 updateShouldNotifyDependent 时就会被用上。
如下图所示是 InheritedModel 的 InheritedModelElement ,能够看到 inheritFrom 传入的 aspect 会变成 dependencies ,而这个 dependencies 便是咱们在 updateShouldNotifyDependent 里用来判断的类型依据。
最终,在 notifyDependent 办法里能够看到,只有 updateShouldNotifyDependent 回来 true 时,才会调用 didChangeDependencies 去更新。
所以 inheritFrom 的特别之处在于:当存在 aspect 时,该 aspect 会变成 dependencies 集合,然后经过 updateShouldNotifyDependent 来决议是否触发更新。
至于 _findModels 办法其实你无需纠结,虽然它是传入一个 List,可是一般情况下你只会获取到一个。什么时分会或许有多个,便是你 override 了 InheritedModel 的 isSupportedAspect 办法,而且会依据 aspect 条件有不同判断回来时或许会有多个。
例如都是承继 InheritedModel ,可是 isSupportedAspect 能够依据条件来决议你这个实例是否支撑 Aspect 绑定。
@override
bool isSupportedAspect(Object aspect) {
return aspects == null || aspects!.contains(aspect);
}
另外 _findModels 里用的是 getElementForInheritedWidgetOfExactType(),它和 dependOnInheritedWidgetOfExactType() 的差异便是前者会注册依赖联系,而后者不会,所以 _findModels 顾名思义仅仅找出契合条件的 InheritedModel 。
最终
最终总结一下,今日的小技巧其实很简略,便是更新你的 MediaQuery.of 到对应参数的 MediaQuery.*****of 然后提升使用性能,而且了解到 InheritedModel 的完成逻辑和自定义支撑,然后学会优化你现在的 InheritedWidget 的运用。
假如你还有什么问题,欢迎留言谈论交流。









