首页
苏兮影视
随笔记
壁纸
更多
直播
时光轴
友联
关于
统计
Search
1
v2ray节点搭建
1,001 阅读
2
软件添加id功能按钮
912 阅读
3
QQ扫码无法登录的解决方案
809 阅读
4
网易云音乐歌单ID获取教程
679 阅读
5
typecho非常有特色的模块
657 阅读
谈天说地
建站源码
经验教程
资源分享
动漫美图
登录
Search
标签搜索
java
rust
flutter
esp32c3
springboot
安卓
linux
vue
dart
设计模式
docker
joe
快捷键
git
fish shell
maven
redis
netty
groovy
js
尽意
累计撰写
112
篇文章
累计收到
39
条评论
首页
栏目
谈天说地
建站源码
经验教程
资源分享
动漫美图
页面
苏兮影视
随笔记
壁纸
直播
时光轴
友联
关于
统计
搜索到
1
篇与
的结果
Flutter模板方法模式在多Tab列表中的应用
项目背景与需求在Flutter开发中,我们经常遇到需要实现多Tab栏列表的场景,每个Tab具有相似的结构(下拉刷新、上拉加载、列表展示),但数据类型和UI展示各不相同。本文将介绍如何使用模板方法模式来优雅地解决这个问题。模板方法模式简介模板方法模式是一种行为设计模式,它在超类中定义了一个算法的框架,允许子类在不修改结构的情况下重写特定步骤。在我们的场景中,每个Tab的加载流程(初始化→刷新→加载更多)是相同的,但具体的数据加载和UI展示是不同的。代码实现解析1. 抽象基类定义const int defaultPageSize = 10; abstract class TabData<T> { final String name; // tab名称 int total; // 数据数量 bool isSelected; // 选中状态 bool hasMore; // 是否有更多数据 List<T> items; // 数据列表 bool _isInited = false; // 是否初始化完成 final RefreshController controller; final Widget Function(BuildContext context,T item) itemBuilder; TabData({ required this.name, required this.itemBuilder, this.total = 0, this.isSelected = false, this.hasMore = true, this.items = const [] }) : controller = RefreshController(); int get pageSize => defaultPageSize; Future<List<T>> loadData(int page); void updateItems(List<T> newItems, bool isRefresh); Future<bool> init() async{ if (_isInited) { return true; } _isInited = true; await onRefresh(); return false; } Future<void> onRefresh() async { try { final data = await loadData(1); updateItems(data, true); hasMore = data.length >= pageSize; controller.refreshCompleted(); } catch (e) { controller.refreshFailed(); rethrow; } } Future<void> onLoadMore() async { if (!hasMore) { controller.loadNoData(); return; } try { final currentPage = (items.length / pageSize).ceil() + 1; final data = await loadData(currentPage); updateItems(data, false); hasMore = data.length >= pageSize; controller.loadComplete(); } catch (e) { controller.refreshFailed(); rethrow; } } void dispose() { controller.dispose(); } }设计要点:定义了完整的生命周期方法(init、onRefresh、onLoadMore)使用泛型T支持不同的数据类型封装了刷新控制器和分页逻辑提供itemBuilder回调实现UI定制化2. 具体子类实现点赞Tab实现:class LikeTabData extends TabData<LikeTabDataEntity>{ LikeTabData():super(name: "点赞",itemBuilder: (context,item){ return Text(item.name); }); @override Future<List<LikeTabDataEntity>> loadData(int page) async{ await Future.delayed(const Duration(seconds: 1)); return List.generate(pageSize, (index)=> LikeTabDataEntity(name: "$name 数据 ${page * pageSize + index}")); } @override void updateItems(List<LikeTabDataEntity> newItems, bool isRefresh) { items = isRefresh ? newItems : [...items, ...newItems]; total = items.length; } }评论Tab实现:class CommentTabData extends TabData<CommentTabDataEntity>{ CommentTabData():super(name: "评论",itemBuilder: (context,item) { Widget text () => Text(item.text,style: const TextStyle(color: Colors.white)); return Container( margin: const EdgeInsets.all(8), padding: const EdgeInsets.all(10), decoration: BoxDecoration( color: Colors.blueAccent, borderRadius: BorderRadius.circular(8), ), child: text(), ); }); @override Future<List<CommentTabDataEntity>> loadData(int page) async{ await Future.delayed(const Duration(seconds: 1)); return List.generate(pageSize, (index)=> CommentTabDataEntity(text: "$name 数据 ${page * pageSize + index}")); } @override void updateItems(List<CommentTabDataEntity> newItems, bool isRefresh) { items = isRefresh ? newItems : [...items, ...newItems]; total = items.length; } }子类只需实现:loadData:具体的数据加载逻辑updateItems:数据合并策略itemBuilder:列表项UI构建3. 状态管理class DialogState extends BaseViewState { List<TabData> tabDataList = [ LikeTabData(), CommentTabData(), LikeTabData(), CommentTabData(), ]; }4. 逻辑控制器class DialogLogic extends BaseGetXLifeCycleController{ late final PageController tabController; final state = DialogState(); @override void onInit() { super.onInit(); tabController = PageController(); state.viewState = TYPE_LOAD; update(); state.tabDataList.first.init().then((_)=> update()); Future.delayed(const Duration(seconds: 1),(){ state.tabDataList.first.isSelected = true; state.viewState = TYPE_NORMAL; update(); }); } @override void onClose() { tabController.dispose(); for(final item in state.tabDataList) { item.dispose(); } super.onClose(); } void onTabChange(int index) async{ for(int i = 0; i < state.tabDataList.length; i++) { state.tabDataList[i].isSelected = index == i; } update(); final isInited = await state.tabDataList[index].init(); if(!isInited) update(); } void changeTab(int index,BuildContext context){ final screenWidth = MediaQuery.of(context).size.width; tabController.animateTo(index * screenWidth,duration: const Duration(milliseconds: 300), curve: Curves.easeInOut); } void onRefresh(Future<void> Function() refresh) async{ await refresh(); update(); } void onLoadMore(Future<void> Function() loadMore) async{ await loadMore(); update(); } }5. UI界面实现class Dialog extends BaseTitleBottomSheet with GetXControllerMixin<DialogLogic>{ Dialog({super.key}); final logic = Get.put(DialogLogic()); final state = Get.find<DialogLogic>().state; @override String getTitle() => "Dialog"; @override Widget buildContent() { return buildPartBaseView( state.viewState,context, _buildContentWidget() ); } Widget _buildContentWidget() { return Expanded( child: Column( children: [ SingleChildScrollView( child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ for(int i = 0;i<state.tabDataList.length;i++) GestureDetector(onTap: ()=> logic.changeTab(i,context),child: _buildTabWidget(state.tabDataList[i])) ], ), ), Expanded( child: PageView.builder( controller: logic.tabController, onPageChanged: logic.onTabChange, itemCount: state.tabDataList.length, itemBuilder: (_,index){ final data = state.tabDataList[index]; if(data.items.isEmpty){ return const Center(child: Text("无数据"),); } return _KeepAliveList( data, keepAlive: true, onLoading: ()=> logic.onLoadMore(data.onLoadMore), onRefresh: ()=> logic.onRefresh(data.onRefresh), controller: data.controller ); }, ), ), ], ), ); } Widget _buildTabWidget(TabData entity){ final noSelectedStyle = TextStyle(color: Colors.black,fontSize: 23.w); final selectedStyle = TextStyle(color: Colors.blueAccent,fontSize: 23.w); final style = entity.isSelected ? selectedStyle : noSelectedStyle; return Row( mainAxisSize: MainAxisSize.min, children: [ Text(entity.name,style: style), Text("(${entity.total})",style: style), ], ); } } class _KeepAliveList extends StatefulWidget { final TabData tabData; final bool keepAlive; final VoidCallback? onRefresh; final VoidCallback? onLoading; final RefreshController controller; const _KeepAliveList(this.tabData,{super.key,this.keepAlive = true,required this.controller,this.onRefresh,this.onLoading}); @override State<_KeepAliveList> createState() => _KeepAliveListState(); } class _KeepAliveListState extends State<_KeepAliveList> with AutomaticKeepAliveClientMixin{ @override Widget build(BuildContext context) { super.build(context); return SmartRefresher( controller: widget.controller, enablePullDown: true, enablePullUp: true, header: const ClassicHeader(), onRefresh: widget.onRefresh, onLoading: widget.onLoading, child: ListView.builder( itemCount: widget.tabData.items.length, itemBuilder: (context,index){ // 新版本dart可以使用switch匹配模式,可以穷举完全部密封类的子类 // 为了兼容只能使用if了 final tabData = widget.tabData; if(tabData is LikeTabData){ return tabData.itemBuilder(context,tabData.items[index]); }else if(tabData is CommentTabData){ return tabData.itemBuilder(context,tabData.items[index]); } throw "未知的TabData类型"; }, ), ); } @override bool get wantKeepAlive => widget.keepAlive; }设计优势1. 代码复用性公共的刷新、加载、分页逻辑在基类中实现新增Tab只需继承TabData并实现三个抽象方法2. 可维护性算法框架稳定,修改基类即可影响所有子类业务逻辑与UI展示分离3. 扩展性支持任意类型的列表数据可轻松添加新的Tab类型4. 一致性所有Tab保持相同的交互逻辑统一的错误处理和加载状态使用建议添加新Tab:只需创建新的TabData子类,实现loadData、updateItems和itemBuilder修改公共逻辑:在TabData基类中修改,所有子类自动生效自定义分页大小:在子类中重写pageSize getter特殊处理:子类可重写onRefresh或onLoadMore方法进行个性化处理总结通过模板方法模式,我们成功地将多Tab列表中的公共逻辑提取到基类中,同时保留了子类的灵活性。这种设计模式特别适用于具有相似流程但不同实现的场景,能够显著减少代码重复,提高项目的可维护性和扩展性。
2026年01月16日
3 阅读
0 评论
1 点赞