【Flutter学习】基本组件之上下刷新列表(一)
一,概述
RefreshIndicator是Flutter基于Material设计语言内置的控件,集合了下拉手势、加载指示器和刷新操作一体,可玩性比FutureBuilder差了一大截,不过大家也用过Material设计语言的其他控件,视觉效果也不赖的。
要实现拉刷新列表的功能仅仅依靠RefreshIndicator还不行,我们还需要ScrollController对ListView的移动偏移量进行监控。
二,两个重要的组件
- RefreshIndicator
- 构造函数
/** * 下拉刷新组件 *const RefreshIndicator ({ Key key, @required this.child, this.displacement: 40.0, //触发下拉刷新的距离 @required this.onRefresh, //下拉回调方法,方法需要有async和await关键字,没有await,刷新图标立马消失,没有async,刷新图标不会消失 this.color, //进度指示器前景色 默认为系统主题色 this.backgroundColor, //背景色 this.notificationPredicate: defaultScrollNotificationPredicate, }) */
- 构造函数
注意:
-
-
- RefreshIndicator的子元素必须是一个可以滚动的控件
- 如果你遇到不符合条件的控件,请将其用可以滚动的控件(如ListView、PageView等)包装一下
onRefresh
的回调函数必须是Future<Null>
类型
-
- ScrollController
- 构造函数
ScrollController({ double initialScrollOffset = 0.0, //初始滚动位置 this.keepScrollOffset = true,//是否保存滚动位置 ... })
-
属性和方法
- offset:可滚动Widget当前滚动的位置。
- jumpTo(double offset)、animateTo(double offset,...):这两个方法用于跳转到指定的位置,它们不同之处在于,后者在跳转时会执行一个动画,而前者不会。
-
滚动监听(addListener(listener))
ScrollController间接继承自Listenable,我们可以根据ScrollController来监听滚动事件。如:
controller.addListener(()=>print(controller.offset))
-
滚动位置恢复(keepScrollOffset,initialScrollOffset)
PageStorage是一个用于保存页面(路由)相关数据的Widget,它并不会影响子树的UI外观,其实,PageStorage是一个功能型Widget,它拥有一个存储桶(bucket),子树中的Widget可以通过指定不同的PageStorageKey来存储各自的数据或状态。
每次滚动结束,Scrollable Widget都会将滚动位置offset存储到PageStorage中,当Scrollable Widget 重新创建时再恢复。如果ScrollController.keepScrollOffset为false,则滚动位置将不会被存储,Scrollable Widget重新创建时会使用ScrollController.initialScrollOffset;ScrollController.keepScrollOffset为true时,Scrollable Widget在第一次创建时,会滚动到initialScrollOffset处,因为这时还没有存储过滚动位置。在接下来的滚动中就会存储、恢复滚动位置,而initialScrollOffset会被忽略。
-
滚动监听
Flutter Widget树中子Widget可以通过发送通知(Notification)与父(包括祖先)Widget通信。父Widget可以通过NotificationListener Widget来监听自己关注的通知,这种通信方式类似于Web开发中浏览器的事件冒泡,我们在Flutter中沿用“冒泡”这个术语。Scrollable Widget在滚动时会发送ScrollNotification类型的通知,ScrollBar正是通过监听滚动通知来实现的。通过NotificationListener监听滚动事件和通过ScrollController有两个主要的不同:
- 通过NotificationListener可以在从Scrollable Widget到Widget树根之间任意位置都能监听。而ScrollController只能和具体的Scrollable Widget关联后才可以。
- 收到滚动事件后获得的信息不同;NotificationListener在收到滚动事件时,通知中会携带当前滚动位置和ViewPort的一些信息,而ScrollController只能获取当前滚动位置。
NotificationListener
NotificationListener是一个Widget,模板参数T是想监听的通知类型,如果省略,则所有类型通知都会被监听,如果指定特定类型,则只有该类型的通知会被监听。NotificationListener需要一个onNotification回调函数,用于实现监听处理逻辑,该回调可以返回一个布尔值,代表是否阻止该事件继续向上冒泡,如果为true时,则冒泡终止,事件停止向上传播,如果不返回或者返回值为false 时,则冒泡继续。
- 构造函数
-
-
ScrollController控制原理
我们来介绍一下ScrollController的另外三个方法:
ScrollPosition createScrollPosition( ScrollPhysics physics, ScrollContext context, ScrollPosition oldPosition ); void attach(ScrollPosition position) ; void detach(ScrollPosition position) ;
当ScrollController和Scrollable Widget关联时,Scrollable Widget首先会调用ScrollController的createScrollPosition()方法来创建一个ScrollPosition来存储滚动位置信息,接着,Scrollable Widget会调用attach()方法,将创建的ScrollPosition添加到ScrollController的positions属性中,这一步称为“注册位置”,只有注册后animateTo() 和 jumpTo()才可以被调用。当Scrollable Widget销毁时,会调用ScrollController的detach()方法,将其ScrollPosition对象从ScrollController的positions属性中移除,这一步称为“注销位置”,注销后animateTo() 和 jumpTo() 将不能再被调用。
需要注意的是,ScrollController的animateTo() 和 jumpTo()内部会调用所有ScrollPosition的animateTo() 和 jumpTo(),以实现所有和该ScrollController关联的Scrollable Widget都滚动到指定的位置。
-
三,下拉加载,上拉刷新实现
class Widget_RefreshIndicator_State extends State<Widget_RefreshIndicator_Page> { var list = []; int page = 0; bool isLoading = false;//是否正在请求新数据 bool showMore = false;//是否显示底部加载中提示 bool offState = false;//是否显示进入页面时的圆形进度条 ScrollController scrollController = ScrollController(); @override void initState() { super.initState(); scrollController.addListener(() { if (scrollController.position.pixels == scrollController.position.maxScrollExtent) { print('滑动到了最底部${scrollController.position.pixels}'); setState(() { showMore = true; }); getMoreData(); } }); getListData(); } @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar( title: Text("RefreshIndicator"), ), body: Stack( children: <Widget>[ RefreshIndicator( child: ListView.builder( controller: scrollController, itemCount: list.length + 1,//列表长度+底部加载中提示 itemBuilder: choiceItemWidget, ), onRefresh: _onRefresh, ), Offstage( offstage: offState, child: Center( child: CircularProgressIndicator(), ), ), ], ) ), ); } @override void dispose() { super.dispose(); //手动停止滑动监听 scrollController.dispose(); } /** * 加载哪个子组件 */ Widget choiceItemWidget(BuildContext context, int position) { if (position < list.length) { return HomeListItem(position, list[position], (position) { debugPrint("点击了第$position条"); }); } else if (showMore) { return showMoreLoadingWidget(); }else{ return null; } } /** * 加载更多提示组件 */ Widget showMoreLoadingWidget() { return Container( height: 50.0, child: Row( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: <Widget>[ Text('加载中...', style: TextStyle(fontSize: 16.0),), ], ), ); } /** * 模拟进入页面获取数据 */ void getListData() async { if (isLoading) { return; } setState(() { isLoading = true; }); await Future.delayed(Duration(seconds: 3), () { setState(() { isLoading = false; offState = true; list = List.generate(20, (i) { return ItemInfo("ListView的一行数据$i"); }); }); }); } /** * 模拟到底部加载更多数据 */ void getMoreData() async { if (isLoading) { return; } setState(() { isLoading = true; page++; }); print('上拉刷新开始,page = $page'); await Future.delayed(Duration(seconds: 3), () { setState(() { isLoading = false; showMore = false; list.addAll(List.generate(3, (i) { return ItemInfo("上拉添加ListView的一行数据$i"); })); print('上拉刷新结束,page = $page'); }); }); } /** * 模拟下拉刷新 */ Future < void > _onRefresh() async { if (isLoading) { return; } setState(() { isLoading = true; page = 0; }); print('下拉刷新开始,page = $page'); await Future.delayed(Duration(seconds: 3), () { setState(() { isLoading = false; List tempList = List.generate(3, (i) { return ItemInfo("下拉添加ListView的一行数据$i"); }); tempList.addAll(list); list = tempList; print('下拉刷新结束,page = $page'); }); }); } }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
2018-07-09 dyld环境变量