Flutter之可滚动组件实例详解

目录
  • 正文
  • Scrollable
    • 主轴和纵轴
  • Viewport
  • Sliver
  • 可滚动组件的通用配置
  • ScrollController
  • 子节点缓存
  • Scrollbar
  • 总结

正文

当内容超过显示视口(ViewPort)时,如果没有特殊处理,Flutter则会提示Overflow错误。为此,Flutter提供了多种可滚动widget(Scrollable Widget)用于显示列表和长布局。

Flutter中有两种布局模型:

  • 基于 RenderBox 的盒模型布局。
  • 基于 Sliver ( RenderSliver ) 按需加载列表布局。

通常可滚动组件的子组件可能会非常多、占用的总高度也会非常大;如果要一次性将子组件全部构建出将会非常昂贵!为此,Flutter中提出一个Sliver(中文为“薄片”的意思)概念,Sliver 可以包含一个或多个子组件。Sliver 的主要作用是配合:加载子组件并确定每一个子组件的布局和绘制信息,如果 Sliver 可以包含多个子组件时,通常会实现按需加载模型。

只有当Sliver 出现在视口中时才会去构建它,这种模型也称为“基于Sliver的列表按需加载模型”。可滚动组件中有很多都支持基于Sliver的按需加载模型,如ListViewGridView,但是也有不支持该模型的,如SingleChildScrollView

Flutter 中的可滚动主要由三个角色组成:Scrollable、Viewport 和 Sliver:

  • Scrollable :用于处理滑动手势,确定滑动偏移,滑动偏移变化时构建 Viewport 。
  • Viewport:显示的视窗,即列表的可视区域;
  • Sliver:视窗里显示的元素。

具体布局过程:

  • Scrollable 监听到用户滑动行为后,根据最新的滑动偏移构建 Viewport 。
  • Viewport 将当前视口信息和配置信息通过 SliverConstraints 传递给 Sliver。
  • Sliver 中对子组件(RenderBox)按需进行构建和布局,然后确认自身的位置、绘制等信息,保存在 geometry 中(一个 SliverGeometry 类型的对象)

比如有一个 ListView,大小撑满屏幕,假设它有 100 个列表项(都是RenderBox)且每个列表项高度相同,结构如下:

图中白色区域为设备屏幕,也是 Scrollable 、 Viewport 和 Sliver 所占用的空间,三者所占用的空间重合,父子关系为:Sliver 父组件为 Viewport,Viewport的 父组件为 Scrollable 。注意ListView 中只有一个 Sliver,在 Sliver 中实现了子组件的按需加载。

其中顶部和底部灰色的区域为 cacheExtent,它表示预渲染的高度,需要注意这是在可视区域之外,如果 RenderBox 进入这个区域内,即使它还未显示在屏幕上,也是要先进行构建的,预渲染是为了后面进入 Viewport 的时候更丝滑。cacheExtent 的默认值是 250,在构建可滚动列表时我们可以指定这个值,这个值最终会传给 Viewport。

Scrollable

用于处理滑动手势,确定滑动偏移,滑动偏移变化时构建 Viewport,我们看一下其关键的属性:

Scrollable({
  ...
  this.axisDirection = AxisDirection.down,
  this.controller,
  this.physics,
  required this.viewportBuilder, //后面介绍
})
  • axisDirection 滚动方向。
  • physics:此属性接受一个ScrollPhysics类型的对象,它决定可滚动组件如何响应用户操作,比如用户滑动完抬起手指后,继续执行动画;或者滑动到边界时,如何显示。默认情况下,Flutter会根据具体平台分别使用不同的ScrollPhysics对象,应用不同的显示效果,如当滑动到边界时,继续拖动的话,在 iOS 上会出现弹性效果,而在 Android 上会出现微光效果。如果你想在所有平台下使用同一种效果,可以显式指定一个固定的ScrollPhysics,Flutter SDK中包含了两个ScrollPhysics的子类,他们可以直接使用:
