扫二维码与项目经理沟通
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流
ListView的基础创建使用有三种方式:
创新互联是专业的绥棱网站建设公司,绥棱接单;提供成都做网站、网站建设,网页设计,网站设计,建网站,PHP网站建设等专业做网站服务;采用PHP框架,可快速的进行绥棱网站开发网页制作和功能扩展;专业做搜索引擎喜爱的网站,专业的做网站团队,希望更多企业前来合作!
通过默认构造函数来创建列表,应用场景 = 短列表
这种方式创建的列表存在一个问题:对于那些长列表或者需要较昂贵渲染开销的子组件,即使还没有出现在屏幕中但仍然会被ListView所创建,这将是一项较大的开销,使用不当可能引起性能问题甚至卡顿。
长列表
列表子项之间需要分割线
ListView的进阶使用主要包括:下拉刷新 上拉加载
在Flutter中,ListView结合RefreshIndicator组件实现下拉刷新
通过包裹一层RefreshIndicator,自定义onRefresh回调方法实现
方式有两种:
通过ListView.controller属性可以判断ListView是否滑动到了底部,再进行上拉加载
NotificationListener是一个Widget,可监听子Widget发出的Notification
ListView在滑动时中会发出ScrollNotification类型的通知,可通过监听该通知得到ListView的滑动状态,判断是否滑动到了底部,从而进行上拉加载
NotificationListener有一个onNotification属性,定义了监听的回调方法,通过它来处理加载更多逻辑
不定期分享关于 安卓开发 的干货,追求 短、平、快 ,但 却不缺深度 。
GridView可以构建一个二维网格列表
默认构造函数如下:
我们可以看到,GridView和ListView的大多数参数都是相同的,它们的含义也都相同的。我们唯一需要关注的是gridDelegate参数,类型是SliverGridDelegate,它的作用是控制GridView子组件如何排列(layout)。
SliverGridDelegate 是一个抽象类,定义了GridView Layout相关接口,子类需要通过实现它们来实现具体的布局算法。Flutter中提供了两个 SliverGridDelegate 的子类 SliverGridDelegateWithFixedCrossAxisCount 和 SliverGridDelegateWithMaxCrossAxisExtent
SliverGridDelegateWithFixedCrossAxisCount
该子类实现了一个横轴为固定数量子元素的layout算法,其构造函数为:
可以发现,子元素的大小是通过crossAxisCount和childAspectRatio两个参数共同决定的。注意,这里的子元素指的是子组件的最大显示空间,注意确保子组件的实际大小不要超出子元素的空间。
示例
SliverGridDelegateWithMaxCrossAxisExtent
该子类实现了一个横轴子元素为固定最大长度的layout算法,其构造函数为
maxCrossAxisExtent为子元素在横轴上的最大长度,之所以是“最大”长度,是因为横轴方向每个子元素的长度仍然是等分的,举个例子,如果ViewPort的横轴长度是450,那么当maxCrossAxisExtent的值在区间[450/4,450/3)内的话,子元素最终实际长度都为112.5,而childAspectRatio所指的子元素横轴和主轴的长度比为最终的长度比。其它参数和SliverGridDelegateWithFixedCrossAxisCount相同。
示例
GridView.count构造函数内部使用了SliverGridDelegateWithFixedCrossAxisCount,我们通过它可以快速的创建横轴固定数量子元素的GridView
示例
GridView.extent构造函数内部使用了SliverGridDelegateWithMaxCrossAxisExtent,我们通过它可以快速的创建横轴子元素为固定最大长度的的GridView
示例
上面我们介绍的GridView都需要一个widget数组作为其子元素,这些方式都会提前将所有子widget都构建好,所以只适用于子widget数量比较少时,当子widget比较多时,我们可以通过GridView.builder来动态创建子widget。GridView.builder 必须指定的参数有两个:
示例
假设我们需要从一个异步数据源(如网络)分批获取一些Icon,然后用GridView来展示:
_retrieveIcons():在此方法中我们通过Future.delayed来模拟从异步数据源获取数据,每次获取数据需要200毫秒,获取成功后将新数据添加到_icons,然后调用setState重新构建。
在 itemBuilder 中,如果显示到最后一个时,判断是否需要继续获取数据,然后返回一个Icon。
GridView.builder 源码定义如下:
它主要要传两个参数 gridDelegate 和 childrenDelegate ,gridDelegate是SliverGridDelegate类型,SliverGridDelegate它有两个子类,就是我们上文中说的 SliverGridDelegateWithFixedCrossAxisCount 和 SliverGridDelegateWithMaxCrossAxisExtent ,childrenDelegate 是SliverChildDelegate类型,SliverChildDelegate也有两个子类 SliverChildBuilderDelegate 和 SliverChildListDelegate ,一个是通过Builder创建Item,一个是指定所有item。和ListView 很类似
示例
补充知识点:
GridView中的Sliver是SliverGrid
ListView 和 GridView 都是继承于BoxScrollView,BoxScrollView中存在一个抽象方法buildChildLayout,这个buildChildLayout方法是在提供Sliver,GridView中关于buildChildLayout实现如下:
讲道理我起的好长的名字啊,不过文如上题,搜索到这里的兄弟应该都知道我说的是啥情况,正好
~~
我这个方案可能有点笨拙TT,不过自测有效,有其它想法的老哥希望可以帮忙指点一下~
下面进入正题
点进源码里面看,可以发现他直接继承了StatelessWidget,那我们就直接看看build方法
可以看到,这里直接返回一个scrollable或者一个子节点是scrollable的InheritedWidget
scrollable是一个StatefulWidget,那我们就看看它的state
首先scrollable持有一个scrollposition对象,是通过其scrollcontroller构建的
在其state的setCanDrag方法中,对其拖动设置了一系列的监听
这里就可以看出来,当拖动触发时,就会通过当前scrollable的position生成一个Drag/Hold对象,并调用相应的方法 这个position有几个子类,我们先随便看一个实现
可以看到生成了一个ScrollDragController对象,当手势拖动而调用这个对象的update方法时
可以看到直接调用其委托对象的applyUserOffset方法进行偏移,而这个委托对象根据刚才的drag方法可以得知正是我们scrollable中的position
最后,由position通知其scrollcontext,也就是之前的scrollable进行滑动
具体的滑动流程这里就不细说了,我们只是要知道这个事件是怎么传递的就好了,有兴趣的老哥可以自行分析
NestedScrollView是一个statefulwidget,那我们就先看看它的build方法
先忽略其他奇奇怪怪的方法,我们发现在我们body的外面,包裹了一层PrimaryScrollController,同时它还持有innerController,这个innerController暂时先不管它是啥
还记不记得在最开始ScrollView的build方法中,生成Scrollable的时候,我们已经见过这个PrimaryScrollController了,再回顾一下
再看看PrimaryScrollController.of(context)
可以看到,在生成scrollable的时候,在primary = true的情况下是会向上查找的,看看有没有PrimaryScrollController,如果有的话,scrollable使用的controller实际就是nestedscrollview中的innerController了
而之前看过了,scrollable中的position就是scrollcontroller来生成的,那么在这种情况下:
实际上是生成了_NestedScrollPosition并返回给了body中的scrollable
构造方法中有一个参数coordinator 暂时先不管
好了,下面我们在回头看刚才NestedScrollView的build方法,实际上是生成了一个_NestedScrollViewCustomScrollView,继承自大名鼎鼎的CustomScrollView,它当然也是scrollview啦,而我们传给它的controller也是一个_NestedScrollController,不过叫做_outerController,和body中的不是同一个罢了,那么自然这个父scrollview的position也是_NestedScrollPosition。
下面我们按照之前的逻辑,当拖动开始时,就会调用position.drag方法
可以看到,实际上吧方法交给了我们之前多次见到的coordinator来完成,那我们就简单看一下吧
这里可以看到,他把返回的ScrollDragController的委托者设成了自己
那么自然在拖动的时候,调用的就是coordinator的applyUseroffset方法了 我们分析一下
可以看到,在需要子列表滚动时,是对innerPositions中的所有position调用滑动方法的
而这innerPositions中的position是怎么来的呢?跟踪一下可以发现是在调用NestedScrollController的attach时添加进来的,如下
因为之前我们看到过,子scrollable中的controller就是这个NestedScrollController,所以在updateopsition时会把旧的detach调,把新生成的position attach进来
另外,在dispose中也会detach
由此我们就知道啦,因为开启了缓存后就不会调用划出屏幕的页面的dispose,自然所有子scrollable的position都存在nestedScrollController里面了,当发生滑动时,遍历调用positions数组,就导致屏幕外的列表也跟着滑动了~
既然开启了缓存,手动dispose肯定是没啥意义的,实际上我们只要在页面切换过后把未显示的position 给detach掉就好了。
然鹅,因为flutter不支持反射,子布局传递的position我们拿不到,nestedScrollController我们也不能直接拿到=。=
不过有一个对象我们之前见到过,scrollable就是通过他获取controller的,而position则是传给了获取到的controller 就是PrimaryScrollController了,所以我打算在中间第三者插足,对传递Position的PrimaryScrollController进行Hook
在使用的时候把子列表添加进去,并设置对应的GlobalKey。
然后监听Tab切换
以上是我的方案,有问题的话还希望老哥帮忙指正,也希望有其他思路的老哥指点一下~~
上一下Github项目地址 用Flutter写的WanAndroid 其中用到了这个方案
= =
3
Flutter 中有两种布局模型:
基于 RenderBox 的盒模型布局。
基于 Sliver ( RenderSliver ) 按需加载列表布局。
通常可滚动组件的子组件可能会非常多、占用的总高度也会非常大;如果要一次性将子组件全部构建出将会非常昂贵!为此,Flutter中提出一个Sliver(中文为“薄片”的意思)概念,Sliver 可以包含一个或多个子组件。Sliver 的主要作用是配合:加载子组件并确定每一个子组件的布局和绘制信息,如果 Sliver 可以包含多个子组件时,通常会实现按需加载模型。
只有当 Sliver 出现在视口中时才会去构建它,这种模型也称为“基于Sliver的列表按需加载模型”。可滚动组件中有很多都支持基于Sliver的按需加载模型,如 ListView 、 GridView ,但是也有不支持该模型的,如 SingleChildScrollView 。
Flutter 中的可滚动主要由三个角色组成: Scrollable 、 Viewport 和 Sliver :
具体布局过程:
比如有一个 ListView,大小撑满屏幕,假设它有 100 个列表项(都是RenderBox)且每个列表项高度相同,结构如图6-1所示:
图中白色区域为设备屏幕,也是 Scrollable 、 Viewport 和 Sliver 所占用的空间,三者所占用的空间重合,父子关系为:Sliver 父组件为 Viewport,Viewport的 父组件为 Scrollable 。注意ListView 中只有一个 Sliver,在 Sliver 中实现了子组件的按需加载。
其中顶部和底部灰色的区域为 cacheExtent,它表示预渲染的高度,需要注意这是在可视区域之外,如果 RenderBox 进入这个区域内,即使它还未显示在屏幕上,也是要先进行构建的,预渲染是为了后面进入 Viewport 的时候更丝滑。cacheExtent 的默认值是 250,在构建可滚动列表时我们可以指定这个值,这个值最终会传给 Viewport。
用于处理滑动手势,确定滑动偏移,滑动偏移变化时构建 Viewport,我们看一下其关键的属性:
在可滚动组件的坐标描述中,通常将滚动方向称为主轴,非滚动方向称为纵轴。由于可滚动组件的默认方向一般都是沿垂直方向,所以默认情况下主轴就是指垂直方向,水平方向同理。
Viewport 比较简单,用于渲染当前视口中需要显示 Sliver。
需要注意的是:
Sliver 主要作用是对子组件进行构建和布局,比如 ListView 的 Sliver 需要实现子组件(列表项)按需加载功能,只有当列表项进入预渲染区域时才会去对它进行构建和布局、渲染。
Sliver 对应的渲染对象类型是 RenderSliver,RenderSliver 和 RenderBox 的相同点是都继承自 RenderObject 类,不同点是在布局的时候约束信息不同。RenderBox 在布局时父组件传递给它的约束信息对应的是 BoxConstraints ,只包含最大宽高的约束;而 RenderSliver 在布局时父组件(列表)传递给它的约束是对应的是 SliverConstraints 。关于 Sliver 的布局协议,我们将在本章最后一节中介绍。
几乎所有的可滚动组件在构造时都能指定 scrollDirection (滑动的主轴)、 reverse (滑动方向是否反向)、 controller 、 physics 、 cacheExtent ,这些属性最终会透传给对应的 Scrollable 和 Viewport,这些属性我们可以认为是可滚动组件的通用属性,后续再介绍具体的可滚动组件时将不再赘述。
可滚动组件都有一个 controller 属性,通过该属性我们可以指定一个 ScrollController 来控制可滚动组件的滚动,比如可以通过ScrollController来同步多个组件的滑动联动。由于 ScrollController 是需要结合可滚动组件一起工作,所以本章中,我们会在介绍完 ListView 后详细介绍 ScrollController。
Scrollbar是一个Material风格的滚动指示器(滚动条),如果要给可滚动组件添加滚动条,只需将Scrollbar作为可滚动组件的任意一个父级组件即可,如:
Scrollbar 和 CupertinoScrollbar 都是通过监听滚动通知来确定滚动条位置的。关于的滚动通知的详细内容我们将在本章最后一节中专门介绍。
CupertinoScrollbar是 iOS 风格的滚动条,如果你使用的是Scrollbar,那么在iOS平台它会自动切换为CupertinoScrollbar
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流