何为硬件绘图
屏幕图像的基本为多个无力显示单元组成,每个单元都可以称之为物理像素点,而人类可观测的颜色值由RGB组成,都有28个值,RGB就有224个值。为了更新画面,屏幕以固定的评率刷新,比如60HZ就是每秒展示60张图像,当切换每一帧画面的时候都会有显示器发出信号,然后同步CPU、GPU。一次绘制大致的流程是CPU计算好内容,交给GPU,GPU渲染后放入帧缓冲区,然后由视屏控制器传递给显示器。
Flutter UI
Flutter则开辟了一种全新的思路,从头到尾重写一套跨平台的UI框架,包括UI控件、渲染逻辑甚至开发语言。渲染引擎依靠跨平台的Skia图形库来实现,依赖系统的只有图形绘制相关的接口,提供了一套Dart API,然后在底层通过OpenGL这种跨平台的绘制库(内部会调用操作系统API)实现了一套代码跨多端。由于Dart API也是调用操作系统API,所以它的性能接近原生。
flutter 渲染
从创建到渲染的大体流程是:根据Widget生成Element,然后创建相应的RenderObject并关联到Element.renderObject属性上,最后再通过RenderObject来完成布局排列和绘制
class StatelessElement extends ComponentElement {
...
@override
Widget build() => widget.build(this);
...
}
BuildContext就是widget对应的Element,所以我们可以通过context在StatelessWidget和StatefulWidget的build方法中直接访问Element对象。我们获取主题数据的代码Theme.of(context)内部正是调用了Element的dependOnInheritedWidgetOfExactType()方法
RenderObject
RenderObject类本身实现了一套基础的layout和绘制协议,但是并没有定义子节点模型(如一个节点可以有几个子节点,没有子节点?一个?两个?或者更多?)。 它也没有定义坐标系统(如子节点定位是在笛卡尔坐标中还是极坐标?)和具体的布局协议(是通过宽高还是通过constraint和size?,或者是否由父节点在子节点布局之前或之后设置子节点的大小和位置等)。为此,Flutter提供了一个RenderBox类,它继承自
RenderObject
,布局坐标系统采用笛卡尔坐标系,这和Android和iOS原生坐标系是一致的,都是屏幕的top、left是原点,然后分宽高两个轴
布局的基本过程
- RenderBox利用size控制宽高
- layout方法将BoxConstraints作为参数传入子节点,另一个参数relayoutBoundary表示子节点布局变化是否影响父节点重新布局。为true则不需要。
- 在performResize() 和 performLayout()两个方法进行实际的测量和布局逻辑。layout是个递归调用,完成全部父子节点的布局。
- ParentData存储节点信息
命中测试
- 当发生用户事件时,会从根节点(RenderView)开始进行命中测试
bool hitTest(HitTestResult result, { Offset position }) {
if (child != null)
child.hitTest(result, position: position); //递归子RenderBox进行命中测试
result.add(HitTestEntry(this)); //将测试结果添加到result中
return true;
}
- 确定指针与屏幕接触的位置存在哪些组件(result中), 指针按下事件(以及该指针的后续事件)然后被分发到由命中测试发现的最内部的组件,然后从那里开始,事件会在组件树中向上冒泡,这些事件会从最内部的组件被分发到组件树根的路径上的所有组件
- 通过dispatchEvent事件进行分发,但并不是所有的控件的 RenderObject 子类都会处理 handleEvent ,大部分时候,RenderPointerListener 处理 handleEvent 事件,这个控件被嵌套在RawGestureDetector中,handleEvent会根据不同的事件类型,回调到RawGestureDetector的相关手势处理
- 当存在多个手势的时候,就会有手势竞争。
- 单击事件:down的时候不产生结果,在UP的时候才会强行取一个。
- sweep:直接取第一个被分发的,也就是最上层的。
ListView保证流程度的做法
延迟构建模型:Flutter中提出一个Sliver(中文为“薄片”的意思)概念,如果一个可滚动组件支持Sliver模型,那么该滚动可以将子组件分成好多个“薄片”(Sliver),只有当Sliver出现在视口中时才会去构建它,这种模型也称为“基于Sliver的延迟构建模型”。可滚动组件中有很多都支持基于Sliver的延迟构建模型
itemExtent就代表子组件的宽度。在ListView中,指定itemExtent比让子组件自己决定自身长度会更高效,这是因为指定itemExtent后,滚动系统可以提前知道列表的长度,而无需每次构建子组件时都去再计算一下,尤其是在滚动位置频繁变化时(滚动系统需要频繁去计算列表高度)
ListView.builder(
itemCount: 100,
itemExtent: 50.0, //强制高度为50.0
itemBuilder: (BuildContext context, int index) {
return ListTile(title: Text("$index"));
}
);