AlwaysScrollableScrollPhysics:总是可以滑动
NeverScrollableScrollPhysics:禁止滚动
BouncingScrollPhysics :内容超过一屏 上拉有回弹效果
ClampingScrollPhysics :包裹内容 不会有回弹
  • controller:此属性接受一个ScrollController对象。ScrollController的主要作用是控制滚动位置和监听滚动事件。默认情况下,Widget树中会有一个默认的PrimaryScrollController,如果子树中的可滚动组件没有显式的指定controller,并且primary属性值为true时(默认就为true),可滚动组件会使用这个默认的PrimaryScrollController。这种机制带来的好处是父组件可以控制子树中可滚动组件的滚动行为,例如,Scaffold正是使用这种机制在iOS中实现了点击导航栏回到顶部的功能。
  • viewportBuilder:构建 Viewport 的回调。当用户滑动时,Scrollable 会调用此回调构建新的 Viewport,同时传递一个 ViewportOffset 类型的 offset 参数,该参数描述 Viewport 应该显示那一部分内容。注意重新构建 Viewport 并不是一个昂贵的操作,因为 Viewport 本身也是 Widget,只是配置信息,Viewport 变化时对应的 RenderViewport 会更新信息,并不会随着 Widget 进行重新构建。

主轴和纵轴

在可滚动组件的坐标描述中,通常将滚动方向称为主轴,非滚动方向称为纵轴。由于可滚动组件的默认方向一般都是沿垂直方向,所以默认情况下主轴就是指垂直方向,水平方向同理。

Viewport

Viewport 比较简单,用于渲染当前视口中需要显示 Sliver。

Viewport({
  Key? key,
  this.axisDirection = AxisDirection.down,
  this.crossAxisDirection,
  this.anchor = 0.0,
  required ViewportOffset offset, // 用户的滚动偏移
  // 类型为Key,表示从什么地方开始绘制,默认是第一个元素
  this.center,
  this.cacheExtent, // 预渲染区域
  //该参数用于配合解释cacheExtent的含义,也可以为主轴长度的乘数
  this.cacheExtentStyle = CacheExtentStyle.pixel,
  this.clipBehavior = Clip.hardEdge,
  List<Widget> slivers = const <Widget>[], // 需要显示的 Sliver 列表
})

需要注意的是:

  • offset:该参数为Scrollabel 构建 Viewport 时传入,它描述了 Viewport 应该显示那一部分内容。
  • cacheExtent 和 cacheExtentStyle:CacheExtentStyle 是一个枚举,有 pixel 和 viewport 两个取值。当 cacheExtentStyle 值为 pixel 时,cacheExtent 的值为预渲染区域的具体像素长度;当值为 viewport 时,cacheExtent 的值是一个乘数,表示有几个 viewport 的长度,最终的预渲染区域的像素长度为:cacheExtent * viewport 的积, 这在每一个列表项都占满整个 Viewport 时比较实用,这时 cacheExtent 的值就表示前后各缓存几个页面。

Sliver

Sliver 主要作用是对子组件进行构建和布局,比如 ListView 的 Sliver 需要实现子组件(列表项)按需加载功能,只有当列表项进入预渲染区域时才会去对它进行构建和布局、渲染。

Sliver 对应的渲染对象类型是 RenderSliver,RenderSliver 和 RenderBox 的相同点是都继承自 RenderObject 类,不同点是在布局的时候约束信息不同。RenderBox 在布局时父组件传递给它的约束信息对应的是 BoxConstraints,只包含最大宽高的约束;而 RenderSliver 在布局时父组件(列表)传递给它的约束是对应的是 SliverConstraints。

可滚动组件的通用配置

几乎所有的可滚动组件在构造时都能指定 scrollDirection(滑动的主轴)、reverse(滑动方向是否反向)、controller、physics 、cacheExtent ,这些属性最终会透传给对应的 Scrollable 和 Viewport,这些属性我们可以认为是可滚动组件的通用属性.

reverse表示是否按照阅读方向相反的方向滑动,如:scrollDirection值为Axis.horizontal 时,即滑动发现为水平,如果阅读方向是从左到右。

reversetrue时,那么滑动方向就是从右往左。

ScrollController

可滚动组件都有一个 controller 属性,通过该属性我们可以指定一个 ScrollController 来控制可滚动组件的滚动,比如可以通过ScrollController来同步多个组件的滑动联动。

子节点缓存

按需加载子组件在大多数场景中都能有正收益,但是有些时候也会有副作用。比如有一个页面,它由一个ListView 组成,我们希望在页面顶部显示一块内容, 这部分内容的数据需要在每次页面打开时通过网络来获取,为此我们通一个 Header 组件来实现,它是一个 StatefulWidget ,会在initState 中请求网络数据,然后将它作为 ListView 的第一个孩子。现在问题来了,因为 ListView 是按需加载子节点的,这意味着如果 Header 滑出 Viewport 的预渲染区域之外时就会被销毁,重新滑入后又会被重新构建,这样就会发起多次网络请求,不符合我们期望。

