一种Flutter加载更多的实现方法

article/2025/9/18 1:50:19

转载注明出处:https://blog.csdn.net/skysukai

1、why flutter?

我们在进行Android开发的时候,比如布局文件,会创建一个xml来存放布局。写熟悉了觉得没什么,可是,用xml来存放布局文件是十年前的技术了。在十年过后,再用xml来写布局文件运行时在由系统负责渲染看起来有些过时。
关于为什么是flutter,网上有很多讨论,在我看来最重要的应该是大前端的思想。作为一个Android开发者,我们也应该去学习这种先进的思想。

2、背景

项目中有很多ListView、GridView的场景,通常来说,从服务器获取数据都会分页获取。而flutter官方并没有提供一个loadmore控件。这就需要开发者自己实现。先贴出一张效果图:
在这里插入图片描述
网上有一些关于ListView加载更多的实现,无外乎都是判断是否滚动到底,滚动到底之后再加载更多。但是在我们的项目中,不仅有ListView还有GridView、StaggeredGrid(瀑布流)。作为一个懒程序员,我更愿意用一套代码解决这三个控件的加载更多 ,而不愿意分别为他们都写一套代码。

3、实现

站在巨人的肩膀上才能看得更远,这篇blog给了我莫大的启示,感谢作者。我的加载更多控件姑且叫做LoadMoreIndicator吧。

3.1 总体思路

3.1.1 状态定义

总体来说,我们还是需要判断是否滚动到底。如果滚动到底且还有数据,则加载更多;否则,无更多数据。所以,我们的LoadMoreIndicator至少应该包含IDLE、NOMORE这两个状态,除此之外,应该还有FAIL、LOADING两个状态,分别对应加载失败、正在加载。

3.1.2 监听滚动事件

作为加载更多的实现,如何判断滚动到底?flutter提供了ScrollController来监听滚动类控件,而我最开始也是使用ScrollController来做的,不过后面还是换成了Notification,其中遇到过一个坑,后边再详细说明。有关flutter的notification机制网上有很多介绍,总的来说就是一个flutter的事件分发机制。

3.1.3 如何统一封装

上面提到过,项目里面用到的滚动控件包括ListView、GridView、StaggeredGrid,那这三个不同的控件该如何封装到一起呢?单论这个问题似乎有很多解。再仔细分析项目需求,除了那三个滚动控件之外,可能还需要Appbar用于定义title,也还需要pinnedHeader。能把三个滚动控件统一起来,且还支持Appbar、pinnedHeader的控件只有CustomScrollView了。CustomScrollView包含多个滚动模型,能够处理许多个滚动控件带来的滑动冲突。
那么,LoadMoreIndicator的主体也很清晰了——通过CustomScrollView封装不同的滚动控件,并且处理各种业务场景。

3.2 主体框架

给出一小段代码,说明LoadMoreIndicator的主体:

class LoadMoreIndicator<T extends Widget, K extends Widget>extends StatefulWidget {/// the Sliver headerfinal K header;/// the Sliver bodyfinal T child;/// callback to loading morefinal LoadMoreFunction onLoadMore;/// footer delegatefinal LoadMoreDelegate delegate;/// whether to load when emptyfinal bool whenEmptyLoad;///define emptyview or use default emptyviewfinal Widget emptyView;const LoadMoreIndicator({Key key,@required this.child,@required this.onLoadMore,this.header,this.delegate,this.whenEmptyLoad = true,this.controller,this.emptyView}) : super(key: key);@override_LoadMoreIndicatorState createState() => _LoadMoreIndicatorState();……
}
class _LoadMoreIndicatorState extends State<LoadMoreIndicator> {……/// original widget need to be wrapped by CustomScrollViewfinal List<Widget> _components = [];@overrideWidget build(BuildContext context) {/// build headerif (childHeader != null) {_components.add(SliverToBoxAdapter(child: childHeader,));}/// add body_components.add(childBody);/// build footer_components.add(SliverToBoxAdapter(child: _buildFooter(),));return _rebuildConcrete();}/// build actual Sliver BodyWidget _rebuildConcrete() {return NotificationListener<ScrollNotification>(onNotification: _onScrollToBottom,child: CustomScrollView(slivers: _components,),);}bool _onScrollToBottom(ScrollNotification scrollInfo) {/// if is loading returnif (_status == LoadMoreStatus.LOADING) {return true;}/// scroll to bottomif (scrollInfo.metrics.extentAfter == 0.0 &&scrollInfo.metrics.pixels >= scrollInfo.metrics.maxScrollExtent * 0.8) {if (loadMoreDelegate is DefaultLoadMoreDelegate) {/// if scroll to bottom and there has more data then loadif (_status != LoadMoreStatus.NOMORE && _status != LoadMoreStatus.FAIL) {loadData();}}}return false;}……}

以上这一小段代码就是LoadMoreIndicator最核心代码了,非常简单。只需要把需要封装的控件传递过来,添加header、footer即可。有一个问题是,这样封装的话,滚动控件必须是sliver的实现,如:SliverGrid、SliverList、SliverStaggeredGrid,目前没有想到其他更好的解决办法。loadData()就是加载更多的实现,一般是连接到服务器获取数据。

3.3 构造footer

LoadMoreIndicator中,封装完滚动控件之后,最重要的工作就是构造footer了。选中了LoadMoreIndicator代码的主体是Customscrollview之后,其实构造footer也很简单了。SliverToBoxAdapter就是flutter提供的用于封装的其他Widget的控件,只需要把构造的footer用SliverToBoxAdapter再包装一层即可大功告成。给出代码片段:

