关于android:不一样角度带你了解-Flutter-中的滑动列表实现

4次阅读

共计 6070 个字符,预计需要花费 16 分钟才能阅读完成。

本篇次要帮忙分析了解 Flutter 里的列表和滑动的组成,用比拟通俗易懂的形式,从常见的 ListView到 NestedScrollView 的外部实现,帮忙你更好了解和使用 Flutter 里的滑动列表。

「本篇不是教你如何应用 API,而是一些日常开发中不常接触,然而很重要的内容」

Flutter 滑动列表

在 Flutter 里咱们常见的滑动列表场景,简略地说其实是由三局部组成:

  • Viewport:它是一个 MultiChildRenderObjectWidget 的控件,「它提供的是一个“视窗”的作用,也就是列表所在的可视区域大小;」
  • Scrollable「它次要通过对手势的解决来实现滑动成果」,比方 VerticalDragGestureRecognizer 和 HorizontalDragGestureRecognizer;
  • Sliver:精确来说应该是 RenderSliver,「它次要是用于在 Viewport 外面布局和渲染内容;」

以 ListView 为例,如上图所示是 ListView 滑动过程的变动,其中:

  • 绿色的 Viewport 就是咱们看到的列表窗口大小;
  • 紫色局部就是解决手势的 Scrollable,让黄色局部 SliverList 在 Viewport 里产生滑动;
  • 黄色的局部就是 SliverList,当咱们滑动时其实就是它在 Viewport 里的地位产生了变动;

理解完这个根底理念后,就能够晓得个别状况下 Viewport 和  Scrollable 的实现都是很通用的,所以个别在 「Flutter 里要实现不同的滑动列表,就是通过自定义和组合不同的 Sliver 来实现布局」

「精确说是实现 RenderSliver 的 performLayout 过程,通过 SliverConstraints 来失去对应的 SliverGeometry

所以在 Flutter 里:

  • ListView 应用的是 SliverFixedExtentList 或者  SliverList
  • GridView 应用的是 SliverGrid
  • PageView 应用的是 SliverFillViewport

当然这里有一个非凡的是 SingleChildScrollView,因为它是单个 child 的可滑动控件,它并没有应用 RenderSliver,而是间接自定义了一个 RenderObject(RenderBox),并且 「在 performLayout 时间接调整 child 的 offset 来达到滑动成果」

RenderSliver

咱们都晓得 Flutter 中的整体渲染流程是 Widget -> Element -> RenderObejct -> Layer 这样的过程,而 「Flutter 里的布局和绘制逻辑都在 RenderObejct

而事实上 RenderObejct 也能够分为两大根底子类:

  • RenderBox:咱们「罕用的布局控件都是基于 RenderBox」 来实现布局;
  • RenderSliver「次要用在 Viewport 里实现布局」,Viewport 里的直属 children 也须要是 RenderSliver;

那到这里你可能会有一个疑难:既然后面 SingleChildScrollView 里没有应用 RenderSliver,间接应用 RenderBox 也能够实现滑动,「为什么还要用 Viewport + RenderSliver 的形式来实现列表滑动?」

RenderBox

在 SingleChildScrollView 外部应用的是 RenderBox,那么在布局过程中自然而然会把整个 child 都进行布局和计算,绘制时次要也是通过 offset 和 clip 等来实现挪动成果,这样的实现当 child 比较复杂或者过长时,性能就会变差」

RenderSliver

RenderSliver 的实现绝对 RenderBox 就简单更多,后面介绍过 RenderSliver 就是通过 SliverConstraints 来失去一个 SliverGeometry,其中:

  • SliverConstraints 中有 remainingPaintExtent 能够用来示意残余的可绘制具体的大小;
  • SliverGeometry 里也有 scrollExtent(可滑动的间隔)、paintExtent(可绘制大小)、layoutExtent(布局大小范畴)、visible(是否须要绘制)等参数;

