Flutter:订单列表记录:TabBar+TabBarView+分页的实现demo

本代码记录的TabBar+TabBarView+分页SmartRefresher的实现方案,

实现了第一次切换tab时加载数据,记录每个tabbarview的滚动位置,

dart 复制代码
import 'package:ayidaojia/common/index.dart';
import 'package:ducafe_ui_core/ducafe_ui_core.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:pull_to_refresh_flutter3/pull_to_refresh_flutter3.dart';

import 'index.dart';

class OrderListPage extends GetView<OrderListController> {
  const OrderListPage({super.key});

  // tab 视图
  Widget _buildTabBar() {
    return Container(
      height: 88.w,
      color: AppTheme.blockBgColor,
      child: TabBar(
        controller: controller.tabController,
        isScrollable: true,
        indicatorColor: AppTheme.primary,
        indicatorSize: TabBarIndicatorSize.label,
        indicatorWeight: 4.w,
        labelColor: AppTheme.primary,
        unselectedLabelColor: AppTheme.color999,
        labelStyle: TextStyle(fontSize: 28.sp, fontWeight: FontWeight.bold),
        unselectedLabelStyle: TextStyle(fontSize: 28.sp, fontWeight: FontWeight.normal),
        tabs: controller.tabNames.map((name) {
          return Tab(text: name);
        }).toList(),
      ),
    );
  }

  // TabBarView 内容
  Widget _buildTabBarView() {
    return Expanded(
      child: TabBarView(
        controller: controller.tabController,
        children: List.generate(
          controller.tabNames.length,
          (index) => _OrderListTab(tabIndex: index),
        ),
      ),
    );
  }

  // 主视图
  Widget _buildView() {
    return <Widget>[
      _buildTabBar(), 
      _buildTabBarView()
    ].toColumn();
  }

  @override
  Widget build(BuildContext context) {
    return GetBuilder<OrderListController>(
      init: OrderListController(),
      id: "order_list",
      builder: (_) {
        return Scaffold(
          appBar: MineNavBar(
            titleWidget: TextWidget.body('我的订单', size: 32.sp, color: AppTheme.colorfff, weight: FontWeight.w600),
          ),
          body: _buildView(),
        );
      },
    );
  }
}

// 订单列表 Tab 页面(带缓存)
class _OrderListTab extends StatefulWidget {
  final int tabIndex;

  const _OrderListTab({required this.tabIndex});

  @override
  State<_OrderListTab> createState() => _OrderListTabState();
}

class _OrderListTabState extends State<_OrderListTab> with AutomaticKeepAliveClientMixin {
  @override
  bool get wantKeepAlive => true; // 保持页面状态

  OrderListController get controller => Get.find<OrderListController>();

  // 订单列表
  Widget _buildList() {
    return GetBuilder<OrderListController>(
      id: "order_list",
      builder: (_) {
        return SmartRefresher(
          controller: controller.getRefreshController(widget.tabIndex),
          enablePullUp: true, // 启用上拉加载
          onRefresh: () => controller.onRefresh(widget.tabIndex), // 下拉刷新回调
          onLoading: () => controller.onLoading(widget.tabIndex), // 上拉加载回调
          header: const SmartRefresherHeaderWidget(), // 头部刷新组件
          footer: const SmartRefresherFooterWidget(), // 底部加载更多组件
          child: ListView.separated(
            padding: EdgeInsets.symmetric(horizontal: 30.w, vertical: 20.w),
            itemBuilder: (context, index) {
              return _buildOrderItem(index);
            },
            separatorBuilder: (context, index) {
              return SizedBox(height: 20.w);
            },
            itemCount: controller.getItems(widget.tabIndex).length,
          ),
        );
      },
    );
  }

