Flutter ListView滑动

        在Flutter中,ScrollController可以精确地控制和管理滚动行为。通过ScrollController,可以监听滚动的位置、速度,甚至可以在用户滚动时触发自定义的动作。此外,ScrollController还提供了对滚动位置的直接控制,可以编程地滚动到特定位置。

 ScrollController构造函数

 ScrollController({double initialScrollOffset = 0.0,this.keepScrollOffset = true,this.debugLabel,this.onAttach,this.onDetach,}) : _initialScrollOffset = initialScrollOffset {if (kFlutterMemoryAllocationsEnabled) {ChangeNotifier.maybeDispatchObjectCreation(this);}}

 ScrollController常用的属性和方法

  • offset:可滚动组件当前的滚动位置。
  • jumpTo(double offset)、animateTo(double offset, {required Duration duration, required Curve curve,}):这两个方法用于跳转到指定的位置,它们不同之处在于,后者在跳转时会执行一个动画,而前者不会。

滚动监听

ScrollController间接继承自Listenable,可以根据ScrollController来监听滚动事件

  final ScrollController _scrollController = ScrollController();@overridevoid initState() {super.initState();_scrollController.addListener(() {});}

 实例

创建一个ListView,当滚动位置发生变化时,记录滑动位置:

final ScrollController _scrollController = ScrollController();@overridevoid initState() {super.initState();_scrollController.addListener(() {print("location:${_scrollController.offset}");setState(() {offset = _scrollController.offset;});});}body: ListView(scrollDirection: scrollDirection,controller: _scrollController,children: randomList.map<Widget>((data) {return Padding(padding: const EdgeInsets.all(8),child: _getRow(data[0], math.max(data[1].toDouble(), 50.0)),);}).toList(),)TextButton(onPressed: () {},child: Text("position: ${offset.floor()}"),)

       

 通过animateTo方法回到顶部。

      TextButton(onPressed: () {_scrollController.animateTo(0,duration: const Duration(seconds: 1),//动画时间是1秒,curve: Curves.bounceInOut);//动画曲线},child: Text("回到顶部"),
)

滚动位置恢复 (copy 6.4 滚动监听及控制 | 《Flutter实战·第二版》 (flutterchina.club))

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

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

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

ListView(key: PageStorageKey(1), ... );
...
ListView(key: PageStorageKey(2), ... );

 不同的PageStorageKey,需要不同的值,这样才可以为不同可滚动组件保存其滚动位置。

注意:一个路由中包含多个可滚动组件时,如果要分别跟踪它们的滚动位置,并非一定就得给他们分别提供PageStorageKey。这是因为Scrollable本身是一个StatefulWidget,它的状态中也会保存当前滚动位置,所以,只要可滚动组件本身没有被从树上移除(detach),那么其State就不会销毁(dispose),滚动位置就不会丢失。只有当Widget发生结构变化,导致可滚动组件的State销毁或重新构建时才会丢失状态,这种情况就需要显式指定PageStorageKey,通过PageStorage来存储滚动位置,一个典型的场景是在使用TabBarView时,在Tab发生切换时,Tab页中的可滚动组件的State就会销毁,这时如果想恢复滚动位置就需要指定PageStorageKey。

ScrollPosition (copy 6.4 滚动监听及控制 | 《Flutter实战·第二版》 (flutterchina.club))

ScrollPosition是用来保存可滚动组件的滚动位置的。一个ScrollController对象可以同时被多个可滚动组件使用,ScrollController会为每一个可滚动组件创建一个ScrollPosition对象,这些ScrollPosition保存在ScrollController的positions属性中(List<ScrollPosition>)。

  final List<ScrollPosition> _positions = <ScrollPosition>[];

ScrollPosition是真正保存滑动位置信息的对象,offset只是一个便捷属性:

  double get offset => position.pixels;

一个ScrollController虽然可以对应多个可滚动组件,但是有一些操作,如读取滚动位置offset,则需要一对一!但是我们仍然可以在一对多的情况下,通过其他方法读取滚动位置,举个例子,假设一个ScrollController同时被两个可滚动组件使用,那么我们可以通过如下方式分别读取他们的滚动位置:

...
controller.positions.elementAt(0).pixels
controller.positions.elementAt(1).pixels
...    

 我们可以通过controller.positions.length来确定controller被几个可滚动组件使用。

ScrollPosition有两个常用方法:animateTo() 和 jumpTo(),它们是真正来控制跳转滚动位置的方法,ScrollController的这两个同名方法,内部最终都会调用ScrollPosition的。

#ScrollPosition  
@overrideFuture<void> animateTo(double to, {required Duration duration,required Curve curve,});#ScrollPosition  @overridevoid jumpTo(double value);

#ScrollController  
Future<void> animateTo(double offset, {required Duration duration,required Curve curve,}) async {assert(_positions.isNotEmpty, 'ScrollController not attached to any scroll views.');await Future.wait<void>(<Future<void>>[for (int i = 0; i < _positions.length; i += 1) _positions[i].animateTo(offset, duration: duration, curve: curve),]);}#ScrollController void jumpTo(double value) {assert(_positions.isNotEmpty, 'ScrollController not attached to any scroll views.');for (final ScrollPosition position in List<ScrollPosition>.of(_positions)) {position.jumpTo(value);}}

ScrollController控制原理 (copy 6.4 滚动监听及控制 | 《Flutter实战·第二版》 (flutterchina.club))

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关联的可滚动组件都滚动到指定的位置。

滚动监听

  String notify = "";@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: const Text("ScrollToIndexDemoPage"),),body: NotificationListener(onNotification: (dynamic notification) {String notify = "";if (notification is ScrollEndNotification) {notify = "ScrollEnd";} else if (notification is ScrollStartNotification) {notify = "ScrollStart";} else if (notification is UserScrollNotification) {notify = " UserScroll";} else if (notification is ScrollUpdateNotification) {notify = "ScrollUpdate";}setState(() {this.notify = notify;});return false;},child: ListView(scrollDirection: scrollDirection,controller: _scrollController,children: randomList.map<Widget>((data) {return Padding(padding: const EdgeInsets.all(8),child: _getRow(data[0], math.max(data[1].toDouble(), 50.0)),);}).toList(),)),persistentFooterButtons: <Widget>[const SizedBox(width: 0.3, height: 30.0),TextButton(onPressed: () {},child: Text(notify),)],);}

 ScrollNotification是滚动事件通知,ScrollEndNotification、ScrollStartNotification、UserScrollNotification、ScrollUpdateNotification都是它的子类。