所以通过这部分参数,「在 Viewport 里能够实现动静治理,节俭资源,依据 SliverGeometry判断须要绘制多大区域的内容,还剩多少内容能够绘制,须要加载的布局是哪些等等。」

「简略地说就是能够实现“懒加载”,按需绘制,从而失去更晦涩的滑动体验。」

以 ListView 为例,如上图所示是一个高为 701 的 ListView,理论布局渲染之后,对于 SliverList 输入的 SliverGeometry 而言:

  • 设定里每个 item 的高度为 114;
  • scrollExtent 是 2353,也就是整体可滑动间隔等于 2353;
  • paintExtent 是 701,因为 ListView 的 Viewport 是 701,所以从 SliverConstraints 失去的 remainingPaintExtent 是 701,「所以默认只须要绘制和布局高度为 701 的局部;」(因为默认 paintExtent = layoutExtent)
  • 对 item 多出的蓝色 8-9 局部,这是因为在  SliverConstraints 内会有一个叫 remainingCacheExtent 的参数,它示意了须要提前缓存的布局区域,也就是“预布局”的区域,这个区域默认大小是 「defaultCacheExtent= 250.0;」

ListView 高度为 701,defaultCacheExtent 为默认的 250,也就是失去「第一次须要布局到底部的间隔其实为 951」,依照每个 item 高度是 114,那么其实是有 8.3 个 item 高度,取整数也就是 9 个 item,最终失去整体须要解决的区域大小为 114 * 9 = 1026,在 SliverList 外部就是 endScrollOffset 参数」

所以依据以上状况,ListView 会输入一个 paintExtent 为 701,cacheExtent 为 1026 的  SliverGeometry

从这个例子能够看出,RenderSliver 在实现可滑动列表的开销和逻辑上,会比间接应用 RenderBox 好和灵便很多」,同时也是为什么 Viewport 里须要应用 RenderSliver 而不是 RenderBox 的起因。

⚠️留神,这里比拟容易有一个误区,那就是 ListView 是由 Viewport + Scrollable 和一个RenderSliver 组成,所以在 ListView 里只会有一个 RenderSliver 而不是多个」,想应用多个  RenderSliver 须要应用 CustomScrollView

最初顺便聊下 CustomScrollView,事实上就是一个「凋谢了可自定义配置 RenderSliver 数组的滑动控件」,例如:

  • 通过利用 SliverList + SliverGrid 就能够搭配出多样化的滑动列表;
  • 通过 CupertinoSliverRefreshControl +  SliverList 实现相似 iOS 原生的下拉刷新列表;

其余可用的内置 Sliver 还有:SliverPaddingSliverFillRemainingSliverFillViewportSliverPersistentHeaderSliverAppbar 等等。

NestedScrollView

为什么会把 NestedScrollView 独自拿出来说呢?这是因为 NestedScrollView 和后面介绍的滑动列表实现不大一样。

外部组成

如上图所示,NestedScrollView 外部次要是通过继承 CustomScrollView,而后自定义一个 NestedScrollViewViewport 来实现联动的成果。

那这有什么特地的呢?如下代码所示,这是应用 NestedScrollView 罕用的模式,那有看出什么特地的中央了吗?

代码里 NestedScrollView 的 body 嵌套的是 ListView,后面咱们介绍了 ListView 自身就是 Viewport + Scrollable + SliverList 组合,而 NestedScrollView 自身也有 NestedScrollViewViewport

「所以 NestedScrollView 的实现实质上其实就是 Viewport 嵌套 Viewport,会有两个 Scrollable 的存在」,并且嵌套的  ListView 是被放在了 NestedScrollView 的 Sliver外面,大抵如下图所示。