  // 订单item
  Widget _buildOrderItem(int itemIndex) {
    final item = controller.getItems(widget.tabIndex)[itemIndex] as Map<String, dynamic>;
    
    return <Widget>[
      // 订单编号和状态
      <Widget>[
        TextWidget.body(
          '订单号: ${item['orderNo']}',
          size: 24.sp,
          color: AppTheme.color999,
        ),
        TextWidget.body(
          item['statusText'],
          size: 24.sp,
          color: AppTheme.colorfff,
        ),
      ].toRow(mainAxisAlignment: MainAxisAlignment.spaceBetween),
      
      SizedBox(height: 20.w),
      
      // 服务名称
      TextWidget.body(
        item['serviceName'],
        size: 28.sp,
        color: AppTheme.colorfff,
        weight: FontWeight.bold,
      ),
      
      SizedBox(height: 16.w),
      
      // 服务类型
      TextWidget.body(
        '服务类型: ${item['serviceType']}',
        size: 24.sp,
        color: AppTheme.color999,
      ),
      
      SizedBox(height: 16.w),
      
      // 阿姨信息
      TextWidget.body(
        '服务阿姨: ${item['workerName']}',
        size: 24.sp,
        color: AppTheme.color999,
      ),
      
      SizedBox(height: 16.w),
      
      // 价格和时间
      <Widget>[
        TextWidget.body(
          '¥${item['price']}',
          size: 32.sp,
          color: AppTheme.colorfff,
          weight: FontWeight.bold,
        ),
        TextWidget.body(
          item['createTime'],
          size: 20.sp,
          color: AppTheme.color666,
        ),
      ].toRow(mainAxisAlignment: MainAxisAlignment.spaceBetween),
      
    ].toColumn(crossAxisAlignment: CrossAxisAlignment.start)
    .padding(all: 24.w)
    .tight(height: 380.w)
    .backgroundColor(AppTheme.blockBgColor)
    .clipRRect(all: 20.w);
  }

  @override
  Widget build(BuildContext context) {
    super.build(context); // 必须调用 super.build
    return _buildList();
  }
}
dart 复制代码
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:pull_to_refresh_flutter3/pull_to_refresh_flutter3.dart';

class OrderListController extends GetxController with GetSingleTickerProviderStateMixin {
  OrderListController();
  
  // TabController
  late TabController tabController;
  
  // tab 名称
  List<String> tabNames = ['全部', '待支付', '待接单', '待使用', '待评价','已完成'];
  
  // 每个 tab 的刷新控制器
  late List<RefreshController> refreshControllers;
  
  // 每个 tab 的订单列表
  late List<List> itemsList;
  
  // 每个 tab 的分页页码
  late List<int> pageList;
  
  // 每个 tab 是否已加载过数据
  late List<bool> hasLoadedList;

  // 初始化数据
  void _initData() {
    // 初始化 TabController
    int initialIndex = Get.arguments?["tabIndex"] ?? 0;
    tabController = TabController(
      length: tabNames.length,
      vsync: this,
      initialIndex: initialIndex,
    );
    
    // 初始化每个 tab 的刷新控制器
    refreshControllers = List.generate(
      tabNames.length,
      (index) => RefreshController(initialRefresh: false),
    );
    
    // 初始化每个 tab 的订单列表
    itemsList = List.generate(tabNames.length, (index) => []);
    
    // 初始化每个 tab 的分页页码
    pageList = List.generate(tabNames.length, (index) => 1);
    
    // 初始化每个 tab 的加载状态
    hasLoadedList = List.generate(tabNames.length, (index) => false);
    
    // 监听 tab 切换
    tabController.addListener(_onTabChanged);
    
    // 首次加载当前 tab 数据
    _loadTabDataIfNeeded(initialIndex);
    
    update(["order_list"]);
  }
  
  // tab 切换监听
  void _onTabChanged() {
    if (!tabController.indexIsChanging) {
      _loadTabDataIfNeeded(tabController.index);
    }
  }
  
  // 如果 tab 未加载过数据,则触发加载
  void _loadTabDataIfNeeded(int tabIndex) {
    if (!hasLoadedList[tabIndex]) {
      hasLoadedList[tabIndex] = true;
      // 延迟一帧执行,确保 UI 已经构建完成
      Future.delayed(const Duration(milliseconds: 100), () {
        refreshControllers[tabIndex].requestRefresh();
      });
    }
  }

  // 获取指定 tab 的刷新控制器
  RefreshController getRefreshController(int tabIndex) {
    return refreshControllers[tabIndex];
  }
  
  // 获取指定 tab 的订单列表
  List getItems(int tabIndex) {
    return itemsList[tabIndex];
  }

