本文共 7336 字,大约阅读时间需要 24 分钟。
# 介绍
在对listview的构造原理的分析时所记录的一些笔记,希望对诸位有所帮助。
listview整体构造庞大,故将由多章构成。 阅读以下内容,你需要对 flutter widget的 构建、布局和绘制有所了解 这篇我们主要分析listview构造 child-view的流程。# Listview
listview 有很多构造函数 如:
ListView
ListView.builder ListView.separated ...等等 我们以ListView.builder 来进行分析。## ListView.builder
首先我们看一个简单的使用代码
```
ListView.builder( //多少个item itemCount: 200, //构建child-view的方法 itemBuilder: (ctx,index){ return Container( alignment: Alignment.center, width: size.width,height: 250, color: index%2 == 0 ? Colors.red:Colors.blueAccent, child: Text('$index',style: TextStyle(color: Colors.white,fontSize: 30),), ); }) ``` 以上会构建一个红蓝相间的垂直滚动的list widget。我们点进去看一下listview的源码。
## ListView.builder 结构图
为了不偏离我们的目标,迷失在源码中,我会只标注相关的参数和方法,其他则忽略不写
 由上图中可以看到,定义了一些绘制、布局相关的参数。方法则只有一个 :```
//两个sliver list 没有太大区别,我们这里只以第二个为准 //返回了一个SliverList(delegate: childrenDelegate) @override Widget buildChildLayout(BuildContext context) { if (itemExtent != null) { return SliverFixedExtentList( delegate: childrenDelegate, itemExtent: itemExtent, ); } return SliverList(delegate: childrenDelegate); } ```在返回的sliverList中传入了一个childrenDelegate,这个参数是在构造函数初始化的时候创建的。
``` /// 我删除一些无关代码 ListView.builder({@required IndexedWidgetBuilder itemBuilder,
}) :
childrenDelegate = SliverChildBuilderDelegate( itemBuilder, childCount: itemCount, addAutomaticKeepAlives: addAutomaticKeepAlives, addRepaintBoundaries: addRepaintBoundaries, addSemanticIndexes: addSemanticIndexes, ); ```我们停下脚步,瞅一眼这个 SliverChildBuilderDelegate
## SliverChildBuilderDelegate

可见,这个只是对我们的 itemBuilder()方法进行一个再封装,并作为sliver构建孩子的代理类。
我们回到buildChildLayout(BuildContext context),即创建sliver的地方。
此方法在哪里调用呢? 根据经验来看,应该是和它的父类有关。
## BoxScrollView

由注释来看,它是一个使用 单孩子 布局模型的scrollView。
由上图来看,除了接收一些子类的参数外,还有两个方法:
这个就是我们上面在listview内实现的方法
```
/// Subclasses should override this method to build the layout model. @protected Widget buildChildLayout(BuildContext context);```
呦~看到调用buildChildLayout()的地方了,第一行就是,后面的则是对sliver进行一些装饰型的包裹,我们不用管。
```
@override List<Widget> buildSlivers(BuildContext context) { Widget sliver = buildChildLayout(context); EdgeInsetsGeometry effectivePadding = padding; if (padding == null) { final MediaQueryData mediaQuery = MediaQuery.of(context, nullOk: true); if (mediaQuery != null) { // Automatically pad sliver with padding from MediaQuery. final EdgeInsets mediaQueryHorizontalPadding = mediaQuery.padding.copyWith(top: 0.0, bottom: 0.0); final EdgeInsets mediaQueryVerticalPadding = mediaQuery.padding.copyWith(left: 0.0, right: 0.0); // Consume the main axis padding with SliverPadding. effectivePadding = scrollDirection == Axis.vertical ? mediaQueryVerticalPadding : mediaQueryHorizontalPadding; // Leave behind the cross axis padding. sliver = MediaQuery( data: mediaQuery.copyWith( padding: scrollDirection == Axis.vertical ? mediaQueryHorizontalPadding : mediaQueryVerticalPadding, ), child: sliver, ); } }if (effectivePadding != null)
sliver = SliverPadding(padding: effectivePadding, sliver: sliver); return <Widget>[ sliver ]; } ```那么,这个buildSlivers()在哪里调用呢? 我们看它的父类
## ScrollView
abstract class ScrollView extends StatelessWidget{}
终于到了我们熟悉的东西:StatelessWidget 来看一下scrollview的结构图
官方给了scrollview这样的注释:
```
一个可滚动的wiget,其有三部分组成1、一个Scrollable widget监听用户的各种手势,并实现用于滚动的交互设计
2、一个viewport(视窗) widget,实现用于滚动时展现部分scroll view内容的视觉设计
3、一个或多个sliver,可以用来组成各种滚动效果的widget
```明白了构成,我们回到原路上继续往下走。scrollview 有三个方法:
这个就是boxscrollview 实现的啦
```
@protected List<Widget> buildSlivers(BuildContext context); ```这个方法则是用于创建视窗 这里我们走 第二个 return
```
@protected Widget buildViewport( BuildContext context, ViewportOffset offset, AxisDirection axisDirection, List<Widget> slivers, ) { if (shrinkWrap) { return ShrinkWrappingViewport( axisDirection: axisDirection, offset: offset, slivers: slivers, ); } return Viewport( axisDirection: axisDirection, offset: offset, slivers: slivers, cacheExtent: cacheExtent, center: center, anchor: anchor, ); } ```我们熟悉的 build()方法
```
@override Widget build(BuildContext context) { //这里就调用了 boxScrollview 的 buildSlivers方法 final List<Widget> slivers = buildSlivers(context); final AxisDirection axisDirection = getDirection(context);final ScrollController scrollController =
primary ? PrimaryScrollController.of(context) : controller; //我们用Scrollable 对 sliver进行包裹 final Scrollable scrollable = Scrollable( dragStartBehavior: dragStartBehavior, axisDirection: axisDirection, controller: scrollController, physics: physics, semanticChildCount: semanticChildCount, viewportBuilder: (BuildContext context, ViewportOffset offset) { //这里调用了 buildViewport, //即,构造了 视窗并将咱们的sliver 传进去了 return buildViewport(context, offset, axisDirection, slivers); }, ); //无关的我删除掉, 最终会返回scrollable(对它进行一些包裹)}
```至此,我们对Listview的调用逻辑就有了一个大致的了解,如下图:

