前言

可以用ScrollController来控制可滚动组件的滚动位置。

接口描述

ScrollController({
// 初始滚动位置
double initialScrollOffset = 0.0,
// 是否保持滚动位置
this.keepScrollOffset = true,
this.debugLabel,
})

代码示例

// ScrollController

// 可以用ScrollController来控制可滚动组件的滚动位置。

import 'package:flutter/material.dart';

class ScrollControllerTest extends StatefulWidget{
@override
ScrollControllerTestState createState() => ScrollControllerTestState();
} class ScrollControllerTestState extends State<ScrollControllerTest>{
ScrollController _controller = ScrollController();
// 是否显示“返回到顶部”按钮
bool showToTopBtn = false; @override
void initState(){
super.initState();
// 监听滚动事件,打印滚动位置
_controller.addListener((){
// 打印滚动位置
print(_controller.offset);
if(_controller.offset < 1000 && showToTopBtn){
setState(() {
showToTopBtn = false;
});
} else if(_controller.offset >= 1000 && showToTopBtn == false){
setState(() {
showToTopBtn = true;
});
}
});
} @override
void dispose(){
// 为了避免内存泄漏,需要调用_controller.dispose
_controller.dispose();
super.dispose();
} Widget build(BuildContext context){
return Scaffold(
appBar: AppBar(
title: Text('滚动控制1'),
),
body: Scrollbar(
child: ListView.builder(
itemCount: 100,
itemExtent: 50.0,
controller: _controller,
itemBuilder: (context, index){
return ListTile(title: Text("$index"),);
},
),
),
floatingActionButton: !showToTopBtn ? null : FloatingActionButton(
child: Icon(Icons.arrow_upward),
onPressed: (){
// 返回到顶部时执行动画
_controller.animateTo(
.0,
// 返回顶部的过程中执行一个滚动动画,动画时间是200毫秒,动画曲线是Curves.ease
duration: Duration(milliseconds: 200),
curve: Curves.ease,
);
},
),
);
} } // 滚动监听
class ScrollNotificationTest extends StatefulWidget{
@override
_ScrollNotificationTestState createState() => _ScrollNotificationTestState();
} class _ScrollNotificationTestState extends State<ScrollNotificationTest>{
//保持进度百分比
String _progress = '0%'; Widget build(BuildContext context){
return Scaffold(
appBar: AppBar(
title: Text('滚动控制2'),
),
// 进度条
body: Scrollbar(
// 监听滚动通知
child: NotificationListener<ScrollNotification>(
onNotification: (ScrollNotification notification){
// pixels:当前滚动位置;maxScrollExtent:最大可滚动长度
double progress = notification.metrics.pixels /
notification.metrics.maxScrollExtent;
// 重新构建
setState(() {
_progress = '${(progress * 100).toInt()}%';
});
// extentBefore:滑出ViewPort顶部的长度;此示例中相当于顶部滑出屏幕上方的列表长度。
// extentInside:ViewPort内部长度;此示例中屏幕显示的列表部分的长度。
// extentAfter:列表中未滑入ViewPort部分的长度;此示例中列表底部未显示到屏幕范围部分的长度。
// atEdge:是否滑到了可滚动组件的边界(此示例中相当于列表顶或底部)。
print('BottomEdge: ${notification.metrics.extentAfter == 0}');
//
return true;
},
child: Stack(
alignment: Alignment.center,
children: <Widget>[
ListView.builder(
itemCount: 100,
itemExtent: 50.0,
itemBuilder: (context, index){
return ListTile(title: Text('$index'),);
},
),
// 显示百分比
CircleAvatar(
radius: 30.0,
child: Text(_progress),
backgroundColor: Colors.black54,
)
],
),
),
),
);
}
}

总结

滚动位置恢复

PageStorage是一个用于保存页面(路由)相关数据的组件,它并不会影响子树的UI外观,其实,PageStorage是一个功能型组件,它拥有一个存储桶(bucket),子树中的Widget可以通过指定不同的PageStorageKey来存储各自的数据或状态。

