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

news2025/1/24 6:23:44

1、自定义tab切换

在这里插入图片描述

view

<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

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

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

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

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(),
        );
      },
    );
  }
}
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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2281263.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Tailscale 配置 subnet 实现访问 Openwrt 路由器下的子网

Openwrt 安装 Tailscale 参考 OpenWrt 配置 Tailscale 内网穿透。 tailscale两台openwrt(双lan)网对网(site to site)互通OpenWrt安装配置Tailscale 在 OpenWrt 上使用 Tailscale 使用 tailscale subnet 在openwrt terminal 执行 tailscale up --advertise-routes192.168.…

低代码可视化-转盘小游戏可视化-代码生成器

转盘小程序是一种互动工具&#xff0c;它通过模拟真实的转盘抽奖或决策体验&#xff0c;为用户提供了一种有趣且公平的选择方式。以下是对转盘小程序的详细介绍&#xff1a; 转盘小程序的应用场景 日常决策&#xff1a;转盘小程序可以帮助用户解决日常生活中的选择困难问题&a…

【Uniapp-Vue3】uni-icons的安装和使用

一、uni-icon的安装 进入到如下页面中&#xff0c;点击“点击下载&安装”。 uni-icons 图标 | uni-app官网 点击“下载插件并导入HBuilder”&#xff0c;如果没有登录就登陆一下 网页中会打开Hbuilder&#xff0c;进入Hbuilder以后&#xff0c;选择需要使用该插件的项目进…

【橘子ES】Kibana的分析能力Analytics简易分析

一、kibana是啥&#xff0c;能干嘛 我们经常会用es来实现一些关于检索&#xff0c;关于分析的业务。但是es本身并没有UI,我们只能通过调用api来完成一些能力。而kibana就是他的一个外置UI&#xff0c;你完全可以这么理解。 当我们进入kibana的主页的时候你可以看到这样的布局。…

一、引论,《组合数学(第4版)》卢开澄 卢华明

零、前言 发现自己数数题做的很烂&#xff0c;重新学一遍组合数学吧。 参考卢开澄 卢华明 编著的《组合数学(第4版)》&#xff0c;只打算学前四章。 通过几个经典问题来了解组合数学所研究的内容。 一、幻方问题 据说大禹治水之前&#xff0c;河里冒出来一只乌龟&#xff0c…

LabVIEW太阳能照明监控系统

在公共照明领域&#xff0c;传统的电力照明系统存在高能耗和维护不便等问题。利用LabVIEW开发太阳能照明监控系统&#xff0c;通过智能控制和实时监测&#xff0c;提高能源利用效率&#xff0c;降低维护成本&#xff0c;实现照明系统的可持续发展。 ​ 项目背景 随着能源危机…

5. 马科维茨资产组合模型+政策意图AI金融智能体(Qwen-Max)增强方案(理论+Python实战)

目录 0. 承前1. AI金融智能体1.1 What is AI金融智能体1.2 Why is AI金融智能体1.3 How to AI金融智能体 2. 数据要素&计算流程2.1 参数集设置2.2 数据获取&预处理2.3 收益率计算2.4 因子构建与预期收益率计算2.5 协方差矩阵计算2.6 投资组合优化2.7 持仓筛选2.8 AI金融…

【华为路由的arp配置】

华为路由的arp配置 ARP&#xff1a;IP地址与MAC地址的映射。 R1: g0/0/0:10.1.1.254/24 g0/0/1:10.1.2.254/24 PC1: 10.1.1.1/16 PC2: 10.1.1.2/16 PC3: 10.1.2.3/16 动态ARP 查看PC1的arp表&#xff0c;可以看到&#xff0c;列表为空。 查看R1的arp表 在PC3上ping命令测…

U3D的.Net学习

Mono&#xff1a;这是 Unity 最初采用的方式&#xff0c;它将 C# 代码编译为中间语言 (IL)&#xff0c;然后在目标平台上使用虚拟机 (VM) 将其转换为本地机器码执行。 IL2CPP&#xff1a;这是一种较新的方法&#xff0c;它会将 C# 代码先编译为 C 代码&#xff0c;再由 C 编译器…

机器学习-线性回归(简单回归、多元回归)

