重庆分公司,新征程启航
为企业提供网站建设、域名注册、服务器等服务
页面中的各界面元素(Widget)以树的形式组织,即控件树。Flutter通过控件树中的每个控件创建不同类型的渲染对象,组成渲染对象树。而渲染对象树在Flutter的展示过程分为三个阶段:布局、绘制、合成和渲染。
网站建设哪家好,找成都创新互联!专注于网页设计、网站建设、微信开发、重庆小程序开发、集团企业网站建设等服务项目。为回馈新老客户创新互联还提供了隆林免费建站欢迎大家使用!
(一)布局
Flutter采用深度优先机制遍历渲染对象树,决定渲染对象树中各渲染对象在屏幕上的位置和尺寸。在布局过程中,渲染对象树中的每个渲染对象都会接收父对象的布局约束参数,决定自己的大小,然后父对象按照控件逻辑决定各个子对象的位置,完成布局过程。
为了防止因子节点发生变化而导致整个控件树重新布局,Flutter加入了一个机制——布局边界(Relayout Boundary),可以在某些节点自动或手动地设置布局边界,当边界内的任何对象发生重新布局时,不会影响边界外的对象,反之亦然。
二)绘制
布局完成后,渲染对象树中的每个节点都有了明确的尺寸和位置。Flutter会把所有的渲染对象绘制到不同的图层上。与布局过程一样,绘制过程也是深度优先遍历,而且总是先绘制自身,再绘制子节点。
以下图为例:节点1在绘制完自身后,会再绘制节点2,然后绘制它的子节点3、4和5,最后绘制节点6。
可以看到,由于一些其他原因(比如,视图手动合并)导致2的子节点5与它的兄弟节点6处于了同一层,这样会导致当节点2需要重绘的时候,与其无关的节点6也会被重绘,带来性能损耗。
为了解决这一问题,Flutter提出了与布局边界对应的机制——重绘边界(Repaint Boundary)。在重绘边界内,Flutter会强制切换新的图层,这样就可以避免边界内外的互相影响,避免无关内容置于同一图层引起不必要的重绘。
重绘边界的一个典型场景是Scrollview。ScrollView滚动的时候需要刷新视图内容,从而触发内容重绘。而当滚动内容重绘时,一般情况下其他内容是不需要重绘的,这时候重绘边界就派上用场了。
(三)合成和渲染
终端设备的页面越来越复杂,因此Flutter的渲染树层级通常很多,直接交付给渲染引擎进行多图层渲染,可能会出现大量渲染内容的重复绘制,所以还需要先进行一次图层合成,即将所有的图层根据大小、层级、透明度等规则计算出最终的显示效果,将相同的图层归类合并,简化渲染树,提高渲染效率。
合并完成后,Flutter会将几何图层数据交由Skia引擎加工成二维图像数据,最终交由GPU进行渲染,完成界面的展示。
四、总结
咱们从各种业界主流跨端方案与Flutter的对比开始,到Flutter的简要介绍以及Flutter的运行机制,并以界面渲染过程为例,从布局、绘制、合成和渲染三个阶段讲述了Flutter的实现原理。相信大家对Flutter已经有一个整体认知,赶快一起上手操作起来吧!
介绍一下我最近开发的一个Flutter插件。Flutter Math是一个完全使用Dart和Flutter渲染LaTeX数学公式的插件,可以看作是移植在Dart和Flutter平台上的KaTeX。其支持的LaTeX语法大致与KaTeX相同(少数当前版本暂不支持的语法详见Github仓库),对数学公式的渲染结果几乎像素级还原KaTeX。
相比已有的flutter_tex插件,本插件完全的基于Dart和Flutter,不包含任何WebView和Javascript,性能远超过flutter_tex。大家如果频繁遇到flutter_tex带来的卡顿以及崩溃,欢迎试用Flutter Math。
x = \frac{-b \pm \sqrt{b^2 - 4ac}}{2a}
i\hbar\frac{\partial}{\partial t}\Psi(\vec x,t) = -\frac{\hbar}{2m}\nabla^2\Psi(\vec x,t)+ V(\vec x)\Psi(\vec x,t)
\hat f(\xi) = \int_{-\infty}^\infty f(x)e^{- 2\pi i \xi x}\mathrm{d}x
项目仓库地址: GitHub地址 。目前版本为0.1.1,更多信息以及暂不支持的KaTeX特性欢迎查阅GitHub页面。欢迎打星,欢迎fork!
开始FrameWork层会通知Engine表示自己可以进行渲染了,在下一个Vsync信号到来之时,Engine层会通过Windows.onDrawFrame回调Framework进行整个页面的构建与绘制。每次收到渲染页面的通知后,Engine调用Windows.onDrawFrame最终交给_handleDrawFrame()方法进行处理。最后会走到 WidgetsBinding.drawFrame() = buildOwner.buildScope(renderViewElement) = _dirtyElements[index].rebuild() = performRebuild() 这里会触发当前element的widget的build方法= updateChild() 注意这里已经是子节点进行接下来的操作了= 子节点update() = 子节点rebuild() = 子节点performRebuild() ...
小结:所以说在widget树中,越高层的 build() 里调用 setState() 会导致遍历所有的子节点=遍历所有子节点的子节点...
话术总结: setState() 会将当前的 element 标记为 脏 ,并交由 buildOwner ,由 buildOwner 加入自己的 脏列表中 ,等收到页面渲染的通知后(这里流程简略掉),会调用 buildOwenr. buildScope () ,这里会遍历 脏列表 然后每一个都会调用 rebuild() , rebuild() 又会调用 performRebuild() , performRebuild() 则会调用 build() 方法重建当前的 element ,然后调用 updateChild () 开始更新子节点,进而触发子节点的 rebuild() 方法,进行下一轮的周期...一直到最后一个节点
抽象类Element 有mount方法
抽象类Widget 有createElement方法
RenderObjectWidget有createElement方法 和 createRenderObject方法
每一个Widget, 都有createElement方法,通过createElement方法 创建一个Element对象,
Element加入Element树中,它会创建三种Element ,每个Element 有个mount方法
第一种:RenderObjectElement(RenderObjectWidget的createElement方法)
mount方法中 调用
widget.createRenderObject(this) ,创建RenderObject对象,RenderObject对象加入Render树中
第二种:StatefulElement继承ComponentElement
第三种:StatelessElement继承ComponentElement
并不是所有的Widget都会被独立渲染!只有继承RenderObjectWidget的才会创建RenderObject对象!