为了解决上述问题,可滚动组件提供了一种缓存子节点的通用解决方案,它允许开发者对特定的子界限进行缓存.

Scrollbar

Scrollbar是一个Material风格的滚动指示器(滚动条),如果要给可滚动组件添加滚动条,只需将Scrollbar作为可滚动组件的任意一个父级组件即可,如:

Scrollbar(
  child: SingleChildScrollView(
    ...
  ),
);

ScrollbarCupertinoScrollbar都是通过监听滚动通知来确定滚动条位置的。

CupertinoScrollbar

CupertinoScrollbar是 iOS 风格的滚动条,如果你使用的是Scrollbar,那么在iOS平台它会自动切换为CupertinoScrollbar

总结

本篇介绍了可滚动组件的概念和具体的组成,构造。后续会具体介绍一些可滚动组件的使用详解。如ListView,GridView等,更多关于Flutter 可滚动组件的资料请关注我们其它相关文章!

(0)

相关推荐

  • Flutter实现Android滚动悬浮效果过程

    目录 1.计算每个区块的高度 2.实现分析-tabBar透明度渐变 3.实现分析-app上下滚动触发tabBar 4.实现分析-tabBar切换触发app滚动 5.源码 有以下几种效果 1.tabBar透明度随偏移0-1渐变过度 2.app上下滚动触发tabBar同步滚动 3.tabBar切换触发app上下同步滚动 1.计算每个区块的高度 用keyList保存声明的key,用heightList保存每个key对应的组件高度 // key列表 List<GlobalKey> keyList =

  • Flutter实现滚动选择数字

    本文实例为大家分享了Flutter实现滚动选择数字的具体代码,供大家参考,具体内容如下 前言 本来想百度查的,结果没查到,只有自己写,顺便记录一下,加深印象 页面需求要用户输入页码,之前选择的是使用TextField.后来觉得用showDialog弹出选项,让用户自己选择.类似这样的: 确定了样式就开始写吧.关于Dialog的选择,我用的是SimpleDialog,有对细节上有要求的可以自己自定义一个. showDialog(                 context: context,

  • Android Flutter实现有趣的页面滚动效果

    目录 CustomScrollView 简介 改造原代码 让导航栏更有趣 改造后的代码 其他效果 总结 在Flutter 高仿一个某支付价值几个亿的页面这一篇中,我们使用了 ListView 将几个 GridView 组合在一起实现了不同可滑动组件的粘合,但是这里必须要设置禁止 GridView 的滑动,防止多个滑动组件的冲突.这种方式写起来不太方便,事实上 Flutter 提供了 CustomScrollView 来粘合多个滑动组件,并且可以实现更有趣的滑动效果. CustomScrollVi

  • Flutter实现文本滚动高亮效果的示例讲解

    目录 前言 功能实现 前言 最近有个需求是人工语音播放时文本能随语音朗读时像歌词滚动的效果. 原本第一考虑的时能随时间字体渐变成更改后的颜色, 有比较流畅的走马灯效果. 但最终实践了几次后发现要能够逐字逐行渐变有一些麻烦, 不好实现. 所以转而变为将字体直接将字体高亮, 一段文本区分成两个部分, 一个部分是高亮文本, 也就是已朗读的部分, 一个部分是剩下未朗读的非高亮文本. 通过时时渲染页面就能达成滚动高亮的效果. 功能实现 因为在Text中会存在两段文本, 所以就不能单只用Text组件, 而改

  • Flutter之可滚动组件实例详解

    目录 正文 Scrollable 主轴和纵轴 Viewport Sliver 可滚动组件的通用配置 ScrollController 子节点缓存 Scrollbar 总结 正文 当内容超过显示视口(ViewPort)时,如果没有特殊处理,Flutter则会提示Overflow错误.为此,Flutter提供了多种可滚动widget(Scrollable Widget)用于显示列表和长布局. Flutter中有两种布局模型: 基于 RenderBox 的盒模型布局. 基于 Sliver ( Rend

  • Flutter快速制作一个水印组件实例详解

    目录 正文 通过child属性将水印叠加给子组件 createWatermark方法 Watermark组件的完整代码 正文 项目开发的过程中,经常会遇到添加水印的需求,其作用无非就是防止重要信息通过截图外传.(虽然我觉得并没有什么卵用,但领导的需求是不容质疑的) 那么,作为一线码农的我,也只能屁颠屁颠的开搞. 通过child属性将水印叠加给子组件 水印组件,既然是组件,就是需要发扬Flutter套娃的精神,通过child属性将水印叠加给子组件. class Watermark extends

  • vue实现简单表格组件实例详解

    本来想这一周做一个关于vuex的总结的,但是由于朋友反应说还不知道如何用vue去写一个组件,所以在此写写一篇文章来说明下如何去写vue页面或者组件.vue的核心思想就是组件,什么是组件呢?按照我的理解组件就是装配页面的零件,比如一辆车有大大小小许多零件组成,那么同样的一个页面,也是有许多组件构成的比如说头部组件 按钮组件等等,vue三大核心组件 路由 状态管理,路由控制页面的渲染,页面由组件组成,数据有vuex进行管理和改变.下面我会以一个简单的案例来说 第一步:构建一个简单的vue项目,老规矩

  • 基于vue+canvas的excel-like组件实例详解

    a vue component,基于vue的表格组件,主要解决大数据量的表格渲染性能问题,使用canvas绘制表格,同时支持类似excel的批量选中,复制黏贴删除,实时编辑等功能. vue-grid-canvas Install NPM / Yarn Install the package: npm install vue-canvas-grid --save Then import it in your project import Vue from 'vue' import Grid fro

  • vue组件编写之todolist组件实例详解

    我们在topNav这个页面上插入一个todolist子组件 我不知道怎么回事,这里的markdown的代码总是串行..所以代码看得不舒服,见谅啊,我最后会放github的源代码地址. 1. 父组件topNav中注册子组件,引入子组件 <template> <div> <p>下面这一行就是定义的组件名称</p> <todo-list></todo-list> <router-view></router-view>

  • Vue的事件响应式进度条组件实例详解

    写在前面 找了很多vue进度条组件,都不包含拖拽和点击事件,input range倒是原生包含input和change事件,但是直接基于input range做进度条的话,样式部分需要做大量调整和兼容性处理.即使做好了,将来需要修改外观,又是一番折腾. 基于以上两个原因,做了一个可以响应input和change事件(即一个是拖动进度条到某处,一个是在进度条某位置点击使其值变为该位置)的div实现的Vue组件,这样既满足了对进度条事件的需求,也带来了如有需求变动,样式修改很方便的好处. 效果图 以

  • Vuejs 单文件组件实例详解

    在之前的实例中,我们都是通过 Vue.component 或者 components 属性的方式来定义组件,这种方式在很多中小规模的项目中还好,但在复杂的项目中,下面这些缺点就非常明显了: 字符串模板:缺乏高亮,书写麻烦,特别是 HTML 多行的时候,虽然可以将模板写在 html 文件中,但是侵入性太强,不利于组件解耦分离. 不支持CSS:意味着当 HTML 和 JavaScript 组件化时,CSS明显被遗漏了 没有构建步骤:限制只能使用 HTML 和 ES5 JavaScript,而不能使用

  • js中自定义react数据验证组件实例详解

    我们在做前端表单提交时,经常会遇到要对表单中的数据进行校验的问题.如果用户提交的数据不合法,例如格式不正确.非数字类型.超过最大长度.是否必填项.最大值和最小值等等,我们需要在相应的地方给出提示信息.如果用户修正了数据,我们还要将提示信息隐藏起来. 有一些现成的插件可以让你非常方便地实现这一功能,如果你使用的是knockout框架,那么你可以借助于Knockout-Validation这一插件.使用起来很简单,例如我下面的这一段代码: ko.validation.locale('zh-CN');

  • 对Python中TKinter模块中的Label组件实例详解

    Python2.7.4 OS-W7x86 1. 简介 Label用于在指定的窗口中显示文本和图像.最终呈现出的Label是由背景和前景叠加构成的内容. Label组件定义函数:Label(master=None, cnf={}, **kw) 其中,kw参数是用来自定义lable组件的键值对. 2. 背景自定义 背景的话,有三部分构成:内容区+填充区+边框 <1>内容区参数有:width,length用于指定区域大小,如果显示前景内容是文本,则以单个字符大小为单位:如果显示的是图像,则以像素为单

  • vue的toast弹窗组件实例详解

    相信普通的vue组件大家都会写, 定义 -> 引入 -> 注册 -> 使用 ,行云流水,一气呵成,但是如果我们今天是要自定义一个弹窗组件呢? 首先,我们来分析一下弹窗组件的特性(需求): 0. 轻量 --一个组件小于 1Kib (实际打包完不到0.8k) 1.一般都是多处使用 --需要解决每个页面重复引用+注册 1.一般都是跟js交互的 --无需 在 <template> 里面写 <toast :show="true" text="弹窗消息

随机推荐