abstract class ScrollNotification extends LayoutChangedNotification with ViewportNotificationMixin {/// Initializes fields for subclasses.ScrollNotification({required this.metrics,required this.context,});/// A description of a [Scrollable]'s contents, useful for modeling the state/// of its viewport.final ScrollMetrics metrics;/// The build context of the widget that fired this notification.////// This can be used to find the scrollable's render objects to determine the/// size of the viewport, for instance.final BuildContext? context;@overridevoid debugFillDescription(List<String> description) {super.debugFillDescription(description);description.add('$metrics');}
}
mixin ScrollMetrics {/// Creates a [ScrollMetrics] that has the same properties as this object.////// This is useful if this object is mutable, but you want to get a snapshot/// of the current state.////// The named arguments allow the values to be adjusted in the process. This/// is useful to examine hypothetical situations, for example "would applying/// this delta unmodified take the position [outOfRange]?".ScrollMetrics copyWith({double? minScrollExtent,double? maxScrollExtent,double? pixels,double? viewportDimension,AxisDirection? axisDirection,double? devicePixelRatio,}) {return FixedScrollMetrics(minScrollExtent: minScrollExtent ?? (hasContentDimensions ? this.minScrollExtent : null),maxScrollExtent: maxScrollExtent ?? (hasContentDimensions ? this.maxScrollExtent : null),pixels: pixels ?? (hasPixels ? this.pixels : null),viewportDimension: viewportDimension ?? (hasViewportDimension ? this.viewportDimension : null),axisDirection: axisDirection ?? this.axisDirection,devicePixelRatio: devicePixelRatio ?? this.devicePixelRatio,);}

 pixels:当前滚动位置。
maxScrollExtent:最大可滚动长度。
extentBefore:滑出ViewPort顶部的长度;此示例中相当于顶部滑出屏幕上方的列表长度。
extentInside:ViewPort内部长度;此示例中屏幕显示的列表部分的长度。
extentAfter:列表中未滑入ViewPort部分的长度;此示例中列表底部未显示到屏幕范围部分的长度。
atEdge:是否滑到了可滚动组件的边界(此示例中相当于列表顶或底部)。

虽然 Flutter 官方提供了 ScrollController,调用相关方法可以滚动到指定偏移处,但是官方没有提供滚动到指定下标位置的功能。

scroll_to_index

我们可以使用三方库实现动到指定下标位置的功能。

import 'dart:math' as math;import 'package:flutter/material.dart';
import 'package:scroll_to_index/scroll_to_index.dart';class ScrollToIndexDemoPage extends StatefulWidget {const ScrollToIndexDemoPage({super.key});@override_ScrollToIndexDemoPageState createState() => _ScrollToIndexDemoPageState();
}class _ScrollToIndexDemoPageState extends State<ScrollToIndexDemoPage> {static const maxCount = 100;/// pub  scroll_to_index 项目的 controllerAutoScrollController? controller;final ScrollController _scrollController = ScrollController();final random = math.Random();final scrollDirection = Axis.vertical;late List<List<int>> randomList;//双获取偏移=>位置.像素;double offset = 0;String notify = "";@overridevoid initState() {super.initState();controller = AutoScrollController(viewportBoundaryGetter: () =>Rect.fromLTRB(0, 0, 0, MediaQuery.paddingOf(context).bottom),axis: scrollDirection);///一个 index 和 item 高度的数组randomList = List.generate(maxCount,(index) => <int>[index, (1000 * random.nextDouble()).toInt()]);_scrollController.addListener(() {print("location:${_scrollController.offset}");setState(() {offset = _scrollController.offset;});});}Widget _getRow(int index, double height) {return _wrapScrollTag(index: index,child: Container(padding: const EdgeInsets.all(8),alignment: Alignment.topCenter,height: height,decoration: BoxDecoration(border: Border.all(color: Colors.lightBlue, width: 4),borderRadius: BorderRadius.circular(12)),child: Text('index: $index, height: $height'),));}Widget _wrapScrollTag({required int index, required Widget child}) =>AutoScrollTag(key: ValueKey(index),controller: controller!,index: index,highlightColor: Colors.black.withOpacity(0.1),child: child,);@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: const Text("ScrollToIndexDemoPage"),),body: NotificationListener(onNotification: (dynamic notification) {String notify = "";if (notification is ScrollEndNotification) {notify = "ScrollEnd";} else if (notification is ScrollStartNotification) {notify = "ScrollStart";} else if (notification is UserScrollNotification) {notify = " UserScroll";} else if (notification is ScrollUpdateNotification) {notify = "ScrollUpdate";}setState(() {this.notify = notify;});return false;},child: ListView(scrollDirection: scrollDirection,controller: controller,children: randomList.map<Widget>((data) {return Padding(padding: const EdgeInsets.all(8),child: _getRow(data[0], math.max(data[1].toDouble(), 50.0)),);}).toList(),)),persistentFooterButtons: <Widget>[TextButton(onPressed: () async {///滑动到第13个的位置await controller!.scrollToIndex(13, preferPosition: AutoScrollPosition.begin);controller!.highlight(13);},child: const Text("Scroll to 13"),),// const SizedBox(width: 0.3, height: 30.0),// TextButton(//   onPressed: () {//     // _scrollController.animateTo(0,//     //     duration: const Duration(seconds: 1),//     //     curve: Curves.bounceInOut);//     // // setState(() {//     // //   entries = entries;//     // // });//   },//   child: Text("position: ${offset.floor()}"),// ),// const SizedBox(width: 0.3, height: 30.0),// TextButton(//   onPressed: () {//     _scrollController.animateTo(0,//         duration: const Duration(seconds: 1),//         curve: Curves.bounceInOut);//   },//   child: Text("回到顶部"),// ),// const SizedBox(width: 0.3, height: 30.0),// TextButton(//   onPressed: () {},//   child: Text(notify),// )],);}
}

AutoScrollTag 是一个可以包裹在任意行级 widget 中的组件,它会接收控制器和索引值,并在需要时高亮显示。
AutoScrollController 则负责整个滚动操作,包括监听和触发滚动到指定索引的命令。

对于有固定行高的情况,可以设置 suggestedRowHeight 参数以提高滚动效率。
通过 viewportBoundaryGetter 自定义视口边界,以及选择垂直或水平滚动方向。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/409286.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

DRF——请求的封装与版本管理

文章目录 django restframework1. 快速上手2. 请求数据的封装3. 版本管理3.1 URL的GET参数传递&#xff08;*&#xff09;3.2 URL路径传递&#xff08;*&#xff09;3.3 请求头传递3.4 二级域名传递3.5 路由的namespace传递 小结 django restframework 快速上手请求的封装版本…

科大讯飞刘聪:大模型加持,人形机器人将跨越三大瓶颈

2024年&#xff0c;AI大模型成为机器人产业新的加速器。 今年3月&#xff0c;ChatGPT4加持的机器人Figure01向外界展示了大模型赋能人形机器人的巨大潜力。Figure01能理解周围环境&#xff0c;流畅地与人类交谈&#xff0c;理解人类的需求并完成具体行动&#xff0c;包括给人类…

虚幻5|AI视力系统,听力系统,预测系统(2)听力系统

虚幻5|AI视力系统&#xff0c;听力系统&#xff0c;预测系统&#xff08;1&#xff09;视力系统-CSDN博客 一&#xff0c;把之前的听力系统&#xff0c;折叠成函数&#xff0c;复制粘贴一份改名为听力系统 1.小个体修改如下&#xff0c;把之前的视力系统改成听力系统 2.整体修…

隐私指纹浏览器产品系列 —— 浏览器指纹 中(三)

1.引言 在上一篇文章中&#xff0c;我们聊到了最老牌的浏览器指纹检测站——BrowserLeaks。BrowserLeaks曾经是浏览器指纹检测的权威&#xff0c;但它似乎更像是一本老旧的工具书&#xff0c;只能呆板告诉你浏览器的指纹值&#xff0c;并对比不同浏览器的指纹差异。 今天&…

C语言 之 浮点数在内存中的存储 详细讲解

文章目录 浮点数浮点数的存储浮点数的存储浮点数的读取例题 浮点数 常见的浮点数&#xff1a;3.14159、1E10&#xff08;表示1*10^10&#xff09;等 浮点数家族包括&#xff1a; float、double、long double 类型。 浮点数表示的范围在float.h 中有定义 浮点数的存储 浮点数…

C++研发笔记1——github注册文档

1、第一步&#xff1a;登录网站 GitHub: Let’s build from here GitHub 最新跳转页面如下&#xff1a; 2、选择“sign up”进行注册&#xff0c;并填写设置账户信息 3、创建账户成功之后需要进行再次登录 4、根据实际情况填写个人状态信息 登录完成后页面网站&#xff1a; 5…

手写SpringAOP

一、非注解式简易版AOP 整体流程 1.1 代码 public class Test {public static void main(String[] args){// Aop代理工厂DefaultAopProxyFactory factory new DefaultAopProxyFactory();// 测试对象AOPDemoImpl demo new AOPDemoImpl();// 支撑类&#xff1a;用于存放目标…

配置策略路由实战 附带基础网络知识

背景 作为一个软件开发人员&#xff0c;不可能做到只负责业务开发工作&#xff0c;一旦功能上线或者系统切换就会遇到非常多考验开发人员个人能力的场景&#xff0c;网络调整就是非常重要的一个方面&#xff0c;如果你在系统上线的过程中无法处理一些简单的网络问题或者听不懂…

文件包含漏洞(1)

目录 PHP伪协议 php://input Example 1&#xff1a; 造成任意代码执行 Example 2&#xff1a; 文件内容绕过 php://filer zip:// PHP伪协议 php://input Example 1&#xff1a; 造成任意代码执行 搭建环境 <meta charset"utf8"> <?php error_repo…

Modern C++——不准确“类型声明”引发的非必要性能损耗

大纲 案例代码地址 C是一种强类型语言。我们在编码时就需要明确指出每个变量的类型&#xff0c;进而让编译器可以正确的编译。看似C编译器比其他弱类型语言的编译器要死板&#xff0c;实则它也做了很多“隐藏”的操作。它会在尝试针对一些非预期类型进行相应转换&#xff0c;以…

QT Quick QML 网络助手——TCP客户端

GitHub 源码: QmlLearningPro &#xff0c;选择子工程 Nettools.pro QML 其它文章请点击这里: QT QUICK QML 学习笔记 ● 运行效果&#xff1a; 左侧为常用的网络调试工具&#xff0c;右侧为本项目 UI 效果&#xff0c;前端使用 QML &#xff0c;后端使用C &#xff…

【文档智能 RAG】浅看开源的同质化的文档解析框架-Docling

前言 RAG的兴起&#xff0c;越来越多的人开始关注文档结构化解析的效果&#xff0c;这个赛道变得非常的同质化。 关于文档智能解析过程中的每个技术环节的技术点&#xff0c;前期文章详细介绍了很多内容&#xff1a; 下面我们简单的看看Docling这个PDF文档解析框架里面都有什…

GPIO(通用输入/输出)、中断(hal库)

目录 GPIO&#xff08;通用输入/输出)&#xff08;hal库&#xff09; GPIO工作模式 推挽输出&#xff08;Push-Pull Output&#xff09; 开漏输出&#xff08;Open-Drain Output&#xff09; 复用推挽输出&#xff08;Alternate Function Push-Pull Output&#xff09; 复…

在Ubuntu 22.04测试ebpf-go入门例子

文章目录 1、eBPF-Go依赖1.1 Ubuntu安装ssh server1.2 安装go1.3 安装llvm和clang1.4 安装libbpf和Linux kernel headers 2 编写eBPF C程序3 使用bpf2go编译eBPF C程序4 编写Go程序5 编译运行Go应用程序 eBPF-Go是一个使用eBPF的Go库。它不依赖于C、libbpf或除标准库之外的任何…

Windows单机安装配置mongodb+hadoop+spark+pyspark用于大数据分析

目录 版本选择安装配置Java环境配置Hadoop配置Spark配置 安装pyspark使用Jupyter Notebook进行Spark MongoDB测试参考 版本选择 根据Spark Connector&#xff1a;org.mongodb.spark:mongo-spark-connector_2.13:10.3.0 的前提要求 这里选择使用最新的MongoDB 7.0.12社区版 ht…

基于R语言进行AMMI分析3

参考资料&#xff1a;https://cran.r-project.org/web/packages/agricolae/agricolae.pdf 1、plot()函数 本次介绍的是Agricolae包中的plot.AMMI()函数。此函数可以绘制AMMI双标图&#xff0c;也可以绘制三标图&#xff08;三个坐标轴&#xff0c;IPCA1&#xff0c;IPCA2&…

TiggerRamDisk绕过激活界面,支持最新iOS17.4.1绕过

&#x1f427;技术交流&#xff1a;582022476 ——————— iOS15等待越狱的日子实在太久了&#xff01;checkra1n越狱目前还未发布iOS15系统越狱。 可很多朋友不小心或者大意已经升级到了最新iOS15系统。一般来说这并没有什么大碍&#xff0c;但如果是绕过激活的设备&#…

Cesium 展示——绘制水面动态升高

文章目录 需求分析需求 如图,绘制水面动态升高,作为洪水淹没的效果 分析 我们首先需要绘制一个面然后给这个面一个高度,在回调函数中进行动态设置值【这里有两种,一种是到达水面一定高度停止升高,一种是水面重新升高】/*** @description :洪水淹没* @author : Hukang*…

一起学Java(3)-Java项目构建工具Gradle和Maven场景定位和优缺点对比

在第一步创建的项目&#xff08;java-all-in-one&#xff09;项目里&#xff0c;我们提到了使用Gradle作为项目构建工具。看到这里&#xff0c;不知道你是否有疑惑&#xff0c;什么是项目构建工具。Java项目常用构建工具有哪些&#xff1f;都有什么特点&#xff1f; 带着疑惑&…

工厂现场多功能帮手,三防平板改善管理体验

随着制造业的智能化变革&#xff0c;信息化、自动化和智能化逐渐成为工厂管理的新常态。在这一波技术浪潮中&#xff0c;三防平板作为一种多功能的工作工具&#xff0c;正在逐步改善工厂现场的管理体验。 一、三防平板的定义与特点 三防平板&#xff0c;顾名思义&#xff0c;是…