这一篇文章&#xff0c;我们主要来理解一下&#xff0c;什么是线性回归中的简单回归和多元回归&#xff0c;顺便掌握一下特征向量的概念。 一、简单回归 简单回归是线性回归的一种最基本形式&#xff0c;它用于研究**一个自变量&#xff08;输入&#xff09;与一个因变量&…

智能体的核心技能之插件,插件详解和实例 ,扣子免费系列教程(11)

欢迎来到滔滔讲AI&#xff0c;今天我们来学习智能体的核心功能点之一的插件。 插件是通过API连接集成各种平台和服务&#xff0c;它扩展了智能体的能力。平台内置了丰富的插件&#xff0c;我们可以直接调用。 一、什么是插件 首先&#xff0c;插件其实就像一个工具箱。 每个插…

Spring Security(maven项目) 3.0.2.6版本—总

通过实践而发现真理&#xff0c;又通过实践而证实真理和发展真理。从感性认识而能动地发展到理性认识&#xff0c;又从理性认识而能动地指导革命实践&#xff0c;改造主观世界和客观世界。实践、认识、再实践、再认识&#xff0c;这种形式&#xff0c;循环往复以至无穷&#xf…

超强推理大模型 QVQ-32B-preview 一键部署

QwQ-32B-Preview 是 Qwen 团队于 2024 年开发的实验研究模型&#xff0c;专注于提高 AI 推理能力。这个模型在多个基准测试中展现了卓越的性能&#xff0c;特别是在 GPQA、AIME、MATH-500 和 LiveCodeBench 等测试中&#xff0c;准确率分别达到了 65.2%、50.0%、90.6% 和 50.0%…

数据结构(Java)——二叉树

1.概念 二叉树是一种树形数据结构&#xff0c;其中每个节点最多有两个子节点&#xff0c;通常被称为左子节点和右子节点。二叉树可以是空的&#xff08;即没有节点&#xff09;&#xff0c;或者由一个根节点以及零个或多个左子树和右子树组成&#xff0c;其中左子树和右子树也分…

谷粒商城——商品服务-三级分类

1.商品服务-三级分类 1.1三级分类介绍 1.2查询三级分类查询-递归树型结构数据获取 1.2.1导入数据pms_catelog.sql到数据表pms_category 1.2.2一次性查出所有分类及子分类 1.2.2.1修改CategoryController.java /*** 查出所有分类以及子分类&#xff0c;以树形结构组装起来*/R…

AviatorScript用法

AviatorScript的介绍&#xff0c;网上有很多&#xff0c;这里就不啰嗦了。这里介绍下传参的用法 应用场景&#xff1a;如果不想频繁的打包升级&#xff0c;而是想只更新某些规则脚本重启服务就可以升级的话&#xff0c;AviatorScript无疑是最佳选择。比如说&#xff0c;今天制…

云计算和服务器

一、云计算概述 ICT是世界电信协会在2001年的全球性会议上提出的综合性概念&#xff0c;ICT分为IT和CT&#xff0c;IT(information technology)信息技术&#xff0c;负责对数据生命周期的管理&#xff1b;CT(communication technology)&#xff0c;负责数据的传输管理。 CT技术…

论文:深度可分离神经网络存内计算处理芯片

引言&#xff1a;SRAM - CIM芯片在处理深度可分离神经网络时面临的挑战 深度可分离卷积&#xff08;Depthwise separable convolution, DSC&#xff09;由逐深度卷积&#xff08;DW&#xff09;和逐点卷积&#xff08;PW)组成&#xff0c;逐深度卷积用于提取空间特征&#xff…

代码随想录刷题day14(1)|(链表篇)142.环形链表 II

目录 一、链表理论基础 二、环形链表思路 1.如何判断有环&#xff1f; 2.如何找出环的入口&#xff1f; 3.其他疑问 三、相关算法题目 四、总结 一、链表理论基础 代码随想录 (programmercarl.com) 二、环形链表思路 1.如何判断有环&#xff1f; 使用快慢指针法&…

运算放大器应用电路设计笔记(六)

6.1输出失调电压发生的原因与计算 6.1.1用噪声增益进行评价 若运算放大器两个输入端接地&#xff0c;则理想运放输出为零&#xff0c;但实际的运放输出不为零&#xff0c;有一定的直流输出电压。这种直流电压称为输出失调电压。发生的原因是&#xff0c;运算放大器内部元件尤…