每次滚动结束,可滚动组件都会将滚动位置offset存储到PageStorage中,当可滚动组件重新创建时再恢复。如果ScrollController.keepScrollOffset为false,则滚动位置将不会被存储,可滚动组件重新创建时会使用ScrollController.initialScrollOffset;ScrollController.keepScrollOffset为true时,可滚动组件在第一次创建时,会滚动到initialScrollOffset处,因为这时还没有存储过滚动位置。在接下来的滚动中就会存储、恢复滚动位置,而initialScrollOffset会被忽略。

当一个路由中包含多个可滚动组件时,如果你发现在进行一些跳转或切换操作后,滚动位置不能正确恢复,这时你可以通过显式指定PageStorageKey来分别跟踪不同的可滚动组件的位置。

ScrollPosition

ScrollPosition是用来保存可滚动组件的滚动位置的。一个ScrollController对象可以同时被多个可滚动组件使用,ScrollController会为每一个可滚动组件创建一个ScrollPosition对象,这些ScrollPosition保存在ScrollController的positions属性中(List)。ScrollPosition是真正保存滑动位置信息的对象,offset只是一个便捷属性。

一个ScrollController虽然可以对应多个可滚动组件,但是有一些操作,如读取滚动位置offset,则需要一对一!但是我们仍然可以在一对多的情况下,通过其它方法读取滚动位置。ScrollPosition有两个常用方法:animateTo() 和 jumpTo(),它们是真正来控制跳转滚动位置的方法,ScrollController的这两个同名方法,内部最终都会调用ScrollPosition的。

ScrollController控制原理

ScrollController的另外三个方法:

ScrollPosition createScrollPosition(
ScrollPhysics physics,
ScrollContext context,
ScrollPosition oldPosition);
void attach(ScrollPosition position) ;
void detach(ScrollPosition position) ;

当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组件来监听自己关注的通知,这种通信方式类似于Web开发中浏览器的事件冒泡。

可滚动组件在滚动时会发送ScrollNotification类型的通知,ScrollBar正是通过监听滚动通知来实现的。通过NotificationListener监听滚动事件和通过ScrollController有两个主要的不同:

  1. 通过NotificationListener可以在从可滚动组件到widget树根之间任意位置都能监听。而ScrollController只能和具体的可滚动组件关联后才可以。
  2. 收到滚动事件后获得的信息不同;NotificationListener在收到滚动事件时,通知中会携带当前滚动位置和ViewPort的一些信息,而ScrollController只能获取当前滚动位置。

