正规外贸网站建设公司,北京建设银行支行查询官方网站,wordpress 5.1,wordpress get_attached_media在 Flutter 开发中#xff0c;列表#xff08;商品列表、消息列表、订单列表#xff09;是高频场景。原生RefreshIndicator仅支持下拉刷新#xff0c;上拉加载需手动监听滚动、管理加载状态#xff0c;且空数据、错误等异常状态需重复开发。本文封装的CommonRefreshList整…在 Flutter 开发中列表商品列表、消息列表、订单列表是高频场景。原生RefreshIndicator仅支持下拉刷新上拉加载需手动监听滚动、管理加载状态且空数据、错误等异常状态需重复开发。本文封装的CommonRefreshList整合 “下拉刷新 上拉加载 空状态 错误状态 加载中状态” 五大核心能力支持分页逻辑、自定义状态样式一行代码集成覆盖 95% 列表场景彻底解放重复编码一、核心优势精准解决开发痛点✅ 状态全适配内置加载中、空数据、错误、无更多数据 4 种异常状态无需手动判断切换✅ 刷新加载整合下拉刷新与上拉加载逻辑封装无需单独处理滚动监听和状态管理✅ 分页逻辑内置支持页码 / 游标分页自动管理pageIndex和hasMore状态减少重复代码✅ 高扩展性下拉刷新样式、上拉加载提示、各状态页面均可自定义适配不同设计风格✅ 性能优化列表项复用、加载状态防重复触发避免多次请求适配大数据列表✅ 交互友好错误状态支持点击重试、下拉刷新动画流畅、上拉加载触发阈值合理贴合用户习惯二、核心配置速览关键参数一目了然配置分类核心参数核心作用必选配置itemBuilder、onLoadData列表项构建器渲染单个列表项、数据加载回调分页请求数据刷新配置enablePullDown、onRefresh是否启用下拉刷新默认 true、自定义刷新回调优先级高于默认逻辑加载配置enablePullUp、pageSize、hasMore是否启用上拉加载默认 true、每页数据量默认 10、是否有更多数据外部控制状态配置emptyWidget、errorWidget等空数据、错误、加载中、无更多数据的自定义组件支持个性化设计列表配置controller、itemExtent、padding滚动控制器外部监听滚动、列表项固定高度优化性能、内边距三、生产级完整代码可直接复制开箱即用dartimport package:flutter/material.dart; import package:flutter_easyloading/flutter_easyloading.dart; /// 列表加载状态枚举统一管理所有状态逻辑清晰 enum ListLoadStatus { loading, // 初始加载中 empty, // 空数据 error, // 加载错误 success, // 加载成功有数据 noMore // 上拉加载无更多 } /// 通用列表刷新加载组件支持下拉刷新、上拉加载、多状态适配 class CommonRefreshListT extends StatefulWidget { // 必选参数核心依赖 final Widget Function(BuildContext, T, int) itemBuilder; // 列表项构建器context, 数据, 索引 final FutureListT Function(int pageIndex, int pageSize) onLoadData; // 数据加载回调页码, 页大小 // 刷新配置下拉刷新相关 final bool enablePullDown; // 是否启用下拉刷新默认true final Futurevoid Function()? onRefresh; // 自定义下拉刷新回调优先级高于默认逻辑 final Color refreshColor; // 刷新指示器颜色默认蓝色 final double refreshTriggerDistance; // 下拉刷新触发距离默认100px // 加载配置上拉加载相关 final bool enablePullUp; // 是否启用上拉加载默认true final int pageSize; // 每页数据量默认10 final int initialPageIndex; // 初始页码默认1 final bool hasMore; // 是否有更多数据外部控制默认true final String loadMoreText; // 上拉加载提示文本默认正在加载更多... final String noMoreText; // 无更多数据提示文本默认没有更多数据了 final TextStyle loadTextStyle; // 加载提示文本样式默认14号灰色 // 状态配置各异常状态组件 final Widget? loadingWidget; // 初始加载中组件自定义样式 final Widget? emptyWidget; // 空数据组件自定义样式 final Widget? errorWidget; // 错误组件点击可重试自定义样式 final Widget? noMoreWidget; // 无更多数据组件自定义样式 // 列表配置基础样式与性能优化 final ScrollController? controller; // 滚动控制器外部传入可监听滚动位置 final ScrollPhysics? physics; // 滚动物理效果默认适配平台 final EdgeInsetsGeometry? padding; // 列表内边距默认无 final double? itemExtent; // 列表项固定高度优化滚动性能推荐设置 final bool shrinkWrap; // 是否适应子组件高度默认false避免列表高度异常 const CommonRefreshList({ super.key, required this.itemBuilder, required this.onLoadData, // 刷新配置 this.enablePullDown true, this.onRefresh, this.refreshColor Colors.blue, this.refreshTriggerDistance 100.0, // 加载配置 this.enablePullUp true, this.pageSize 10, this.initialPageIndex 1, this.hasMore true, this.loadMoreText 正在加载更多..., this.noMoreText 没有更多数据了, this.loadTextStyle const TextStyle(fontSize: 14, color: Colors.grey), // 状态配置 this.loadingWidget, this.emptyWidget, this.errorWidget, this.noMoreWidget, // 列表配置 this.controller, this.physics, this.padding, this.itemExtent, this.shrinkWrap false, }); override StateCommonRefreshListT createState() _CommonRefreshListStateT(); } class _CommonRefreshListStateT extends StateCommonRefreshListT { late ScrollController _scrollController; // 滚动控制器复用外部传入或新建 late ListT _dataList; // 列表数据源 late int _currentPage; // 当前页码 late ListLoadStatus _loadStatus; // 列表加载状态 bool _isLoadingMore false; // 上拉加载锁防重复请求 bool _isRefreshing false; // 下拉刷新锁防重复请求 override void initState() { super.initState(); // 初始化滚动控制器外部传入则复用内部新建则自行管理生命周期 _scrollController widget.controller ?? ScrollController(); // 初始化数据与状态 _dataList []; _currentPage widget.initialPageIndex; _loadStatus ListLoadStatus.loading; // 监听滚动事件触发上拉加载 _scrollController.addListener(_onScroll); // 初始加载数据 _initLoadData(); } override void didUpdateWidget(covariant CommonRefreshListT oldWidget) { super.didUpdateWidget(oldWidget); // 外部控制hasMore变化时恢复加载状态支持重新加载更多 if (widget.hasMore ! oldWidget.hasMore _loadStatus ListLoadStatus.noMore) { setState(() _loadStatus ListLoadStatus.success); } } override void dispose() { // 外部传入的控制器由外部管理内部新建的需手动释放 if (widget.controller null) _scrollController.dispose(); super.dispose(); } /// 初始加载数据首次进入页面触发 Futurevoid _initLoadData() async { try { final data await widget.onLoadData(_currentPage, widget.pageSize); setState(() { _dataList data; // 根据返回数据判断状态空数据→empty有数据→success _loadStatus data.isEmpty ? ListLoadStatus.empty : ListLoadStatus.success; }); } catch (e) { setState(() _loadStatus ListLoadStatus.error); EasyLoading.showError(加载失败${e.toString()}); } } /// 下拉刷新逻辑重置页码重新加载第一页 Futurevoid _handleRefresh() async { if (_isRefreshing) return; // 防重复刷新 _isRefreshing true; try { _currentPage widget.initialPageIndex; // 重置页码 final newData await widget.onLoadData(_currentPage, widget.pageSize); setState(() { _dataList newData; _loadStatus newData.isEmpty ? ListLoadStatus.empty : ListLoadStatus.success; }); } catch (e) { EasyLoading.showError(刷新失败${e.toString()}); } finally { _isRefreshing false; // 释放刷新锁 } } /// 上拉加载逻辑页码1追加数据 Futurevoid _handleLoadMore() async { // 防重复加载正在加载中/无更多数据/非成功状态→不触发 if (_isLoadingMore || !widget.hasMore || _loadStatus ! ListLoadStatus.success) return; _isLoadingMore true; try { _currentPage; // 页码自增 final newData await widget.onLoadData(_currentPage, widget.pageSize); setState(() { if (newData.isEmpty) { _loadStatus ListLoadStatus.noMore; // 无更多数据 } else { _dataList.addAll(newData); // 追加新数据 } }); } catch (e) { _currentPage--; // 加载失败回退页码避免跳过当前页 EasyLoading.showError(加载更多失败${e.toString()}); } finally { _isLoadingMore false; // 释放加载锁 } } /// 滚动监听判断是否触发上拉加载 void _onScroll() { if (!widget.enablePullUp) return; // 滚动到列表底部100px内且未在加载中→触发加载更多 if (_scrollController.position.pixels _scrollController.position.maxScrollExtent - 100 !_isLoadingMore) { _handleLoadMore(); } } /// 重试加载错误状态点击触发 void _onRetry() { setState(() _loadStatus ListLoadStatus.loading); _initLoadData(); } /// 构建初始加载中组件默认自定义适配 Widget _buildLoadingWidget() { return widget.loadingWidget ?? const Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ CircularProgressIndicator(strokeWidth: 2), SizedBox(height: 16), Text(正在加载中..., style: TextStyle(color: Colors.grey)), ], ), ); } /// 构建空数据组件默认自定义适配 Widget _buildEmptyWidget() { return widget.emptyWidget ?? Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.inbox_outlined, size: 64, color: Colors.grey[300]), const SizedBox(height: 16), const Text(暂无数据, style: TextStyle(color: Colors.grey, fontSize: 16)), ], ), ); } /// 构建错误组件默认自定义适配支持点击重试 Widget _buildErrorWidget() { return widget.errorWidget ?? Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.error_outline, size: 64, color: Colors.grey[300]), const SizedBox(height: 16), const Text(加载失败, style: TextStyle(color: Colors.grey, fontSize: 16)), const SizedBox(height: 8), TextButton( onPressed: _onRetry, child: const Text(点击重试), ), ], ), ); } /// 构建无更多数据组件默认自定义适配 Widget _buildNoMoreWidget() { return widget.noMoreWidget ?? Padding( padding: const EdgeInsets.symmetric(vertical: 16), child: Center(child: Text(widget.noMoreText, style: widget.loadTextStyle)), ); } /// 构建上拉加载提示组件加载中状态 Widget _buildLoadMoreWidget() { return Padding( padding: const EdgeInsets.symmetric(vertical: 16), child: Center( child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ const CircularProgressIndicator(strokeWidth: 2), const SizedBox(width: 8), Text(widget.loadMoreText, style: widget.loadTextStyle), ], ), ), ); } /// 构建列表主体包含正常列表项加载更多/无更多提示 Widget _buildListBody() { return ListView.builder( controller: _scrollController, physics: widget.physics, padding: widget.padding, itemExtent: widget.itemExtent, // 固定高度优化性能 shrinkWrap: widget.shrinkWrap, // 列表项数量数据量1最后一项显示加载更多/无更多 itemCount: _dataList.length (widget.enablePullUp ? 1 : 0), itemBuilder: (context, index) { // 最后一项显示加载更多或无更多 if (widget.enablePullUp index _dataList.length) { return _loadStatus ListLoadStatus.noMore ? _buildNoMoreWidget() : _buildLoadMoreWidget(); } // 正常列表项通过itemBuilder渲染 return widget.itemBuilder(context, _dataList[index], index); }, ); } override Widget build(BuildContext context) { // 根据加载状态显示对应页面 Widget child; switch (_loadStatus) { case ListLoadStatus.loading: child _buildLoadingWidget(); break; case ListLoadStatus.empty: child _buildEmptyWidget(); break; case ListLoadStatus.error: child _buildErrorWidget(); break; case ListLoadStatus.success: case ListLoadStatus.noMore: child _buildListBody(); break; } // 包裹下拉刷新组件启用时 if (widget.enablePullDown) { child RefreshIndicator( color: widget.refreshColor, triggerMode: RefreshIndicatorTriggerMode.onEdge, displacement: widget.refreshTriggerDistance, onRefresh: widget.onRefresh ?? _handleRefresh, // 优先使用自定义刷新逻辑 child: child, ); } return child; } }四、三大高频场景实战示例直接复制可用场景 1基础分页列表商品列表支持下拉刷新 上拉加载适用场景电商商品列表、资讯列表等需要分页加载的场景dartclass ProductListPage extends StatefulWidget { override StateProductListPage createState() _ProductListPageState(); } class _ProductListPageState extends StateProductListPage { bool _hasMore true; // 控制是否有更多商品 // 商品数据模型实际项目可替换为真实模型 class Product { final String id; final String name; final double price; final String imageUrl; Product({required this.id, required this.name, required this.price, required this.imageUrl}); } // 模拟加载商品数据实际项目替换为接口请求 FutureListProduct _loadProductData(int pageIndex, int pageSize) async { await Future.delayed(const Duration(1000)); // 模拟网络延迟 // 模拟第3页无更多数据 if (pageIndex 3) { setState(() _hasMore false); return []; } // 生成模拟数据 return List.generate(pageSize, (index) { final realIndex (pageIndex - 1) * pageSize index; return Product( id: prod_$realIndex, name: 2025新款夏季T恤 $realIndex, price: 99.0 realIndex * 10, imageUrl: https://picsum.photos/200/200?random$realIndex, ); }); } // 构建商品列表项 Widget _buildProductItem(BuildContext context, Product product, int index) { return Container( padding: const EdgeInsets.all(12), margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( borderRadius: BorderRadius.circular(8), border: Border.all(color: Colors.grey[200]!), ), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 商品图片 ClipRRect( borderRadius: BorderRadius.circular(6), child: Image.network( product.imageUrl, width: 80, height: 80, fit: BoxFit.cover, ), ), const SizedBox(width: 12), // 商品信息 Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( product.name, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w500), maxLines: 2, overflow: TextOverflow.ellipsis, ), const SizedBox(height: 8), Text( ¥${product.price.toStringAsFixed(2)}, style: const TextStyle(fontSize: 16, color: Colors.redAccent), ), ], ), ), // 加入购物车按钮 IconButton( icon: const Icon(Icons.add_shopping_cart, color: Colors.grey), onPressed: () EasyLoading.showToast(添加 ${product.name} 到购物车), ), ], ), ); } override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text(商品列表)), body: CommonRefreshListProduct( itemBuilder: _buildProductItem, onLoadData: _loadProductData, hasMore: _hasMore, pageSize: 8, // 每页8条数据 enablePullDown: true, enablePullUp: true, padding: const EdgeInsets.symmetric(vertical: 8), // 自定义空状态组件贴合商品场景 emptyWidget: const Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.shopping_bag_outlined, size: 64, color: Colors.grey[300]), SizedBox(height: 16), Text(暂无商品数据, style: TextStyle(color: Colors.grey, fontSize: 16)), SizedBox(height: 8), Text(换个关键词试试吧~, style: TextStyle(color: Colors.grey[500], fontSize: 14)), ], ), ), ), ); } }场景 2无分页列表消息列表仅下拉刷新适用场景消息列表、通知列表等无需分页仅需下拉刷新的场景dartclass MessageListPage extends StatefulWidget { override StateMessageListPage createState() _MessageListPageState(); } class _MessageListPageState extends StateMessageListPage { // 消息数据模型 class Message { final String id; final String title; final String content; final String time; final bool isRead; // 是否已读 Message({ required this.id, required this.title, required this.content, required this.time, this.isRead false, }); } // 加载消息数据无分页仅下拉刷新 FutureListMessage _loadMessageData(int pageIndex, int pageSize) async { await Future.delayed(const Duration(800)); // 模拟网络延迟 // 生成模拟消息数据 return List.generate(15, (index) { return Message( id: msg_$index, title: index % 3 0 ? 系统通知 : 好友消息, content: 这是一条测试消息内容用于展示列表项样式 $index, time: ${10 index}:${index * 5}, isRead: index 5, // 前5条为未读 ); }); } // 构建消息列表项 Widget _buildMessageItem(BuildContext context, Message message, int index) { return ListTile( leading: CircleAvatar( child: Text(message.title.substring(0, 1)), backgroundColor: message.isRead ? Colors.grey[200] : Colors.blue, foregroundColor: message.isRead ? Colors.grey : Colors.white, ), title: Text( message.title, style: TextStyle( fontWeight: message.isRead ? FontWeight.normal : FontWeight.bold, ), ), subtitle: Text( message.content, maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle( color: message.isRead ? Colors.grey : Colors.black87, ), ), trailing: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text(message.time, style: const TextStyle(fontSize: 12, color: Colors.grey)), // 未读红点 if (!message.isRead) const SizedBox( width: 8, height: 8, child: CircleAvatar(backgroundColor: Colors.red), ), ], ), onTap: () EasyLoading.showToast(查看消息${message.title}), ); } override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text(消息列表)), body: CommonRefreshListMessage( itemBuilder: _buildMessageItem, onLoadData: _loadMessageData, enablePullDown: true, enablePullUp: false, // 关闭上拉加载无分页 refreshColor: Colors.orangeAccent, // 自定义刷新颜色 // 自定义错误状态组件 errorWidget: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon(Icons.message_outlined, size: 64, color: Colors.grey[300]), const SizedBox(height: 16), const Text(消息加载失败, style: TextStyle(color: Colors.grey, fontSize: 16)), const SizedBox(height: 8), ElevatedButton( onPressed: () {}, child: const Text(重新加载), ), ], ), ), ), ); } }场景 3固定高度列表订单列表优化滚动性能适用场景订单列表、账单列表等列表项高度固定的场景性能更优dartclass OrderListPage extends StatefulWidget { override StateOrderListPage createState() _OrderListPageState(); } class _OrderListPageState extends StateOrderListPage { bool _hasMore true; // 控制是否有更多订单 // 订单数据模型 class Order { final String orderNo; final double amount; final String status; final String time; Order({ required this.orderNo, required this.amount, required this.status, required this.time, }); } // 加载订单数据 FutureListOrder _loadOrderData(int pageIndex, int pageSize) async { await Future.delayed(const Duration(1000)); // 模拟网络延迟 // 模拟第4页无更多数据 if (pageIndex 4) { setState(() _hasMore false); return []; } // 生成模拟订单数据 return List.generate(pageSize, (index) { final realIndex (pageIndex - 1) * pageSize index; final statusList [待支付, 已支付, 已取消, 已完成]; return Order( orderNo: ORDER${DateTime.now().year}${10000 realIndex}, amount: 50.0 realIndex * 20, status: statusList[realIndex % 4], time: 2024-12-${10 realIndex % 20} 1${realIndex % 9}:${realIndex % 59}, ); }); } // 构建订单状态标签 Widget _buildStatusTag(String status) { Color color; switch (status) { case 待支付: color Colors.orangeAccent; break; case 已支付: color Colors.green; break; case 已取消: color Colors.grey; break; case 已完成: color Colors.blue; break; default: color Colors.grey; } return Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), decoration: BoxDecoration( color: color.withOpacity(0.1), borderRadius: BorderRadius.circular(4), ), child: Text( status, style: TextStyle(color: color, fontSize: 12), ), ); } override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text(订单列表)), body: CommonRefreshListOrder( // 构建订单列表项 itemBuilder: (context, order, index) { return Container( margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), padding: const EdgeInsets.all(12), decoration: BoxDecoration( borderRadius: BorderRadius.circular(8), border: Border.all(color: Colors.grey[200]!), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 订单号和状态 Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text(订单号${order.orderNo}, style: const TextStyle(fontSize: 14, color: Colors.grey)), _buildStatusTag(order.status), ], ), const SizedBox(height: 8), // 订单金额 Text(订单金额¥${order.amount.toStringAsFixed(2)}, style: const TextStyle(fontSize: 16)), const SizedBox(height: 8), // 下单时间和操作 Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text(order.time, style: const TextStyle(fontSize: 14, color: Colors.grey)), TextButton( onPressed: () EasyLoading.showToast(查看订单详情${order.orderNo}), child: const Text(查看详情), ), ], ), ], ), ); }, onLoadData: _loadOrderData, hasMore: _hasMore, pageSize: 6, itemExtent: 140, // 固定列表项高度大幅提升滚动性能 padding: const EdgeInsets.symmetric(vertical: 8), // 自定义无更多数据组件 noMoreWidget: Padding( padding: const EdgeInsets.symmetric(vertical: 20), child: Center( child: Text(已加载全部订单, style: widget.loadTextStyle.copyWith(fontSize: 15)), ), ), ), ); } }五、核心封装技巧复用成熟设计思路状态分层管理通过ListLoadStatus枚举统一管理 5 种状态避免分散判断状态切换逻辑清晰外部无需关心内部状态流转。防重复触发机制通过_isLoadingMore和_isRefreshing加载锁防止滚动或下拉时多次触发请求避免接口压力和数据错乱。分页逻辑解耦页码管理、数据追加、无更多判断等逻辑内置外部仅需实现onLoadData回调返回数据无需重复编写分页逻辑。组件插槽化设计各状态页面空、错误、加载中支持外部自定义兼顾通用性和个性化适配不同 APP 设计风格。性能优化细节支持itemExtent固定列表项高度减少 ListView 布局计算复用外部传入的ScrollController便于监听滚动位置实现吸顶等扩展功能。错误恢复机制上拉加载失败时自动回退页码错误状态支持点击重试提升用户体验避免数据丢失。六、避坑指南解决 90% 开发痛点数据状态同步hasMore需外部根据接口返回结果更新如无更多数据时设为false否则会持续触发上拉加载。页码回退关键上拉加载失败时必须回退_currentPage否则下次加载会跳过当前页导致数据断层。控制器生命周期外部传入的ScrollController需由外部管理dispose内部新建的控制器会自动释放避免内存泄漏。空状态适配初始加载为空时显示空状态下拉刷新后数据为空也会切换为空状态需确保onLoadData返回空列表时状态正确切换。性能优化建议列表项高度固定时务必设置itemExtent大数据列表超过 50 项建议配合ListView.builder复用特性避免卡顿。自定义刷新逻辑如需自定义下拉刷新如清除缓存可通过onRefresh回调实现优先级高于默认逻辑灵活适配特殊需求。欢迎大家加入[开源鸿蒙跨平台开发者社区](https://openharmonycrossplatform.csdn.net)一起共建开源鸿蒙跨平台生态。