Flutter完整开发实战详解(9、 深刻绘制原理)

做为系列文章的第九篇,本篇主要深刻了解 Widget 中绘制相关的原理,探索 Flutter 里的 RenderObject 最后是如何走完屏幕上的最后一步,结尾再经过实际例子理解如何设计一个 Flutter 的自定义绘制。node

前文:git

在第6、第七篇中咱们知道了 WidgetElementRenderObject 的关系,同时也知道了Widget 的布局逻辑,最终全部 Widget 都转化为 RenderObject 对象, 它们堆叠出咱们想要的画面。github

因此在 Flutter 中,最终页面的 LayoutPaint 等都会发生在 Widget 所对应的 RenderObject 子类中,而 RenderObject 也是 Flutter 跨平台的最大的特色之一:全部的控件都与平台无关 ,这里简单的人话就是: Flutter 只要求系统提供的 “Canvas”,而后开发者经过 Widget 生成 RenderObject “直接” 经过引擎绘制到屏幕上。canvas

ps 从这里开始篇幅略长,可能须要消费您的一点耐心。bash

1、绘制过程

咱们知道 Widget 最终都转化为 RenderObject , 因此了解绘制咱们直接先看 RenderObjectpaint 方法。ide

以下图所示,全部的 RenderObject 子类都必须实现 paint 方法,而且该方法并非给用户直接调用,须要更新绘制时,你能够经过 markNeddsPaint 方法去触发界面绘制。布局

image.png

那么,按照“国际流程”,在经历大小和布局等位置计算以后,最终 paint 方法会被调用,该方法带有两个参数: PaintingContextOffset ,它们就是完成绘制的关键所在,那么相信此时你们确定有个疑问就是:post

  • PaintingContext 是什么?
  • Offset 是什么?

经过飞速查阅源码,咱们能够首先了解到有 :测试

  • PaintingContext 的关键是 A place to paint ,同时它在父类 ClipContext 是包含有 Canvas ,而且 PaintingContext 的构造方法是 @protected,只在 PaintingContext.repaintCompositedChildpushLayer 时自动建立。动画

  • Offsetpaint 中主要是提供当前控件在屏幕的相对偏移值,提供绘制时肯定绘制的坐标。

OK,继续往下走,那么既然 PaintingContext 叫 Context ,那它确定是存在上下文关系,那它是在哪里开始建立的呢?

经过调试源码可知,项目在 runApp 时经过 WidgetsFlutterBinding 启动,而在之前的篇幅中咱们知道, WidgetsFlutterBinding 是一个“胶水类”,它会触发 mixinRendererBinding ,以下图建立出根 node 的 PaintingContext

好了,那么Offset 呢?以下图,对于 Offset 的传递,是经过父控件和子控件的 offset 相加以后,一级一级的将须要绘制的坐标结合去传递的。

目前简单来讲,经过 PaintingContextOffset ,在布局以后咱们就能够在屏幕上准确的地方绘制会须要的画面。

一、测试绘制

这里咱们先作一个有趣的测试。

咱们如今屏幕上经过 Container 限制一个高为 60 的绿色容器,以下图,暂时忽略容器内的 Slider 控件 ,咱们图中绘制了一个 100 x 100 的红色方块,这时候咱们会看到下图右边的效果是:纳尼?为何只有这么小?

事实上,由于正常 Flutter 在绘制 Container 的时候,AppBar 已经帮咱们计算了状态栏和标题栏高度误差,但咱们这里在用 Canvas 时直接粗暴的 drawRect,绘制出来的红色小方框,左部和顶部起点均为0,实际上是从状态栏开始计算绘制的。

那若是咱们调整位置呢?把起点 top 调整到 300,出现了以下图的效果:纳尼?红色小方块竟然画出去了,明明 Container 只有绿色的大小。

其实这里的问题仍是在于 PaintingContext ,它有一个参数是 estimatedBounds ,而 estimatedBounds 正常是在建立时经过 child.paintBounds 赋值的,可是对于 estimatedBounds 还有以下的描述:原来画出去也是能够。

The canvas will allow painting outside these bounds.
The [estimatedBounds] rectangle is in the [canvas] coordinate system.
复制代码

因此到这里你能够通俗的总结, 对于 Flutter 而言,整个屏幕都是一块画布,咱们经过各类 OffsetRect 肯定了位置,而后经过 PaintingContextCanvas 绘制上去,目标是整个屏幕区域,整个屏幕就是一帧,每次改变都是从新绘制。

二、RepaintBoundary

