Flutter:自定义Tab切换,订单列表页tab,tab吸顶

1、自定义tab切换

view

js 复制代码
<Widget>[
  // 好评
  <Widget>[
    TDImage(assetUrl: 'assets/img/order4.png',width: 36.w,height: 36.w,),
    SizedBox(width: 10.w,),
    TextWidget.body('好评',size: 24.sp,color: controller.tabIndex == 0 ? AppTheme.colorfff : AppTheme.color999,),
  ].toRow(mainAxisAlignment: MainAxisAlignment.center)
  .card(color: controller.tabIndex == 0 ? AppTheme.error : AppTheme.colorfff,shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10.w)))
  .tight(width: 160.w,height: 68.w)
  .onTap(() {
    controller.onTapOrderStatus(0);
  }),

  SizedBox(width: 20.w,),

  // 中评
  <Widget>[
    TDImage(assetUrl: 'assets/img/order5.png',width: 36.w,height: 36.w,),
    SizedBox(width: 10.w,),
    TextWidget.body('中评',size: 24.sp,color: controller.tabIndex == 1 ? AppTheme.colorfff : AppTheme.color999,),
  ].toRow(mainAxisAlignment: MainAxisAlignment.center)
  .card(color: controller.tabIndex == 1 ? AppTheme.error : AppTheme.colorfff,shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10.w)))
  .tight(width: 160.w,height: 68.w)
  .onTap(() {
    controller.onTapOrderStatus(1);
  }),
  
  SizedBox(width: 20.w,),
  
  // 差评
  <Widget>[
    TDImage(assetUrl: 'assets/img/order6.png',width: 36.w,height: 36.w,),
    SizedBox(width: 10.w,),
    TextWidget.body('差评',size: 24.sp,color: controller.tabIndex == 2 ? AppTheme.colorfff : AppTheme.color999,),
  ].toRow(mainAxisAlignment: MainAxisAlignment.center)
  .card(color: controller.tabIndex == 2 ? AppTheme.error : AppTheme.colorfff,shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10.w)))
  .tight(width: 160.w,height: 68.w)
  .onTap(() {
    controller.onTapOrderStatus(2);
  }),
].toRow(),

controller

js 复制代码
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class OrderEvaluateController extends GetxController{
  OrderEvaluateController();
  
  // tab 索引
  int tabIndex = 0;
  // 状态改变
  onTapOrderStatus(int index){
    tabIndex = index;
    // print('tabIndex: $tabIndex');
    update(["order_evaluate"]);
  }

  @override
  void onClose() {
    super.onClose();
    tabController.dispose();
  }
}

2、订单列表tab,可以指定tabIndex的初始位置

view

js 复制代码
import 'package:demo/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 'package:tdesign_flutter/tdesign_flutter.dart';
import 'index.dart';

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

  // tab切换
  Widget _buildTab() {
    return TDTabBar(
      controller: controller.tabController,
      tabs: controller.tabNames.map((e) => TDTab(text: e)).toList(),
      onTap: (index) {
        controller.onTapOrderStatus(index);
      },
      backgroundColor: Colors.white,
      showIndicator: true,
      indicatorColor: const Color(0xffe01e1e),
      labelColor: const Color(0xffe01e1e),
    );
  }

  // list 列表
  Widget _buildList() {
    return SmartRefresher(
      controller: controller.refreshController,
      enablePullUp: true, // 启用上拉加载
      onRefresh: controller.onRefresh, // 下拉刷新回调
      onLoading: controller.onLoading, // 上拉加载回调
      footer: const SmartRefresherFooterWidget(), // 底部加载更多组件
      header: const SmartRefresherHeaderWidget(), // 顶部下拉刷新组件
      child: ListView.builder(
        itemCount: 10,
        itemBuilder: (context, index) {
          return _buildItem();
        },
      ),
    );
  }

  // 商品item
  Widget _buildItem() {
    return Text('item')
  }

  // 主视图
  Widget _buildView() {
    return <Widget>[
      _buildTab(),
      Expanded(child: _buildList()),
    ].toColumn();
  }

  @override
  Widget build(BuildContext context) {
    return GetBuilder<OrderListController>(
      init: OrderListController(),
      id: "order_list",
      builder: (_) {
        return Scaffold(
          backgroundColor: const Color(0xffF5F6FA),
          appBar: const TDNavBar(
            height: 45,
            title: '我的订单',
            titleFontWeight: FontWeight.w600,
            backgroundColor: Colors.white,
            screenAdaptation: true,
            useDefaultBack: true,
          ),
          body: SafeArea(
            child: _buildView(),
          ),
        );
      },
    );
  }
}

controller

