重磅! flutter视图局部更新
新建一个flutter工程, 以flutter框架给我们自动生成的代码为例, 当我们点击按钮更新记数_counter
时,最终是通过调用State<T>.setState
来更新视图的:
setState(() {_counter++;
})
首先需要理解为什么要setState
, 它表示当前节点的数据变更,通知视图需要更新.更新哪个视图? 持有当前这个State
实例的节点对应的视图. 注意这个节点具体指的是Element
对象, Widget
只是创建了State
实例(_MyHomePageState createState()
),并没有持有, 同样State
又继续创建了子视图,也没有持有子视图(Widget build(BuildContext context)
), 持有State
的只有Element
. setState
的参数是一个方法执行体, 实现哪些数据的具体变更, 所以其实没有设置所谓的状态, 还不如叫notifyChanges
来的明晰.
其次需要理解视图如何更新. 像Text
那个控件, 文本是作为构造函数的参数直接传给控件的, 根本连类似setText
的方法也没有! 所以显示出来的数据要更新除了新建视图对象外没有别的办法!
这里就体现了flutter与传统移动端界面开发的巨大不同: 视图是通过新建视图对象来完成更新的. 以往的界面开发中视图对象都是一个比较重比较大的对象, 视图要避免冗余, 要尽量复用, 不要频繁创建. 但在flutter中就不是这样了, 代表视图对象的Widget
是轻量对象, 它不持有State
, 也不持有Widget
, 所有视图对象都是通过build
这种创建型关系建立. 所以开发过程中也要坚决避免自定义的Widget
持有数据, 因为Widget
对象会被很快替换掉.
有了上述两点就能明白setState
之后发生了什么: 当前_MyHomePageState
的Widget build(BuildContext context)
方法会被调用, 于是生成了新的Scaffold
对象,连带着AppBar,FloatingActionButton,Column
一干控件其中自然包括我们需要展示的Text
对象, 这时传入的文本是更新过后的_counter
,于是视图得以更新.
只是想更新一个个小小的文本框就不得不重新创建整个视图?!
对, 目前的机制就是这样. 那随着视图层次加深, 界面交互复杂,这种重新创建型操作就没有一点问题? 毕竟对象再小也有开销, 那么多对象累积起来,也可能造成创建过程的消耗.于是我们的问题终于来了:
有没有方法可以只更新部分视图?
缩小一下更新范围不就得了? 现在的更新范围大是因为_MyHomePageState.build
被调用返回了整个视图, 而_MyHomePageState
对应的视图是MyHomePage
. 所以创建一个State<Text>
, build
返回Text
控件实例, 再将这个State<Text>
持有, 数据变更时调用State<Text>
.setState()`不就可以达到目的?
这个想法符合flutter本身的机制, 但问题就是谁来创建这个State<Text>
? 如前文所述, 首先只有StatefulWidget
才能创建State实例, 其次必须是父节点创建这个State<Text>
. 但示例中Text
的父节点Column
首先就不是StatefulWidget
; 就算是了, 我们还要声明Widget类继承Column
覆盖build
方法, 再声明State类继承State<Text>
, 烦都烦死了. 那如果从Text
向上找一个StatefulWidget
, 创建的时候是Text
的一个祖先节点, 存在一点冗余可以接受呢? 这个想法实践上一点也不可行, 且不说有个特定视图对象的查找过程, 上面所说的各种类声明一点也没有减少, 所以这个路子是没法搞的.
所以还是从setState
源码入手, 看一个节点到底是如何更新视图的.
State.setStateElement.markNeedsBuildElement._dirty = true;BuildOwner.scheduleBuildForBuildOwner._dirtyElements.addElement._inDirtyList = true;
过程比想象的简单, 最后仅仅是将Element节点标识成dirty并加入到了BuildOwner的_dirtyElements列表里. 从Element角度看setState
这个名称似乎也没有错, 不过它是相对Element
说的, 具体设置的是Element
的dirty
状态. 那我们只需找到Text
对应的Element节点并调用一下它的markNeedsBuild
不就ok了? 所以先要找到Text
这个Widget节点对应的Element节点.
在以前的建树流程中说过Element节点结构像挂钩, 只有parent没有直接持有children, 要找子节点需要像Element.visitChildren
那样传递一个访问者来进行遍历, 而判断条件自然就是Element持有的Widget是否是我们需要更新的Widget, 于是有:
static Element findChild(Element e, Widget w) {Element child;void visit(Element element) {if (w == element.widget)child = element;elseelement.visitChildren(visit);}visit(e);return child;}
但是对找到的element设置markNeedsBuild
竟然不起作用! 查了半天原因, 才明白还是把建树流程搞混了, markNeedsBuild
仅让当前Element节点的build被调用, 创建的是当前节点的子节点视图对象, 而我们现在需要的是把当前子节点持有的视图对象替换掉('视图更新是通过创建新的Widget对象'), 同时不能重新创建当前Element节点及其子节点. 而Element.update(Widget)
正是这个作用!! 如果说inflateWidget
是初始化Element节点树, 那update
正是在树建立成功后进行更新操作. 于是有
onPressed: () {_counter++;Element e = findChild(context as Element, title);if (e != null) {e.update(title);}
},
因为要找节点, 所以用了一个title
持有了Text
, 以方便在onTap()
的上下文中作查找参数.
但这样也是不对的! 这里存在2个问题:
- 视图对象没有更新. 我们需要展示的是一个新的_counter相关的文本, 因此需要的是一个新的视图对象, 现在传入的还是老的视图对象,等于什么也没更新...
- 直接调用
Element.update
是有异常的, 跟踪了一下发现一个标识状态的数据_debugStateLockLevel
不对, 原来要在BuildOwner.lockState
中执行才可以.
这里啰里八嗦的写这一坨是想表明一个的新想法的实现是环环相扣关联细节的, 很多时候思路是对的, 但细节实现错误导致半途而废, 行百里者半九十!
还是上完整代码, findChild
前面已定义就不再贴了:
import 'package:flutter/foundation.dart'
import 'package:flutter/material.dart';
import 'utils/ElementUtils.dart';void main() {runApp(new MyApp());
}class MyApp extends StatelessWidget {@overrideWidget build(BuildContext context) {return MaterialApp(title: 'Flutter Demo',theme: ThemeData(primarySwatch: Colors.blue,),home: MyHomePage(title: 'Flutter Demo Home Pages'),);}
}class MyHomePage extends StatefulWidget {MyHomePage({Key key, this.title}) : super(key: key);final String title;@override_MyHomePageState createState() => _MyHomePageState();
}class _MyHomePageState extends State<MyHomePage> {int _counter = 0;@overrideWidget build(BuildContext context) {Widget title = new Text('another times: $_counter',);return Scaffold(appBar: AppBar(title: Text(widget.title),),body: Center(child: Column(mainAxisAlignment: MainAxisAlignment.center,children: <Widget>[Text('You have pushed the button this many times:',),Text('$_counter',style: Theme.of(context).textTheme.display1,),title,],),),floatingActionButton: FloatingActionButton(onPressed: () {_counter++;Element e = findChild(context as Element, title);if (e != null) {title = new Text('another times: $_counter',);e.owner.lockState(() {e.update(title);});}},tooltip: 'Increment',child: Icon(Icons.add),),);}
}
现在只是重新创建了仅仅一个视图哦, 它不快都不行~!
然而还是需要考虑一下这么做的缺点或者劣势是什么
首先, 明显的存在一个查询操作, 这是由Element机制决定的, 遍历只能通过访问者模式, 时间复杂度O(n), 能不能避免这个查询或者建立Widget到Element的映射? 也可以, 但是至少要查询一次, 因为创建widget的时候Element可能还没创建或者还没有关联, 只有Element树建立完成之后才能查的到.
其次, 如果一个操作涉及多个视图的更新, 我们不得不持有多个widget, 并查找多个widget对应的element, 还是有多个查询操作, 这么麻烦还不如全部新建呢.
所以只能视情况而定, 没有包打天下一劳永逸的方案, 合适的才是最好的!
转载于:https://www.cnblogs.com/lindeer/p/11567901.html
重磅! flutter视图局部更新相关推荐
- vue数组中数据变化但是视图没有更新解决方案
vue数组中数据变化但是视图没有更新解决方案 参考文章: (1)vue数组中数据变化但是视图没有更新解决方案 (2)https://www.cnblogs.com/sufubo/p/6906261.h ...
- 解决vue中对象属性改变视图不更新的问题
解决vue中对象属性改变视图不更新的问题 参考文章: (1)解决vue中对象属性改变视图不更新的问题 (2)https://www.cnblogs.com/buxiugangzi/p/12050165 ...
- ES使用脚本进行局部更新的排错记录
初学Elasticsearch,在按照<Elasticsearch服务器开发(第2版)>进行学习的过程中,在P17页中1.4.5 更新文档小节,使用脚本对文档进行局部更新的时候遇到了如下报 ...
- WebApiClient的JsonPatch局部更新
1. 文章目的 随着WebApiClient的不断完善,越来越多开发者选择WebApiClient替换原生的HttpClient,本文将介绍使用WebApiClient来完成JsonPatch提交的新 ...
- vue 修改对象的值视图没有发生改变_在vue中处理对象属性改变视图不更新问题? - echart...
...图等等,但是这些代码比较难写,因此我们通常会用借助echarts,那你知道如何使用echarts吗?这篇文章就和大家讲讲echarts的使用方法,有一定的参考价值,感兴趣的朋友可以看看.以饼状图 ...
- vue数据改变了,视图不更新不刷新问题
vue数据改变了,视图不更新不刷新问题 描述:在对象中添加一个属性 seen,初始想法使用for循环添加 seen 属性,然后改变这个属性去更新视图,然后发现不行. 解决,使用$set: this.$ ...
- flutter APP自动更新
flutter APP自动更新 前言 在pubspec.yaml中安装依赖 在main.dart文件中,初始化FlutterDownLoader 配置网络 在AndroidManifest.xml新增 ...
- safair中vue修改了数据,但是视图没有更新解决方案
使用vuex,也适用了splice来改数据, 修改了评论的数据,但是视图没有更新. state.tabContainer.splice(index, 1, {info: info}); 查看dom元素 ...
- 微信小程序数组更新,但视图不更新的问题
1.起因 写一个微信小程序的时候,需要在云数据库中拉取所有用户数据,展示到页面上,展示用户列表,一开始是这么实现的 新建一个空数组,然后从从云数据库取数据,push到数组 data: {sj:[]}, ...
最新文章
- InvokeHelper,让跨线程访问/修改主界面控件不再麻烦(转)
- C语言十六进制转换为八进制(附完整源码)
- 9.6Gbps WiFi联盟宣布802.11ax协议!
- 机器学习代码实战——保存和加载模型(Save and Load Model)
- quick-cocos2d-x 游戏开发——StateMachine 状态机
- IE6下链接onclick事件处理中的请求被aborted
- MASM32汇编SDK安装
- USB设备仿真框架设计指南——6.DSF核心模拟器
- 深度学习目标检测之SSD网络(超级详细)
- spark streaming读取kafka数据,记录offset
- JAVA8 UnaryOperator接口
- 李沐老师 PyTorch版——线性回归 + softmax回归的简洁实现(3)
- c# opengl tao
- 阿里云弹性计算总经理张献涛:智能化、高效能、新交互将重塑互联网
- java的数据库连接编程(jdbc)技术_Java的数据库连接编程(JDBC)技术
- react+antd实现图片上传并且剪裁(请参照最新文章,此案例有bug)
- java web租车系统_JavaWeb在线租车服务系统项目源码(福利)
- 数字人民币APP更新后 打不开/闪退 的解决方法
- K8S核心插件-coredns服务
- Springboot + Ureport
热门文章
- hint oracle qbname_从才oracle中找到所有列名为BANK_ACC,且BANK_ACC=000的项,并将BANK_ACC=000000的项修改为BANK_ACC=111...
- Spring @Bean @Scope @Qualifier
- regrex pattern
- python 字符串
- 为ESXi 4.x / 5.x / 6.x / 7.x创建持久暂存位置(1033696)
- 数据传输服务 DTS > 数据订阅 > 数据订阅(新版) > 创建RDS MySQL数据订阅通道(新版)
- VCSA 6.X(VMware vCenter Server Appliance)空间不足问题处理
- Linux学习总结(51)——25个Linux服务器安全小贴士
- Maven学习总结(34)——Maven settings.xml配置解读
- oracle复杂分组查询语句,oracle中的“复杂”分组统计sql