固然,每次从新绘制并非彻底从新绘制 ,这里面实际上是存在一些规制的。

还记得前面的 markNeedsPaint 方法吗 ?咱们先从 markNeedsPaint() 开始, 总结出其大体流程以下图,能够看到 markNeedsPaintrequestVisualUpdate 时确实触发了引擎去更新绘制界面。

绘制大体流程图

接着咱们看源码,如源码所示,当调用 markNeedsPaint() 时,RenderObject 就会往上的父节点去查找,根据 isRepaintBoundary 是否为 true,会决定是否从这里开始去触发重绘。换个说法就是,肯定要更新哪些区域。

因此其实流程应该是:经过isRepaintBoundary 往上肯定了更新区域,经过 requestVisualUpdate 方法触发更新往下绘制。

markNeedsPaint

而且从源码中能够看出, isRepaintBoundary 只有 get ,因此它只能被子类 override ,由子类代表是不是为重绘的边缘,好比 RenderProxyBoxRenderViewRenderFlowRenderObjectisRepaintBoundary 都是 true。

因此若是一个区域绘制很频繁,且能够不影响父控件的状况下,其实能够将 override isRepaintBoundary 为 true。

三、Layer

上文咱们知道了,当 isRepaintBoundary 为 true 时,那么该区域就是一个可更新绘制区域,而当这个区域造成时, 其实就会新建立一个 Layer

不一样的 Layer 下的 RenderObject 是能够独立的工做,好比 OffsetLayer 就在 RenderObject 中用到,它就是用来作定位绘制的。

同时这也引生出了一个结论:不是每一个 RenderObject 都具备 Layer 的,由于这受 isRepaintBoundary 的影响。

其次在 RenderObject 中还有一个属性叫 needsCompositing ,它会影响生成多少层的 Layer ,而这些 Layer 又会组成一棵 Layer Tree 。好吧,到这里又多了一个树,实际上这颗树才是所谓真正去给引擎绘制的树。

到这里咱们大概就了解了 RenderObject 的整个绘制流程,而且这个绘制时机咱们是去“触发”的,而不是主动调用,而且更新是判断区域的。 嗯~有点 React 的味道!

2、Slider 控件的绘制实现

前面咱们讲了那么多绘制的流程,如今让咱们从 Slider 这个控件的源码,去看看一个绘制控件的设计实现吧。

整个 Slider 的实现能够说是很 Flutter 了,大致结构以下图。

_RenderSlider 中,除了 手势动画 以外,其他的每一个绘制的部分,都是独立的 Component 去完成绘制,而这些 Component 都是经过 SliderThemeSliderThemeData 提供的。

巧合的是,SliderTheme 自己就是一个 InheritedWidget 。看过之前篇章的同窗应该会知道, InheritedWidget 通常就是用于作状态共享的,因此若是你须要自定义 Slider ,完成能够经过 SliderTheme 嵌套,而后经过 SliderThemeData 选择性的自定义你须要的模块。

而且以下图,在 _RenderSlider 中注册时手势和动画,会在监听中去触发 markNeedsPaint 方法,这就是为何你的触摸可以响应画面的缘由了。

同时能够看到 _SliderRender内的参数都重写了 getset 方法, 在 set 时也会有 markNeedsPaint() ,或者调用 _updateLabelPainter 去间接调用 markNeedsLayout

image.png

至于 Slider 内的各类 Shape 的绘制这里就不展开了,都是 Canvas 标准的 pathTodrawRecttranslatedrawPath等熟悉的操做了。

自此,第九篇终于结束了!(///▽///)

资源推荐

完整开源项目推荐:
文章

《Flutter完整开发实战详解(1、Dart语言和Flutter基础)》

《Flutter完整开发实战详解(2、 快速开发实战篇)》

《Flutter完整开发实战详解(3、 打包与填坑篇)》

《Flutter完整开发实战详解(4、Redux、主题、国际化)》

《Flutter完整开发实战详解(5、 深刻探索)》

《Flutter完整开发实战详解(6、 深刻Widget原理)》

《Flutter完整开发实战详解(7、 深刻布局原理)》

《Flutter完整开发实战详解(8、 实用技巧与填坑)》

《Flutter完整开发实战详解(9、 深刻绘制原理)》

《Flutter完整开发实战详解(10、 深刻图片加载流程)》

《Flutter完整开发实战详解(11、全面深刻理解Stream)》

《跨平台项目开源项目推荐》

《移动端跨平台开发的深度解析》

《React Native 的将来与React Hooks》

咱们还会再见吗?
相关文章
相关标签/搜索