  /*
  * 分页
  * tabIndex: tab 索引
  * isRefresh: 是否刷新
  * */
  Future<bool> _loadOrderList(int tabIndex, bool isRefresh) async {
    // 模拟网络延迟
    await Future.delayed(const Duration(milliseconds: 800));
    
    // 模拟数据
    List<Map<String, dynamic>> mockData = List.generate(10, (index) {
      int currentPage = isRefresh ? 1 : pageList[tabIndex];
      int orderNum = (currentPage - 1) * 10 + index + 1;
      return {
        'orderId': 'ORD${tabNames[tabIndex]}${orderNum.toString().padLeft(4, '0')}',
        'orderNo': '202412${orderNum.toString().padLeft(8, '0')}',
        'serviceName': '${tabNames[tabIndex]} - 家政服务 $orderNum',
        'serviceType': '保洁服务',
        'price': (99 + orderNum * 10).toDouble(),
        'status': tabIndex,
        'statusText': tabNames[tabIndex],
        'createTime': '2024-12-02 ${(10 + index).toString().padLeft(2, '0')}:30:00',
        'workerName': '阿姨 ${String.fromCharCode(65 + index)}',
        'workerAvatar': 'https://picsum.photos/100/100?random=$orderNum',
      };
    });
    
    if (isRefresh) {
      pageList[tabIndex] = 1;
      itemsList[tabIndex].clear();
    }
    
    // 模拟分页:第3页后没有更多数据
    if (pageList[tabIndex] >= 3) {
      return true; // 返回 true 表示没有更多数据
    }
    
    if (mockData.isNotEmpty) {
      pageList[tabIndex]++;
      itemsList[tabIndex].addAll(mockData);
    }
    
    return mockData.isEmpty;
    
    // TODO: 调用接口获取数据
    // var result = await MallApi.getOrderList(PageListReq(
    //   page: isRefresh ? 1 : pageList[tabIndex],
    //   limit: 20,
    //   status: tabIndex.toString(),
    // ));
    // if (isRefresh) {
    //   pageList[tabIndex] = 1;
    //   itemsList[tabIndex].clear();
    // }
    // if (result.isNotEmpty) {
    //   pageList[tabIndex]++;
    //   itemsList[tabIndex].addAll(result);
    // }
    // return result.isEmpty;
  }

  // 上拉加载
  void onLoading(int tabIndex) async {
    if (itemsList[tabIndex].isNotEmpty) {
      try {
        // 拉取数据是否为空 ? 设置暂无数据 : 加载完成
        var isEmpty = await _loadOrderList(tabIndex, false);
        isEmpty
            ? refreshControllers[tabIndex].loadNoData()
            : refreshControllers[tabIndex].loadComplete();
      } catch (e) {
        refreshControllers[tabIndex].loadFailed(); // 加载失败
      }
    } else {
      refreshControllers[tabIndex].loadNoData(); // 设置无数据
    }
    update(["order_list"]);
  }

  // 下拉刷新
  void onRefresh(int tabIndex) async {
    try {
      await _loadOrderList(tabIndex, true);
      refreshControllers[tabIndex].refreshCompleted();
    } catch (e) {
      refreshControllers[tabIndex].refreshFailed();
    }
    update(["order_list"]);
  }

  @override
  void onReady() {
    super.onReady();
    _initData();
  }

  @override
  void onClose() {
    // 释放 TabController
    tabController.dispose();
    // 释放所有刷新控制器
    for (var controller in refreshControllers) {
      controller.dispose();
    }
    super.onClose();
  }
}
相关推荐
Swuagg18 小时前
Flutter 数据存储之 SharedPreferences 键值对存储
flutter·sp
Fate_I_C19 小时前
Flutter鸿蒙0-1开发-工具环境篇
flutter·华为·harmonyos·鸿蒙
走在路上的菜鸟19 小时前
Android学Dart学习笔记第二十四节 类-可调用对象Class()()
android·笔记·学习·flutter
2501_9159214319 小时前
Flutter App 到底该怎么测试?如何在 iOS 上进行测试
android·flutter·ios·小程序·uni-app·cocoa·iphone
Fate_I_C19 小时前
Flutter鸿蒙0-1开发-flutter create <prjn>
flutter·华为·harmonyos·鸿蒙
走在路上的菜鸟19 小时前
Android学Dart学习笔记第二十五节 类修饰符
android·笔记·学习·flutter
kirk_wang19 小时前
Flutter animations 库在 OpenHarmony 平台的适配与性能优化实践
flutter·移动开发·跨平台·arkts·鸿蒙
Bigger1 天前
Flutter 开发实战:解决华为 HarmonyOS 任务列表不显示 App 名称的终极指南
android·flutter·华为
梧桐ty1 天前
鸿蒙应用冷启动优化:Flutter首屏秒开与白屏治理实战
flutter·华为·harmonyos
梧桐ty1 天前
驾驭未来:基于鸿蒙的Flutter车载应用与手机端协同实战
flutter·华为·harmonyos