js 复制代码
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();
  // 订单列表
  List items = [];
  // tab 控制器
  late TabController tabController;
  // tab 索引
  int tabIndex = Get.arguments["tabIndex"] ?? 0;
  // tab 名称
  List<String> tabNames = ['全部', '待付款', '待发货', '待收货', '已完成'];
  
  // 订单状态改变
  onTapOrderStatus(int index){
    tabIndex = index;
    // 刷新订单列表
    refreshController.requestRefresh();
    update(["order_list"]);
  }

  @override
  void onInit() {
    super.onInit();
    // 初始化时设置当前索引
    tabController = TabController(
      length: tabNames.length,
      vsync: this,
      initialIndex: tabIndex, // 设置初始索引
    );
    
    // 监听 tab 切换
    tabController.addListener(() {
      if (tabController.indexIsChanging) {
        tabIndex = tabController.index;
        update(["order_list"]);
      }
    });
  }

  @override
  void onClose() {
    super.onClose();
    refreshController.dispose();
    tabController.dispose();
  }
  
  /*
  * 分页
  * refreshController:分页控制器
  * _page:分页
  * _limit:每页条数
  * _loadNewsSell:拉取数据(是否刷新)
  * onLoading:上拉加载新商品
  * onRefresh:下拉刷新
  * */
  final RefreshController refreshController = RefreshController(
    initialRefresh: true,
  );
  // int _page = 1;
  // int _limit = 20;
  Future<bool> _loadNewsSell(bool isRefresh) async {
    return false;
    // var result = await ProductApi.products(ProductsReq(
    //   page:isRefresh ? 1:_page,
    //     prePage:_limit
    // ));
    // if(isRefresh){
    //   _page = 1;
    //   items.clear();
    // }
    // if(result.isNotEmpty){
    //   _page++;
    //   items.addAll(result);
    // }
    // // 是否是空
    // return result.isEmpty;
  }

  // 上拉载入新商品
  void onLoading() async {
    if (items.isNotEmpty) {
      try {
        // 拉取数据是否为空 ? 设置暂无数据 : 加载完成
        var isEmpty = await _loadNewsSell(false);
        isEmpty
            ? refreshController.loadNoData()
            : refreshController.loadComplete();
      } catch (e) {
        refreshController.loadFailed(); // 加载失败
      }
    } else {
      refreshController.loadNoData(); // 设置无数据
    }
    update(["goods_list"]);
  }

  // 下拉刷新
  void onRefresh() async {
    try {
      await _loadNewsSell(true);
      refreshController.refreshCompleted();
    } catch (e) {
      refreshController.refreshFailed();
    }
    update(["goods_list"]);
  }
}

3、tab吸顶,主要记录view中的实现。
下拉刷新和tab切换的方法基本与上方2、的controller一致


view

js 复制代码
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 'package:tdesign_flutter/tdesign_flutter.dart';
import 'package:xiaoshukeji/common/index.dart';
import 'index.dart';

// 1. SliverPersistentHeaderDelegate:必须实现的抽象类
class _StickyTabBarDelegate extends SliverPersistentHeaderDelegate {
  final Widget child;

  _StickyTabBarDelegate({required this.child});

  @override
  Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) {
    // shrinkOffset: 滚动距离
    // overlapsContent: 是否与其他内容重叠
    return Container(
      color: AppTheme.pageBgColor,
      child: child,
    );
  }

  @override
  double get maxExtent => 92.w; // 最大高度,已知tab高度72+上下padding:10

  @override
  double get minExtent => 92.w; // 最小高度

  @override
  bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) => true;
}


class RankingPage extends GetView<RankingController> {
  const RankingPage({super.key});

  // 头部皇冠位置
  Widget _buildHeader() {
    return <Widget>[

    ].toRow()
    .card(color: AppTheme.pageBgColor)
    .tight(width: 750.w,height: 300.w,);
  }

  // tab,可吸顶
  Widget _buildTab() {
    return <Widget>[
      <Widget>[
        TextWidget.body('日榜', size: 28.sp, weight: FontWeight.w600, color: AppTheme.textColorfff),
      ].toRow(mainAxisAlignment: MainAxisAlignment.center)
      .card(color: AppTheme.primaryYellow)
      .tight(width: 336.w,height: 72.w,),

      <Widget>[
        TextWidget.body('总榜', size: 28.sp, weight: FontWeight.w600, color: AppTheme.textColor6a7),
      ].toRow(mainAxisAlignment: MainAxisAlignment.center)
      .card(color: AppTheme.navBarBgColor)
      .tight(width: 336.w,height: 72.w,),

    ].toRow(mainAxisAlignment: MainAxisAlignment.spaceBetween);
  }

