准确说是完成
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
还有:SliverPadding
、SliverFillRemaining
、SliverFillViewport
、SliverPersistentHeader
、SliverAppbar
等等。
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
等方法里进行内外滚动的分配;
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)
最后
分享一份工作1到5年以上的Android程序员架构进阶学习路线体系,希望能对那些还在从事Android开发却还不知道如何去提升自己的,还处于迷茫的朋友!
-
阿里P7级Android架构师技术脑图;查漏补缺,体系化深入学习提升
-
**全套体系化高级架构视频;**七大主流技术模块,视频+源码+笔记
有任何问题,欢迎广大网友一起来交流
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
50117670)]
有任何问题,欢迎广大网友一起来交流
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!