
正文
flutter动态控制滚动 flutter超长文本自动滚动
提示:扫一扫查出行【扫一扫了解最新限行尾号】
复制提示
【Flutter】ListView、GridView高度自适应并随页面滚动
Cannot hit test a render box that has never been laid out.
当ListView、GridView作为Wrap、Column等组件的子组件时,若外部没有设置高度,则会产生此报错。
解决放案:为ListView、GridView设置属性 shrinkWrap: true,
刷新后,报错消失,但是发现其只可内部滚动。
至此,需求达成。
参考
相关问答
Q1: Flutter了解之可滑动组件
Flutter官方并没有对Widget进行官方分类,对其分类主要是为了对Widget进行功能区分。
当组件内容超过当前显示窗口时,如果没有特殊处理,Flutter则会提示Overflow错误。为此,Flutter提供了多种可滚动组件用于显示列表和长布局。
在可滚动组件的坐标描述中,通常将滚动方向称为主轴,非滚动方向称为纵轴。由于可滚动组件的默认方向一般都是沿垂直方向,所以默认情况下主轴就是指垂直方向,水平方向同理。
通常可滚动组件的子组件可能会非常多、占用的总高度也会非常大;如果要一次性将子组件全部构建出将会非常昂贵!为此,Flutter中提出一个Sliver(中文为“薄片”的意思)概念,如果一个可滚动组件支持Sliver模型,那么该滚动可以将子组件分成好多个“薄片”(Sliver),只有当Sliver出现在视口中时才会去构建它,这种模型也称为“基于Sliver的延迟构建模型”。
可滚动组件中有很多都支持基于Sliver的延迟构建模型,如ListView、GridView,但是也有不支持该模型的,如SingleChildScrollView。
在很多布局系统中都有ViewPort的概念,在Flutter中,术语ViewPort(视口),如无特别说明,则是指一个Widget的实际显示区域。例如: 一个ListView的显示区域高度是800像素,虽然其列表项总高度可能远远超过800像素,但是其ViewPort仍然是800像素。
可滚动组件都直接或间接包含一个Scrollable组件
如果要给可滚动组件添加滚动条,只需将Scrollbar作为可滚动组件的任意一个父级组件。
沿一个方向线性排布所有子组件。支持基于Sliver的延迟构建模型。
ListView高度边界无法确定时会异常。
默认构造函数有一个children参数,它接受一个Widget列表。
实际上通过此方式创建的ListView和使用SingleChildScrollView+Column的方式没有本质的区别。
适合只有少量的子组件的情况,因为这种方式需要将所有children都提前创建好(这需要做大量工作),而不是等到子widget真正显示的时候再创建,也就是说通过默认构造函数构建的ListView没有应用基于Sliver的懒加载模型。
例
例
例(水平滚动)
适合列表项比较多(或者无限)的情况,因为只有当子组件真正显示的时候才会被创建,也就说通过该构造函数创建的ListView是支持基于Sliver的懒加载模型的。
例
例
例(不同类型的item)
ListView.separated可以在生成的列表项之间添加一个分割组件,它比ListView.builder多了一个separatorBuilder参数,该参数是一个分割组件生成器。
例
例
类似于Android中的ScrollView,它只能接收一个子组件。
通常内容不会超过屏幕太多时使用SingleChildScrollView,这是因为它不支持基于Sliver的延迟实例化模型,所以如果预计视口可能包含超出屏幕尺寸太多的内容时,那么使用SingleChildScrollView将会非常昂贵(性能差),此时应该使用一些支持Sliver延迟加载的可滚动组件,如ListView。
例(将大写字母A-Z沿垂直方向显示)
一个二维网格列表
GridView和ListView的大多数参数都是相同的。
横轴为固定数量子元素。
GridView.count构造函数内部使用了SliverGridDelegateWithFixedCrossAxisCount。
例
例(GridView.count)
该子类实现了一个横轴子元素为固定最大长度的layout算法
例
当子widget比较多时,可以通过GridView.builder来动态创建子widget。
GridView.builder 必须指定的参数有两个,其中itemBuilder为子widget构建器。
例
举个例子,假设有一个页面,顶部需要一个GridView,底部需要一个ListView,而要求整个页面的滑动效果是统一的,即它们看起来是一个整体。如果使用GridView+ListView来实现的话,就不能保证一致的滑动效果,因为它们的滚动效果是分离的。
所以这时就需要一个"胶水",把这些彼此独立的可滚动组件"粘"起来,而CustomScrollView的功能就相当于“胶水”。
Sliver有细片、薄片之意,在Flutter中Sliver通常指可滚动组件子元素。在CustomScrollView中,需要粘起来的可滚动组件就是CustomScrollView的Sliver了,如果直接将ListView、GridView作为CustomScrollView是不行的,因为它们本身是可滚动组件而并不是Sliver。
因此,为了能让可滚动组件能和CustomScrollView配合使用,Flutter提供了一些可滚动组件的Sliver版,如SliverList、SliverGrid等。
实际上Sliver版的可滚动组件和非Sliver版的可滚动组件最大的区别就是前者不包含滚动模型(自身不能再滚动),而后者包含滚动模型 ,也正因如此,CustomScrollView才可以将多个Sliver"粘"在一起,这些Sliver共用CustomScrollView的Scrollable,所以最终才实现了统一的滑动效果。
例(SliverList)
例(SliverGrid)
可以用ScrollController来控制可滚动组件的滚动位置
例
滚动位置恢复
ScrollPosition
ScrollController控制原理
滚动监听
例
Q2: Flutter 之 可滚动组件 -- 理论知识点(十四)
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
Q3: Flutter 之 滚动监听及控制(十九)
ListView、GridView的组件控制器是ScrollController,我们可以通过它来获取视图的滚动信息,并且可以调用里面的方法来更新视图的滚动位置。
ScrollController构造函数如下:
ScrollController常用的属性和方法:
ScrollController间接继承自Listenable,我们可以根据ScrollController来监听滚动事件,如:
示例
我们创建一个ListView,当滚动位置发生变化时,我们先打印出当前滚动位置,然后判断当前位置是否超过1000像素,如果超过则在屏幕右下角显示一个“返回顶部”的按钮,该按钮点击后可以使ListView恢复到初始位置;如果没有超过1000像素,则隐藏“返回顶部”按钮。
ScrollPosition是用来保存可滚动组件的滚动位置的。一个ScrollController对象可以同时被多个可滚动组件使用,ScrollController会为每一个可滚动组件创建一个ScrollPosition对象,这些ScrollPosition保存在ScrollController的positions属性中(ListScrollPosition)。ScrollPosition是真正保存滑动位置信息的对象,offset只是一个便捷属性
一个 ScrollController 虽然可以对应多个可滚动组件,但是有一些操作,如读取滚动位置 offset ,则需要一对一!但是我们仍然可以在一对多的情况下,通过其它方法读取滚动位置,举个例子,假设一个 ScrollController 同时被两个可滚动组件使用,那么我们可以通过如下方式分别读取他们的滚动位置:
我们可以通过 controller.positions.length 来确定 controller 被几个可滚动组件使用。
ScrollPosition 有两个常用方法: animateTo() 和 jumpTo() ,它们是真正来控制跳转滚动位置的方法, ScrollController 的这两个同名方法,内部最终都会调用 ScrollPosition 的。
我们来介绍一下 ScrollController 的另外三个方法:
当 ScrollController 和可滚动组件关联时,可滚动组件首先会调用 ScrollController 的 createScrollPosition() 方法来创建一个 ScrollPosition 来存储滚动位置信息,接着,可滚动组件会调用 attach() 方法,将创建的 ScrollPosition 添加到 ScrollController 的 positions 属性中,这一步称为“注册位置”,只有注册后 animateTo() 和 jumpTo() 才可以被调用。
当可滚动组件销毁时,会调用 ScrollController 的 detach() 方法,将其 ScrollPosition 对象从 ScrollController 的 positions 属性中移除,这一步称为“注销位置”,注销后 animateTo() 和 jumpTo() 将不能再被调用。
需要注意的是, ScrollController 的 animateTo() 和 jumpTo() 内部会调用所有 ScrollPosition 的 animateTo() 和 jumpTo() ,以实现所有和该 ScrollController 关联的可滚动组件都滚动到指定的位置。
Flutter Widget树中子Widget可以通过发送通知(Notification)与父(包括祖先)Widget通信。父级组件可以通过NotificationListener组件来监听自己关注的通知
可滚动组件在滚动时会发送 ScrollNotification 类型的通知, ScrollBar 正是通过监听滚动通知来实现的。通过 NotificationListener 监听滚动事件和通过 ScrollController 有两个主要的不同:
示例
下面,我们监听ListView的滚动通知,然后显示当前滚动进度百分比:
在接收到滚动事件时,参数类型为ScrollNotification,它包括一个metrics属性,它的类型是ScrollMetrics,该属性包含当前ViewPort及滚动位置等信息:
Q4: Flutter 可滚动组件 之 GridView (十七)
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实现如下:
Q5: Flutter:手把手教你使用滚动型列表组件:ListView
ListView的基础创建使用有三种方式:
通过默认构造函数来创建列表,应用场景 = 短列表
这种方式创建的列表存在一个问题:对于那些长列表或者需要较昂贵渲染开销的子组件,即使还没有出现在屏幕中但仍然会被ListView所创建,这将是一项较大的开销,使用不当可能引起性能问题甚至卡顿。
长列表
列表子项之间需要分割线
ListView的进阶使用主要包括:下拉刷新 上拉加载
在Flutter中,ListView结合RefreshIndicator组件实现下拉刷新
通过包裹一层RefreshIndicator,自定义onRefresh回调方法实现
方式有两种:
通过ListView.controller属性可以判断ListView是否滑动到了底部,再进行上拉加载
NotificationListener是一个Widget,可监听子Widget发出的Notification
ListView在滑动时中会发出ScrollNotification类型的通知,可通过监听该通知得到ListView的滑动状态,判断是否滑动到了底部,从而进行上拉加载
NotificationListener有一个onNotification属性,定义了监听的回调方法,通过它来处理加载更多逻辑
不定期分享关于 安卓开发 的干货,追求 短、平、快 ,但 却不缺深度 。
关于flutter动态控制滚动和flutter超长文本自动滚动的介绍到此就结束了,不知道你从中找到你需要的信息了吗 ?如果你还想了解更多这方面的信息,记得收藏关注本站。






