介绍
在对listview的构造原理的分析时所记录的一些笔记,希望对诸位有所帮助。
listview整体构造庞大,故将由多章构成。
阅读以下内容,你需要对 flutter widget的 构建、布局和绘制有所了解
系列文章
这篇我们主要分析listview中用于显示children的Viewport(聚焦在显示children方面)。
回顾
我们先回顾一下上篇的流程图
可见在listview是 statelessWidget,其在build()方法中构建了sliverList用于创建children,然后用Scrollable对齐进行包裹,并创建一个Viewport。
Scrollable将在下篇将,我们先看一下viewport.
Viewport
由注释中,我们知道:
viewport是滚动机制的主力,它根据自身的尺寸和给定的‘offset’来显示children。当offset改变时(如,滚动),不同的children穿过这个视窗。
结合上篇,我们可以大致理解,slivers 是由一堆child构造成一个长条(sliver),然后这个viewport在这个sliver之上,我们所能看到的内容由viewport的尺寸和offset来决定,而我们滚动的时候就会改变这个offset。
换句话说,有个窗户在这个sliver上面,我们透过这个窗户来看sliver的内容,而滚动时,就改变了窗户的位置。
暂停
这里我们暂停一下
因为viewport直接继承自xxRenderObjectWidget,避免后面看的一头雾水,这里对widget、element和renderObject构造布局和绘制做个简单介绍:
当开始渲染的时候,会先通过createElement()创建element,随后element会在其performRebuild()方法中调用widget的build方法创建widget,而widget继承自xxRenderObjectWidget(当然最高级的还是Widget)。进而在performRebuild()方法中调用创建的widget的createRenderObject()创建对应的 renderObject并调用attachXXX()方法将它挂到renderObject树上,而后期在绘制的时候,则会调用renderObject的paint()方法完成绘制。
大致流程就是这样,有兴趣的可以去找文章详细了解,我们继续。
继续
再看上图可以看到它继承自MultiChildRenderObjectWidget,覆写了它的三个方法
因为三个方法都被覆写,所以我们就无需查看MultiChildRenderObjectWidget的源码
只要知道,咱们的slivers(children)被MultiChildRenderObjectWidget 保存着即可
由之前的了解,我们先来看element (_ViewportElement)
@override
_ViewportElement createElement() => _ViewportElement(this);
//下面两个方法为分别是创建render object 以及更新它
//也就是根据‘offset’创建显示的内容(children)
//两个方法本质上有些相似
//遂,我们以createRenderObject()方法来分析
@override
RenderViewport createRenderObject(BuildContext context)
@override
void updateRenderObject(BuildContext context, RenderViewport renderObject)
_ViewportElement & MultiChildRenderObjectElement
因为_ViewportElement中的方法与咱们要分析的东西没有太大关系,我们直接来看MultiChildRenderObjectElement。
class MultiChildRenderObjectElement extends RenderObjectElement
不看那些‘增删改查’,我们注意这两个东西
List<Element> children //这个就是存放我们child-view 生成的child-element
mount() //方法,则在buildOwner创建完新的widget后调用,
//此方法后面就要开始layout和paint了
我们知道performRebuild()方法中会调用build()方法创建一个widget。
widget创建完毕后,就会通过inflateWidget()创建它的element,随后便会调用element的mount()方法,将它挂载到element 树上。
当然实际流程并不是这样简单,
其次inflateWidget() 并不是只有performRebuild()方法会调用,update也会。
@override
void mount(Element parent, dynamic newSlot) {
//这里调用父类的mount()方法
//最终会调用MultiChildRenderObjectWidget(就是上面继续那里)
//的createRenderObject()方法
//创建 viewport的renderObject 并挂到树上
//当布局、绘制的时候就用到这个renderObject
//这里,我们的‘窗户’算是创建完毕了
super.mount(parent, newSlot);
_children = List<Element>(widget.children.length);
Element previousChild;
for (int i = 0; i < _children.length; i += 1) {
final Element newChild = inflateWidget(widget.children[i], IndexedSlot<Element>(i, previousChild));
_children[i] = newChild;
previousChild = newChild;
}
}
从代码来看,viewport的element创建完毕后,会调用它的mount方法,而在方法内,我们根据children (我们传进来的那个slivers), 生成了对应的 elements _children。
那么,这个List _children 在哪里使用的呢?我们看到上面还有一个方法:
@override
void visitChildren(ElementVisitor visitor) {
for (final Element child in _children) {
if (!_forgottenChildren.contains(child))
visitor(child);
}
}
这个方法遍历观察了child,我们来看看哪里调用了这个方法。 ctrl+左键,发现这段代码调用了 :
RenderObject get renderObject {
RenderObject result;
void visit(Element element) {
assert(result == null); // this verifies that there's only one child
if (element is RenderObjectElement)
result = element.renderObject;
else
element.visitChildren(visit);
}
visit(this);
return result;
}
这是一个get方法,返回当前element持有的renderObject,而它调用的地方之一就是上面的mount方法中创建完RenderObject之后的attachRenderObject()方法。
attachRenderObject()方法被覆写,内部会调用insertChildRenderObject()方法
(在这里)会将child的renderObject插入到 renderObject树上去。
至此,整个viewPort及其children的renderObject树就构建完成了,随后将使用此树来进行布局和绘制。
viewport & children 布局流程
下图是上面流程的简化,方便我们理解与梳理。
谢谢大家阅读,如有错误的地方欢迎指出。
相关文章
pageview的构造与listview有很多相似甚至相同的地方,也许下面两篇可以帮助你从另一个角度更全面的了解flutter的滚动view的原理