  // 数据列表
  Widget _buildDataList() {
    return SliverList(
      delegate: SliverChildBuilderDelegate(
        (context, index) {
          return <Widget>[]
              .toRow()
              .paddingHorizontal(30.w)
              .card(color: AppTheme.blockBgColor)
              .tight(
                width: 690.w,
                height: 120.w,
              )
              .marginOnly(bottom: 20.w);
        },
        childCount: 20,
      ),
    );
  }

  // 主视图
  Widget _buildView() {
    return SmartRefresher(
      controller: controller.refreshController,
      enablePullUp: true,
      onRefresh: controller.onRefresh,
      onLoading: controller.onLoading,
      footer: const SmartRefresherFooterWidget(),
      header: const SmartRefresherHeaderWidget(),
      child: CustomScrollView(
        slivers: [
          // 头部
          _buildHeader().sliverToBoxAdapter().sliverPaddingHorizontal(30.w),
          
          // 2. SliverPersistentHeader:实现吸顶的核心组件
          SliverPersistentHeader(
            pinned: true,  // 设置为 true 实现吸顶
            delegate: _StickyTabBarDelegate(
              child: Container(
                padding: EdgeInsets.symmetric(horizontal: 30.w, vertical: 10.w),
                child: _buildTab(),
              ),
            ),
          ),
          
          // 列表内容
          _buildDataList().sliverPaddingHorizontal(30.w),
        ],
      ),
    );
  }


  @override
  Widget build(BuildContext context) {
    return GetBuilder<RankingController>(
      init: RankingController(),
      id: "ranking",
      builder: (_) {
        return Scaffold(
          backgroundColor: AppTheme.pageBgColor, // 自定义颜色
          appBar: const TDNavBar(
            height: 0,
            titleColor: AppTheme.textColorfff,
            titleFontWeight: FontWeight.w600,
            backgroundColor: AppTheme.pageBgColor,
            screenAdaptation: true, // 是否进行屏幕适配,默认true
            useDefaultBack: false,
          ),
          body: _buildView(),
        );
      },
    );
  }
}
cpp 复制代码
GetTickerProviderStateMixin 和 GetSingleTickerProviderStateMixin 的主要区别在于:
1、GetSingleTickerProviderStateMixin:
用于创建单个 AnimationController
性能更好,因为只维护一个 Ticker
适用场景:只需要一个动画控制器的情况,如单个 TabController
class MyController extends GetxController with GetSingleTickerProviderStateMixin {
  late TabController tabController;
  
  @override
  void onInit() {
    super.onInit();
    tabController = TabController(length: 3, vsync: this);
  }
  
  @override
  void onClose() {
    tabController.dispose();
    super.onClose();
  }
}

2、GetTickerProviderStateMixin:
可以创建多个 AnimationController
适用场景:需要多个独立动画控制器的情况

class MyController extends GetxController with GetTickerProviderStateMixin {
  late AnimationController controller1;
  late AnimationController controller2;
  
  @override
  void onInit() {
    super.onInit();
    controller1 = AnimationController(
      vsync: this,
      duration: const Duration(milliseconds: 300),
    );
    controller2 = AnimationController(
      vsync: this,
      duration: const Duration(milliseconds: 500),
    );
  }
}


什么时候需要 GetSingleTickerProviderStateMixin:
当你使用 TabController 来控制 tab 切换动画
当你需要实现滑动切换 tab 功能
当你需要更复杂的 tab 切换效果

如果只需要点击切换,不需要 GetSingleTickerProviderStateMixin
相关推荐
硬件人某某某8 分钟前
Java基于SSM框架的社区团购系统小程序设计与实现(附源码,文档,部署)
java·开发语言·社区团购小程序·团购小程序·java社区团购小程序
kucupung20 分钟前
【C++基础】多线程并发场景下的同步方法
开发语言·c++
zzlyx9921 分钟前
.NET 9 微软官方推荐使用 Scalar 替代传统的 Swagger
javascript·microsoft·.net
Quantum&Coder26 分钟前
Objective-C语言的计算机基础
开发语言·后端·golang
五味香28 分钟前
Java学习,List 元素替换
android·java·开发语言·python·学习·golang·kotlin
Bunury28 分钟前
组件封装-List
javascript·数据结构·list
Joeysoda31 分钟前
Java数据结构 (从0构建链表(LinkedList))
java·linux·开发语言·数据结构·windows·链表·1024程序员节
迂幵myself31 分钟前
14-6-1C++的list
开发语言·c++·list
扫地僧00934 分钟前
(Java版本)基于JAVA的网络通讯系统设计与实现-毕业设计
java·开发语言
天乐敲代码34 分钟前
JAVASE入门九脚-集合框架ArrayList,LinkedList,HashSet,TreeSet,迭代
java·开发语言·算法