【Flutter】可滚动组件之滚动控制和监听的更多相关文章

  1. highcharts图表组件入门教程:如何监听柱状图柱子点击事件动态更新当前数据点数值和所对应X轴刻度

    highcharts图表组件入门教程:如何监听柱状图柱子点击事件动态更新当前数据点数值和所对应X轴刻度 作者:highcharts | 时间:2014-6-11 14:07:05 | [小  大] | ...

  2. 【Flutter学习】可滚动组件之滚动监听及控制

    一,概述 ScrollController可以用来控制可滚动widget的滚动位置 二,ScrollController 构造函数 ScrollController({ double initialS ...

  3. 对ListView滚动状态的监听

    有的时候,我们需要对ListView滚动做一个相应的监听事件,例如:要实现如下图通讯录的功能: 思路为:首先呢,中间那个"路"字为一个TextView,它与ListView采用相对 ...

  4. 20、解决Vue使用bus兄弟组件间传值,第一次监听不到数据

    1.新建bus.js文件: import Vue from 'vue' export default new Vue; 2.在需要通信组件A,B中引入bus: A组件: import Bus from ...

  5. 【Flutter学习】可滚动组件之ScrollView

    一,概述 ScrollView 是一个带有滚动的视图组件. 二,组成部分 ScrollView 由三部分组成: Scrollable - 它监听各种用户手势并实现滚动的交互设计.可滚动Widget都直 ...

  6. 【Flutter】可滚动组件简介

    前言 当组件内容超过当前显示视口(ViewPort)时,如果没有特殊处理,Flutter则会提示Overflow错误.为此,Flutter提供了多种可滚动组件(Scrollable Widget)用于 ...

  7. 【Flutter学习】可滚动组件之SingleChildScrollView

    一,概述 SingleChildScrollView类似于Android中的ScrollView,它只能接收一个子Widget.定义如下: 二,构造函数 const SingleChildScroll ...

  8. vue.js 组件监听

    一.在通过点击事件触发的子组件中: addCart(event) { if (!event._constructed) { return; } if (!this.food.count) { Vue. ...

  9. 父组件向子组件传值时,值已经传过来却没有触发子组件的watch监听,解决~

    需求: 父组件像封装的子组件传值  (父组件属性传值,子组件props接受)   子组件接受后经过处理回显页面; 预想:子组件接受值 , 经过watch监听,在监听中处理数据,回显数据; 问题:子组件 ...

随机推荐

  1. 安卓qq视频动态名片制作器

    本软件来自互联网,仅供个人参考,严禁商业用途! 非常炫酷的diy动态名片教程,B格绝对高,内含软件教程代码,包会!

  2. proxySQL with MGR

    环境信息 hostname IP port role comm ms81 192.168.188.81 3399 master ms82 192.168.188.82 3399 slave ms83 ...

  3. python 安装相关

    一.安装python 1.官网下载python 1.1 可下载绿色版 2.2 也可下载安装版,安装时可自动安装pip 和 自动配置环境变量 2.手动配置环境变量,我的电脑>属性>高级> ...

  4. css进阶 05-CSS的一些小知识

    05-CSS的一些小知识 #隐藏盒子的几种方式 隐藏盒子,有以下几种方式: (1)方式一: overflow:hidden; //隐藏盒子超出的部分   (2)方式二: display: none; ...

  5. JavaScript var,let,const三个关键字的区别

    var: 1)声明作用域:在函数内部,使用var定义一个变量(局部变量),在函数被调用完之后,该变量会被立即销毁.在定义变量时如果省略var,就会创建一个全局变量(不建议在局部作用域中定义全局变量,难 ...

  6. python菜鸟教程学习:数据结构

    列表方法 list.append(x):把一个元素添加到列表的结尾,相当于 a[len(a):] = [x]. list.extend(L):通过添加指定列表的所有元素来扩充列表,相当于 a[len( ...

  7. 分析《令人心动的offer2》网友们都在吐槽什么?

    综艺,是我们劳累了一天的放松方式,也是我们饭后的谈资.看着自己喜欢的综艺,时光足够美.而<令人心动的offer >,就是一个不错的综艺选择.有人说它让自己更自卑了,而我觉得挺有意思. &l ...

  8. 手把手教你搭饥荒专用服务器(三)—MOD及其他高级设置

    友情链接: 手把手教你搭饥荒专用服务器(一)-服务器准备工作 手把手教你搭饥荒专用服务器(二)-环境配置及基本使用 手把手教你搭饥荒专用服务器(三)-MOD及其他高级设置 手把手教你搭饥荒专用服务器( ...

  9. Liunx运维(六)-文件备份与压缩命令

    文档目录: 一.tar:打包备份 二.gzip:压缩或解压文件 三.zip:打包和压缩文件 四.unzip:解压zip文件 五.scp:远程文件复制 六.rsync:文件同步工具 ---------- ...

  10. vue+element对常用表格的简单封装

    在后台管理和中台项目中, table是使用率是特别的高的, 虽然element已经有table组件, 但是分页和其他各项操作还是要写一堆的代码, 所以就在原有的基础上做了进一步的封装 所涵盖的功能有: ...