博客
关于我
Flutter——ListView源码分析之child-view的构建
阅读量:356 次
发布时间:2019-03-04

本文共 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 结构图

    为了不偏离我们的目标,迷失在源码中,我会只标注相关的参数和方法,其他则忽略不写

    
![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/00c12f72f08748f580a1717b5f3d423d~tplv-k3u1fbpfcp-watermark.image)

由上图中可以看到,定义了一些绘制、布局相关的参数。方法则只有一个 :

```

    //两个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

![](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b13fa67cb0bc40698be5ad9f087cffb2~tplv-k3u1fbpfcp-watermark.image)

可见,这个只是对我们的 itemBuilder()方法进行一个再封装,并作为sliver构建孩子的代理类。

我们回到buildChildLayout(BuildContext context),即创建sliver的地方。

此方法在哪里调用呢? 根据经验来看,应该是和它的父类有关。

## BoxScrollView

![](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a46f9b0486b44e639f7702b02edc36e4~tplv-k3u1fbpfcp-watermark.image)

由注释来看,它是一个使用 单孩子 布局模型的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的结构图

![](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b95e6cb656554dd283fab21bacac6a85~tplv-k3u1fbpfcp-watermark.image)

官方给了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的调用逻辑就有了一个大致的了解,如下图:

![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/3226e01a9e194fab9fb665737236a294~tplv-k3u1fbpfcp-watermark.image)

根据上图,我们知道了 

    buildSlivers() 返回的sliverList    这个跟我们的 child-view 息息相关

    buildChildLayout()        对sliverList进行了一些装饰
    build()     会依次调用上面的方法,并用Scrollable widget进行包裹
                同时创建一个视窗viewport

由此可见,与咱们的child-view构建相关的可能是sliverList,我们来看看

## SliverList

![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e464cc880b4a4ab6b9bc216373ed9e63~tplv-k3u1fbpfcp-watermark.image)

由上图可见,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了

![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/64fbd53d0932422fb93ff4c72fafb210~tplv-k3u1fbpfcp-watermark.image)

我们发现在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/

你可能感兴趣的文章
MySQL 存储引擎
查看>>
mysql 存储过程 注入_mysql 视图 事务 存储过程 SQL注入
查看>>
MySQL 存储过程参数:in、out、inout
查看>>
mysql 存储过程每隔一段时间执行一次
查看>>
mysql 存在update不存在insert
查看>>
Mysql 学习总结(86)—— Mysql 的 JSON 数据类型正确使用姿势
查看>>
Mysql 学习总结(87)—— Mysql 执行计划(Explain)再总结
查看>>
Mysql 学习总结(88)—— Mysql 官方为什么不推荐用雪花 id 和 uuid 做 MySQL 主键
查看>>
Mysql 学习总结(89)—— Mysql 库表容量统计
查看>>
mysql 实现主从复制/主从同步
查看>>
mysql 审核_审核MySQL数据库上的登录
查看>>
mysql 导入 sql 文件时 ERROR 1046 (3D000) no database selected 错误的解决
查看>>
mysql 导入导出大文件
查看>>
MySQL 导出数据
查看>>
mysql 将null转代为0
查看>>
mysql 常用
查看>>
MySQL 常用列类型
查看>>
mysql 常用命令
查看>>
Mysql 常见ALTER TABLE操作
查看>>
MySQL 常见的 9 种优化方法
查看>>