根据上图,我们知道了
buildSlivers() 返回的sliverList 这个跟我们的 child-view 息息相关
buildChildLayout() 对sliverList进行了一些装饰 build() 会依次调用上面的方法,并用Scrollable widget进行包裹 同时创建一个视窗viewport由此可见,与咱们的child-view构建相关的可能是sliverList,我们来看看
## SliverList

由上图可见,sliverList最终继承自 renderObjectWidget,换言之它就是一个自定义view。
renderObjectWidget 咱们常用的widget不少都继承它,帮助咱们的widget创建 renderObject
你说element干啥的?简单讲,主要负责‘增删改查’ 如果不太理解,可以去查阅布局、绘制相关的文章再回来,这个sliver list 就实现了一个父类方法:
```
@override RenderSliverList createRenderObject(BuildContext context) { final SliverMultiBoxAdaptorElement element = context as SliverMultiBoxAdaptorElement; return RenderSliverList(childManager: element); } ```在哪里调用的,咱也不知道,看看它的父类
## SliverMultiBoxAdaptorWidget
他有一个方法:当然还有上面子类实现的:createRenderObject()方法
```
// 看! 创建了element 还把自己这个widget传进去了 @override SliverMultiBoxAdaptorElement createElement() => SliverMultiBoxAdaptorElement(this); ```这样一看,是不是就跟咱们平常的 stateful widget的构建流程一样了?
我们来看看这个element
## SliverMultiBoxAdaptorElement
点开内部一看,与element如出一辙。
其内部有一大堆方法,全都与widget构建有关,如:
performRebuild()
update() updateChild() didFinishChildLayout() ...等等等等不一而足 简单说一下performRebuild()这个方法所有的widget其element都会有这个方法,此方法间接受vsync信号驱动,
最终会调用你熟悉的build()方法构建你的widget。像你平常setState()都会触发这个方法,不然也不会叫XXrebuild了

我们发现在performRebuild方法中,会调用_build方法
```
Widget _build(int index) { return widget.delegate.build(this, index); } ```这个delegate熟悉吧(SliverChildBuilderDelegate)? 它的build方法就是对我们的itemBuilder的封装。
还记得开头我们使用的listview的代码吗?
```
ListView.builder( itemCount: 200,itemBuilder: (ctx,index){
return Container( alignment: Alignment.center, width: size.width,height: 250, color: index%2 == 0 ? Colors.red:Colors.blueAccent, child: Text('$index',style: TextStyle(color: Colors.white,fontSize: 30),), ); }) ```这个itemBuilder的方法是不是一样?
注: BuildContext 就是element
至此,我们就分析完了child-view的构建,在稍后的文章将介绍视窗(Viewport)和Scrollable widget,谢谢大家阅读。转载地址:http://tqnr.baihongyu.com/