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