这外面有几个要害的对象,其中:

  • SliverFillRemaining:用于充斥 Viewport 的残余空间,在  NestedScrollView 外面就是充斥 header 之外的残余空间;
  • NestedScrollViewViewport:在原 Viewport 的根底上减少了一个 SliverOverlapAbsorberHandle 参数,SliverOverlapAbsorberHandle 自身是一个 ChangeNotifier,次要是用来当 markNeedsLayout 时对外发出通知,比方对 header 局部;

所以 NestedScrollView 实质上两个 Viewport 之间的嵌套,那他们之间是滑动关系是如何解决的?「这就要说到 NestedScrollView 里的 _NestedScrollCoordinator 对象。」

_NestedScrollCoordinator

_NestedScrollCoordinator 的实现比较复杂,简略地说 _NestedScrollCoordinator 外部创立了两个 _NestedScrollController

  • _outerController:属于 _NestedScrollViewCustomScrollView 的 controller,也就是它本人 controller;
  • _innerController:属于 body 的 controller;

在 ListView 的父类 ScrollView 外部,默认状况下应用的就是 PrimaryScrollController.of(context) 这个 controller,因为 PrimaryScrollController 是一个 InheritedWidget

而整个联动滑动的流程,次要就是 _NestedScrollCoordinator 里和它创立的两个 _NestedScrollController 有关系:

  • _NestedScrollController 的次要作用就是应用 _NestedScrollPosition 来替换 ScrollPosition
  • _NestedScrollCoordinator 将 _outer 和 _inner 两个 _NestedScrollController 组合起来(_outer 和 _inner 别离被利用到 NestedScrollView 和 body);
  • _NestedScrollPosition 外部将 Drag 等手势操作传递回 _NestedScrollCoordinator里。
  • 最初在 _NestedScrollCoordinator 的 drag 和 applyUserOffset 等办法里进行内外滚动的调配;

SliverPersistentHeader

理解完 NestedScrollView 的布局和联动实现之外,最初简略介绍一下  SliverPersistentHeader,因为常常在  NestedScrollView 里应用的  SliverAppBar,实质上 SliverAppBar 的实现靠的就是 SliverPersistentHeader

SliverPersistentHeader 次要是具备 floating 和  pinned 两个属性,它们的区别次要在于应用了不同的 RenderSliver 实现,而「最终不同的中央其实就是输入 SliverGeometry 的不同」

以第一个 _SliverFloatingPinnedPersistentHeader 和最初一个 _SliverScrollingPersistentHeader 之间的比照为例子,如下代码所示,在须要 floating 和  pinned 的 Sliver上,能够看到 paintExtent 和 layoutExtent 都有一个最小值。

「所以 Sliver 被固定住的原理,其实就是 Viewport 失去了它的 paintExtent 和 layoutExtent 并不为 0,所以会持续为这个 Sliver 绘制对应区域的内容。」

最初须要留神的是,「当你应用 SliverPersistentHeader 去固定住头部的时候,作为 body的列表是不晓得顶部有个固定区域。」 所以如果这时候不额定做一些解决,那么对于 body 而言,它的 paintOrigin 还是从最顶部开始而不是固定区域的下方。

如上动图所示,能够看到 item0 并没有在橙色区域进行滑动,而是持续往上滑动,这就是因为作为 body 的列表不晓得顶部有固定区域。

这时候就能够通过应用 SliverOverlapAbsorber + SliverOverlapInjector 的组合来解决这个问题:

  • 在 SliverPersistentHeader 的外层嵌套一个 SliverOverlapAbsorber 用于排汇 SliverPersistentHeader 的高度;
  • 应用 SliverOverlapInjector 将这个高度配置到 body 列表中,让列表晓得顶部存在一个固定高度的区域;

Android 高级开发零碎进阶笔记、最新面试温习笔记 PDF,我的 GitHub

文末

您的点赞珍藏就是对我最大的激励!
欢送关注我,分享 Android 干货,交换 Android 技术。
对文章有何见解,或者有何技术问题,欢送在评论区一起留言探讨!

正文完
 0