Flutter 分页列表页面实现指南

张开发
2026/5/10 14:01:46 15 分钟阅读
Flutter 分页列表页面实现指南
Flutter 分页列表页面实现指南在移动端开发中分页列表是最常见的场景之一。本文将分享一套经过实践检验的通用分页列表实现模式一、架构分层清晰的架构分层是代码可维护性的基础。我们采用三层架构UI 层ui 包 XxxView ← 纯 UIwatch provider触发 init/loadMore 状态层ckzl_core 包 XxxController ← Notifier管理分页状态调用 Service XxxState ← 不可变状态对象 数据层ckzl_core 包 XxxService ← HTTP 封装分页查询接口职责划分· UI 层只负责展示和用户交互不包含业务逻辑· 状态层管理页面状态处理分页逻辑调用数据层接口· 数据层封装网络请求与后端 API 交互二、State 设计状态对象应该是不可变的这样可以保证状态变化的可预测性classXxxState{finalbool isLoading;// 首次加载中finalbool isLoadingMore;// 加载更多中finalListXxxDataitems;finalint pageNum;// 当前页码finalint pageSize;// 每页数量finalint total;// 总记录数finalbool hasMore;// 是否还有更多finalString?error;// 错误信息XxxState({this.isLoadingfalse,this.isLoadingMorefalse,this.itemsconst[],this.pageNum1,this.pageSize20,this.total0,this.hasMoretrue,this.error,});XxxStatecopyWith({...}){...}} 提示使用 copyWith 方法可以方便地创建新状态实例这是不可变对象的常见模式。三、Controller 核心实现Controller 负责状态管理和业务逻辑核心是 init() 和 loadMore() 两个方法方法 说明init() 重置状态从第1页重新加载下拉刷新也调这个loadMore() 加载下一页hasMore isLoadingMore 双重防重classXxxControllerextendsNotifierXxxState{overrideXxxStatebuild()XxxState();Futurevoidinit()async{statestate.copyWith(isLoading:true,pageNum:1,items:[],hasMore:true,);await_fetch(pageNum:1,refresh:true);}FuturevoidloadMore()async{if(!state.hasMore||state.isLoadingMore)return;statestate.copyWith(isLoadingMore:true);await_fetch(pageNum:state.pageNum1,refresh:false);}Futurevoid_fetch({required int pageNum,required bool refresh})async{finalrespawaitXxxService.instance.listXxx(pageNum:pageNum,pageSize:state.pageSize,);if(resp.isSuccessresp.data!null){finalnewItemsresp.data!.items;statestate.copyWith(isLoading:false,isLoadingMore:false,items:refresh?newItems:[...state.items,...newItems],pageNum:pageNum,total:resp.data!.total,hasMore:newItems.lengthstate.pageSize,);}else{statestate.copyWith(isLoading:false,isLoadingMore:false,error:resp.msg,);}}}关键点说明· 双重防重hasMore 确保还有数据时才能继续加载isLoadingMore 防止重复请求· 分页判断返回数据量 pageSize 则继续分页否则认为已无更多数据· 错误处理将错误信息保存到 stateUI 层可以据此展示错误提示四、Provider 注册使用 Riverpod 的 NotifierProvider 注册 ControllerfinalxxxControllerProviderNotifierProviderXxxController,XxxState(XxxController.new,);五、UI 页面实现5.1 基础结构classXxxViewextendsConsumerStatefulWidget{constXxxView({super.key});overrideConsumerStateXxxViewcreateState()_XxxViewState();}class_XxxViewStateextendsConsumerStateXxxView{finalScrollController_scrollControllerScrollController();overridevoidinitState(){super.initState();_scrollController.addListener(_onScroll);WidgetsBinding.instance.addPostFrameCallback((_){ref.read(xxxControllerProvider.notifier).init();});}// 滚动监听距底部 200px 时触发加载更多void_onScroll(){if(_scrollController.position.pixels_scrollController.position.maxScrollExtent-200){ref.read(xxxControllerProvider.notifier).loadMore();}}overrideWidgetbuild(BuildContextcontext){finalstateref.watch(xxxControllerProvider);returnScaffold(body:RefreshIndicator(onRefresh:()asyncref.read(xxxControllerProvider.notifier).init(),child:_buildBody(state),),);}}5.2 列表主体CustomScrollView Sliver推荐使用 CustomScrollView Sliver 组合这样可以灵活控制列表的各个部分Widget_buildBody(XxxStatestate){if(state.isLoadingstate.items.isEmpty){returnconstCenter(child:CircularProgressIndicator());}if(state.error!nullstate.items.isEmpty){returnCenter(child:Text(加载失败:${state.error}));}returnCustomScrollView(controller:_scrollController,slivers:[// 主列表区域 - 根据布局选择 SliverGrid 或 SliverListSliverGrid(gridDelegate:constSliverGridDelegateWithFixedCrossAxisCount(crossAxisCount:3,crossAxisSpacing:4,mainAxisSpacing:4,),delegate:SliverChildBuilderDelegate((context,index)_buildItem(state.items[index]),childCount:state.items.length,),),// 加载更多 Loadingif(state.isLoadingMore)constSliverToBoxAdapter(child:Padding(padding:EdgeInsets.all(16),child:Center(child:CircularProgressIndicator()),),),// 没有更多提示if(!state.hasMorestate.items.isNotEmpty)constSliverToBoxAdapter(child:Padding(padding:EdgeInsets.all(16),child:Center(child:Text(没有更多了)),),),],);}六、完整流程图ServiceControllerUIUserServiceControllerUIUseralt[有更多数据且未加载中][无更多数据或加载中]进入页面/下拉刷新init()重置状态listXxx(pageNum1)返回数据更新 state渲染列表滚动到底部loadMore()listXxx(pageNum1)返回数据追加数据渲染新增项忽略请求七、复用新页面的步骤当你需要实现一个新的分页列表页面时只需按照以下步骤操作步骤 操作 说明1 新建 XxxState 包含 isLoading / isLoadingMore / items / pageNum / hasMore / error 等字段2 新建 XxxController 实现 init() / loadMore() / _fetch() 方法3 新建 Service 方法 返回 ApiResponse响应体包含 items total4 新建 UI 页面 添加 ScrollController 监听滚动RefreshIndicator 下拉刷新CustomScrollView 渲染列表5 注册 Provider 使用 NotifierProvider 注册 Controller八、常见问题与优化建议8.1 空状态处理当列表为空时建议显示空状态占位图if(state.items.isEmpty!state.isLoading){returnconstCenter(child:EmptyStateWidget(message:暂无数据));}8.2 错误重试在错误状态中提供重试按钮if(state.error!nullstate.items.isEmpty){returnCenter(child:Column(mainAxisAlignment:MainAxisAlignment.center,children:[Text(加载失败:${state.error}),constSizedBox(height:16),ElevatedButton(onPressed:()ref.read(xxxControllerProvider.notifier).init(),child:constText(重试),),],),);}8.3 滚动优化· 设置 cacheExtent 可以提前缓存列表项提升滚动流畅度· 对于图片列表建议配合缓存框架如 cached_network_image使用结语这套分页列表模式经过多个项目的验证具备良好的复用性和可维护性。核心思想是状态与 UI 分离、分页逻辑统一封装新页面只需替换数据模型和 Service 即可快速实现。

更多文章