  Widget _buildFooter() {return NotificationListener<_RetryNotify>(child: NotificationListener<_AutoLoadNotify>(child: DefaultLoadMoreView(status: _status,delegate: loadMoreDelegate,),onNotification: _onAutoLoad,),onNotification: _onRetry,);}

DefaultLoadMoreView用于设置默认的加载更多动画,如果用户没有设置,则使用这个加载效果;否则使用定义过的加载效果。

/// if don't define loadmoreview use default
class DefaultLoadMoreView extends StatefulWidget {final LoadMoreStatus status;final LoadMoreDelegate delegate;const DefaultLoadMoreView({Key key,this.status = LoadMoreStatus.IDLE,@required this.delegate,}) : super(key: key);@overrideDefaultLoadMoreViewState createState() => DefaultLoadMoreViewState();
}class DefaultLoadMoreViewState extends State<DefaultLoadMoreView> {LoadMoreDelegate get delegate => widget.delegate;@overrideWidget build(BuildContext context) {notify();return GestureDetector(behavior: HitTestBehavior.translucent,onTap: () {if (widget.status == LoadMoreStatus.FAIL ||widget.status == LoadMoreStatus.IDLE) {/// tap to load_RetryNotify().dispatch(context);}},child: Container(alignment: Alignment.center,child: delegate.buildChild(context,widget.status,),),);}……
}

加载动画的实现在DefaultLoadMoreDelegate中,通过代理的模式来设置默认的加载动画:

///default LoadMoreView delegate
class DefaultLoadMoreDelegate extends LoadMoreDelegate {@overrideWidget buildChild(BuildContext context, LoadMoreStatus status) {switch (status) {case LoadMoreStatus.IDLE:case LoadMoreStatus.LOADING:return LoadingAnimation(blockBackKey: false);break;case LoadMoreStatus.NOMORE:return Center(child: Padding(padding: EdgeInsets.all(10.0),child: Row(mainAxisAlignment: MainAxisAlignment.center,crossAxisAlignment: CrossAxisAlignment.center,children: <Widget>[Text(S.of(context).loadMore_Nomore,style: TextStyle(color: Colors.white),),],),),);break;case LoadMoreStatus.FAIL:return Text(S.of(context).loadMore_Fail,style: TextStyle(color: Colors.white),);break;}return null;}
}

3.4 其他问题

到这里,基本讲清楚了LoadMoreIndicator的实现思路,还有很多细节问题需要花功夫完善,如:怎么判断是否加载完,没有更多数据?是否可以提供默认的EmptyView?

3.4.1 loadData()的实现

前面已经提到过当判断滚动到底的时候需要触发加载更多,loadData()这个函数怎么实现呢?

  /// notify UI to load more data and receive resultvoid loadData() {if (_status == LoadMoreStatus.LOADING) {return;}if(mounted) {setState(() {_updateStatus(LoadMoreStatus.LOADING);});}widget.onLoadMore((int count, int pageNum, int pageSize) {if (pageNum * pageSize >= count) {_updateStatus(LoadMoreStatus.NOMORE);} else {_updateStatus(LoadMoreStatus.IDLE);}if(mounted) {setState(() {_isEmpty = count == 0;});}}, (int errorCode) {_updateStatus(LoadMoreStatus.FAIL);if (mounted) {setState(() {});}});}

LoadMoreIndicator中滚动到底之后,需要触发真实的页面去请求数据,而不可能在控件里边去完成业务逻辑。在java中可以使用回调接口来完成,再把请求结果传回LoadMoreIndicator,用于更新footer状态。在dart中可以使用typedef来完成相同的功能,即用方法来代替回调接口,这部分不是本文的重点,在此略过。
来看一下LoadMoreIndicator中回调方法的定义:

typedef void LoadMoreOnSuccess(int totalCount, int pageNum, int pageSize);
typedef void LoadMoreOnFailure(int errorCode);
typedef void LoadMoreFunction(LoadMoreOnSuccess success, LoadMoreOnFailure failure);

LoadMoreFunction作为LoadMoreIndicator的一个成员变量,它的实现在具体业务逻辑中。LoadMoreOnSuccessLoadMoreOnFailure是业务逻辑加载失败或成功的回调,用于通知LoadMoreIndicator更新footer状态。

3.3.2 为什么不能用ScrollController

LoadMoreIndicator完成之后,能够满足项目中大部门场景,但是在一个场景中,页面不能滚动了。先来看下设计图:
在这里插入图片描述
在这个界面中,有三个页签,每一个页签都要求能够加载更多。flutter提供了NestedScrollView来实现一个滑动头部折叠的动画效果。在NestedScrollView的body中设置TabBarView,即可达到效果。
之前提到,为了监听滚动,一般来说得给控件设置ScrollController来监听,但是NestedScrollView本身自带一个监听,用于处理滚动冲突,并且在NestedScrollView有一段注释:

 // The "controller" and "primary" members should be left// unset, so that the NestedScrollView can control this// inner scroll view.// If the "controller" property is set, then this scroll// view will not be associated with the NestedScrollView.// The PageStorageKey should be unique to this ScrollView;// it allows the list to remember its scroll position when// the tab view is not on the screen.

所以,在LoadMoreIndicator只能使用ScrollNotification来监听滚动到底,但是在这样修改之后,理论上能够监听tabbarview的滚动了,实际上,tabbarview还是不能滚动到底,头像依然不能被收起。来看下那个包裹头像的appbar是怎么写的吧:

SliverAppBar(pinned: true,expandedHeight: ScreenUtils.px2dp(1206),forceElevated: innerBoxIsScrolled,bottom: PreferredSize(child: Container(child: TabBar(indicatorColor: Colors.red,indicatorWeight: ScreenUtils.px2dp(12),indicatorPadding: EdgeInsets.only(top: 10.0),indicatorSize: TabBarIndicatorSize.label,labelColor: Colors.red,labelStyle: _tabBarTextStyle(),unselectedLabelColor: Colors.white,unselectedLabelStyle: _tabBarTextStyle(),tabs: _tabTagMap.keys.map((String tag) => Tab(child: Tab(text: tag),),).toList(),),color: Colors.black,),preferredSize:Size(double.infinity, ScreenUtils.px2dp(192))),flexibleSpace: Container(child: Column(children: <Widget>[AppBar(backgroundColor: Colors.black,),Expanded(child: _userInfoHeadWidget(context, _userInfo, UserInfoType.my),),],),),),
),

看上去没有什么问题,但是tabbar无论如何不能被收起,后来无意在github上发现,改为以下可实现:

SliverAppBar(expandedHeight: ScreenUtils.px2dp(1206),flexibleSpace: SingleChildScrollView(physics: NeverScrollableScrollPhysics(),child: Container(child: Column(children: <Widget>[AppBar(backgroundColor: Colors.black,),_userInfoHeadWidget(context, _userInfo, UserInfoType.my),],),),),

其实思想就是把用户头像appbar的flexiblespace里,同时设置flexiblespace可滚动。这样,tarbar就可以收起了。

4、结语

经过一些踩坑,一个在flutter下的加载更多就完成了。总体来说,flutter的开发是比Android开发效率高。不过,目前还是很不成熟,在Android中一句话可以搞定的事情,在flutter中确不一定。能够做出这个加载更多,也是站在巨人的肩膀上,感谢以下作者给予的启发。
相关参考:https://blog.csdn.net/qq_28478281/article/details/83827699
相关参考:https://juejin.im/post/5bfb9cb7e51d45592b766769
相关参考:https://stackoverflow.com/questions/48035594/flutter-notificationlistener-with-scrollnotification-vs-scrollcontroller
相关参考:https://github.com/xuelongqy/flutter_easyrefresh/blob/master/README.md


http://chatgpt.dhexx.cn/article/tGdBR1tO.shtml

相关文章

Android BaseRecyclerViewAdapterHelper上拉加载更多

private boolean isErr false; //是否加载错误 private int TOTAL_COUNTER 16; //一共模拟加载16条数据&#xff0c;所有的数据总数 private int mCurrentCounter 6; //当前的数据总数&#xff0c;因为第一次默认加载6个//1.上拉加载mImgDetailsAdapter.setOnLoadMoreListen…

使用SmartRefreshLayout下拉刷新框架实现加载更多

使用 SmartRefreshLayout 可以实现recyclerview的下拉刷新和上拉加载更多&#xff0c;但是有时候在使用上拉加载更多时&#xff0c;不同的json格式需要不同的判断方法 1、后台根据每次相加的totalElements的个数作为请求的参数即pageSize12 pageNo1 {"content":[{…

Android recyclerview上拉加载更多

记录一下视频列表&#xff08;recyclerview&#xff09;上拉显示一个“加载更多”的item&#xff0c;然后加载数据。 效果图&#xff1a; 实现思路&#xff1a; 1.写两个item布局&#xff0c;一个用来展示数据&#xff0c;一个用来展示加载中也就是滑到最下方的时候显示的“…

vue 点击加载更多

然后按照需求一步步来&#xff0c;先写基本页面 如果不想看过程&#xff0c;直接去后面完整代码那就可以 <template><div><div><div class"Journalism" ref"div"><div v-for"item in todos" :key"item.id&quo…

uniapp实现点击加载更多

使用场景 举个栗子&#xff1a;外卖app当订单商品数量比较多时&#xff0c;不方便一次性展示出来。通常会展示一部分&#xff0c;并在页面给出一个查看更多功能。点击后即可自定义展示剩余内容&#xff0c;比如可以每次点击多展示N个内容&#xff0c;或展示所有。 实现效果 实…

RecyclerView系列之加载更多

一、背景 很久很久以前简单封装过一次RecyclerView&#xff0c;后来一直开发TV端&#xff0c;列表的需求花样不是很多&#xff0c;主要是解决TV端各种兼容问题&#xff0c;也没怎么琢磨这个美丽的控件了&#xff0c;现在打算重新整理一下&#xff0c;其实网上已经有很多优秀的开…

js页面中实现加载更多功能

分页-如何实现加载更多功能&#xff0c;目前的在很多网站上使用的加载更多功能中&#xff0c;使用最多的是iscroll.js实现的上拉加载更多、下拉刷新功能。但是iscroll.js本身并没有集成加载更多的功能&#xff0c;需要进行自行扩展。 最简单的就是给一个加载更多的按钮&#xf…

微信小程序之加载更多(分页加载)实例

1.业务需求&#xff1a;列表滚动到底部时&#xff0c;继续往上拉&#xff0c;加载更多内容 2.必传参数&#xff1a; &#xff08;1&#xff09;page: 1 //第几次加载 &#xff08;2&#xff09;limit: 5//每次加载的显示数据条数 &#xff08;3&#xff09;total: null //需要返…

自己收藏整理的一些操作系统资源

在CSDN混迹这么多年 感觉在技术宽度和广度都深不可测的C站 Windows方面的技术相对较少一些 今天&#xff0c;借着寻找C站宝藏的活动 介绍一些C站宝藏的 Windows相关资源技术专栏 附带一下猎奇操作系统的资源~~~ 【操作系统资源&#xff1a;吐血整理&#xff0c;建议收藏&a…

写代码爬取了某 Hub 资源,只为撸这个鉴黄平台!

黄色已经是我们所不容然而却防不胜防的&#xff0c;尤其是对于做内容的工具和平台&#xff0c;所以花了30分钟搭建了一个鉴黄平台&#xff0c;分享给大家。 &#xfeff; 数据准备 &#xfeff; 找了 N 多资源都不能解决问题&#xff0c;于是怒爬某 Bub资料&#xff0c;备用…

QGC(QGroundControl)地面站手把手教你改——高德地图的添加和瓦片地图在线资源

如何添加高德地图和瓦片地图在线资源 1. 演示效果2. 代码添加3. 瓦片地图在线资源3.1 高德地图3.2 天地图3.3 其它地图源相关链接 所有的热爱都要不遗余力&#xff0c;真正喜欢它便给它更高的优先级&#xff0c;和更多的时间吧&#xff01; 关于QGC地面站其它文章请点击这里: …

系统硬件资源测算

上一篇写到了架构在规划时&#xff0c;应该做哪些&#xff1f;当项目启动后&#xff0c;资源的需求就会提上议程&#xff0c;包括人力资源、项目所需的软件资源、硬件资源以及其他资源。而今天想探讨的是很少被触及的硬件资源。因为硬件资源的规划往往都是经验值的积累&#xf…

在线学习Java的资源网站

CodeGym&#xff08;https://codegym.cc/&#xff09;&#xff1a;一个在线Java编程课程&#xff0c;80%的内容是练习&#xff0c;适合一窍不通的入门者。 CodeAcademy&#xff08;https://www.codecademy.com/&#xff09;&#xff1a;该课程注重的是在找工作时非常有用的技术…

RTSP在线视频流资源地址

在线视频流地址&#xff1a; rtsp://wowzaec2demo.streamlock.net/vod/mp4:BigBuckBunny_115k.mov 真机显示界面: 模拟器显示界面: 学好一门语言&#xff0c;动手去练&#xff0c;半天上手&#xff0c;一星期炉火纯青。—— 专攻无人车的学长

在线地图资源

一、ARCGIS在线地图资源 1&#xff0c;全球服务地址目录&#xff1a; http://services.arcgisonline.com/arcgis/rest/services 影像&#xff1a; http://services.arcgisonline.com/arcgis/rest/services/World_Imagery/MapServer?fjsapi 电子地图&#xff1a; ht…

【ArcGIS微课1000例】0025:ArcGIS Online当前未连接到在线资源终极解决办法

ArcGIS Online在线资源列表: World Imagery: 底图服务: 中国地图彩色版: 打开ArcGIS时,系统托盘提示“ArcGIS Online当前未连接到在线资源”,如下图所示,如果无法连接到ArcGIS Online,则就无法添加在线资源,如World Imagery等。 关于该问题,网上有多种解决办法,然而…

赶紧收藏3个免费在线资源齐全的网站

非搜 是一个综合搜索网站&#xff0c;能同时获得多个网站上的搜索结果展示&#xff0c;在APP上用户也能自己添加网站&#xff0c;除了看所有影视&#xff0c;还有小说&#xff0c;漫画&#xff0c;招聘&#xff0c;搜索&#xff0c;等搜索类型。 优点&#xff1a;不光包含影视…

arcgis当前未连接到在线资源

显示 arcgis当前未连接到在线资源时图标会打叉&#xff0c;一般按步骤破解的都没问题&#xff0c;这种情况应该是突然出现的&#xff0c;具体原因应该是你下了什么病毒软件或者操作损坏了你的网卡配置&#xff0c;这种缺陷不至于来连不了网&#xff0c;但访问界面可能有异常&am…

8个压箱底的资源网站,一个顶十个,再也不用到处找资源了

分享8个压箱底的资源网站&#xff0c;个个都让人相见恨晚&#xff0c;免费且资源丰富&#xff0c;一个顶几十个&#xff0c;有了它们就有了用不完的资源&#xff01; 1、电子书资源&#xff1a;Libgen 一个超好用的电子书搜索下载网站&#xff0c;里面的资源非常丰富&#xf…

微信排行榜主域和子域的操作

在排行榜列表UI加一个 2D精灵&#xff08;最下方&#xff09;初始化子域舞台 在初始化微信平台的时候调用 3.设置子域属性&#xff08;childCanvas 是第一步创建的2D精灵&#xff09; 在当前类的构造或者初始化调用 打开或者关闭排行榜 向子域发送消息 数据上报的key要和 子域获…