TabBar
还有TabBarView
都是谷歌flutter官方组件库------Material组件库提供的组件,其中TabBar
用于导航切换,TabBarView
则是配合其切换显示的对应的视图,官网参考地址:TabBarView class - material library - Dart API。
实现一体联动有两种实现方式:使用默认控制器(DefaultTabController)和自定义控制器。使用自定义控制器灵活性更高,但是需要指定TabController的length属性,但有些情况下栏目的实际数据是从网络上异步加载读取的,这个TabController的length无法动态更新或后面重新指定,非常扯淡,具体实现参考:flutter 之 TabBar、TabBarView的使用 - 简书。
本文主要介绍在使用DefaultTabController下,实现获取点击当前栏目的索引,包括点击TabBar和滑动TabBarView以及视图状态保持,示例如下:
Kotlin
late int _selectIndex = 0;
late final ScrollController _scrollController;
late final NewsPageViewModel _vm = NewsPageViewModel();
late final _scaffoldKey = GlobalKey<_NewsPageViewState>();
@override
void initState() {
// TODO: implement initState
super.initState();
//栏目滑动索引改变
_scrollController = ScrollController(onDetach: (ScrollPosition position) {
if (_scaffoldKey.currentContext != null) {
final controller =
DefaultTabController.of(_scaffoldKey.currentContext!);
controller.addListener(() {
if (controller.index.toDouble() == controller.animation?.value) {
//loadListDataFor(controller.index);
debugPrint(controller.index);
}
});
}
});
}
@override
Widget build(BuildContext context) {
// TODO: implement build
return BaseView<NewsPageViewModel>(
viewModel: _vm,
build: (context, viewModel, child) {
if (viewModel.state == ViewState.Busy) {
return BaseView.loadingWidget();
} else {
return DefaultTabController(
initialIndex: _selectIndex,
length: viewModel.arrCategory.length,
child: NestedScrollView(
controller: _scrollController,
headerSliverBuilder:
(BuildContext context, bool innerBoxIsScrolled) {
return <Widget>[
SliverOverlapAbsorber(
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(
context),
sliver: SliverAppBar(
title: Utils.shareInstance.customPageTitle('资讯'),
//标题横向间距
titleSpacing: 15,
//左侧的图标或文字,多为返回箭头
leading: null,
//左边按钮的宽度
leadingWidth: 15,
//居中
centerTitle: false,
//标题栏是否固定
pinned: true,
//滑动时是否悬浮
floating: false,
//配合floating使用
snap: false,
//是否显示在状态栏的下面,false就会占领状态栏的高度
primary: true,
//设置分栏区域上面的高度
// expandedHeight: 0,
elevation: 0,
//是否显示阴影,直接取值innerBoxIsScrolled,展开不显示阴影,合并后会显示
forceElevated: innerBoxIsScrolled,
//自定义导航和中间内容的展示
flexibleSpace: null,
//导航栏背景色
backgroundColor: K_APP_NAVIGATION_BACKGROUND_COLOR,
//TabBar 分栏标题
bottom: _setCategoryTabBar(viewModel),
),
)
];
},
//分栏展示的页面信息
body: _setCategoryTabBarView(context, viewModel),
));
}
},
onModelReady: (viewModel) {
//加载栏目
viewModel.categoryLoad(context, isLoading: false);
});
}
@override
void dispose() {
_scrollController.dispose();
// TODO: implement dispose
super.dispose();
}
//MARK: - 扩展
extension on _NewsPageViewState {
//分栏菜单
TabBar _setCategoryTabBar(NewsPageViewModel viewModel) {
return TabBar(
key: _scaffoldKey,
//设置对齐方式(否则左边有空白)
tabAlignment: TabAlignment.start,
//是否允许滚动
isScrollable: true,
//未选中的颜色
unselectedLabelColor: Utils.shareInstance.hexToInt(0x505050),
//未选中的样式
unselectedLabelStyle: const TextStyle(fontSize: 15),
//选中的颜色
labelColor: K_APP_TINT_COLOR,
//选中的样式
labelStyle: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
//底部滑动条样式
indicatorSize: TabBarIndicatorSize.label,
//底部滑动条颜色
indicatorColor: K_APP_TINT_COLOR,
//菜单项目
tabs: viewModel.arrCategory.map((item) {
return Tab(text: item.name);
}).toList(),
//点击事件
onTap: (index) {
debugPrint(index);
//loadListDataFor(index);
setState(() {
_selectIndex = index;
});
},
);
}
// tabBar 分栏菜单各个页面
Widget _setCategoryTabBarView(
BuildContext context, NewsPageViewModel viewModel) {
return TabBarView(
children: viewModel.arrCategory.map((item) {
//设置UI
return SafeArea(
top: false,
bottom: false,
child: Builder(
builder: (BuildContext context) {
return CustomScrollView(
slivers: [
SliverOverlapInjector(
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(
context),
),
SliverPadding(
padding: EdgeInsets.zero,
sliver: SliverFixedExtentList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
final m = viewModel.arrList[index];
if (m.className == '快讯') {
//7x24快讯
return NewsPageViewInformationCell(
index, m, context, viewModel);
}
return NewsPageViewCell(index, m, context, viewModel);
}, childCount: viewModel.arrList.length),
itemExtent: 50.0 //item高度或宽度,取决于滑动方向
),
)
],
);
},
));
}).toList());
}
}
上面方法进过实际测试,可以实现点击和滑动时获取当前栏目的索引,以便根据索引执行加载当前页面的栏目数据的业务逻辑。随之会产生新的问题就是栏目来回切换没有保持页面的状态,会重复加载数据,解决思路是借助 AutomaticKeepAliveClientMixin 并设置 wantKeepAlive 为true,在PageView和PageController组合中同样适用,效果如下:
86_1720350605