Tab选项卡,这是一个非常常见且权重很高的一个组件,随便打开一个App,比如掘金,如下图,首页顶部就是一个Tab选项卡,这个功能可以说,几乎每个App都会存在。
在Android中,我们可以使用TabLayout+ViewPager,轻松的实现一个Tab指示器+页面滑动,而在Flutter当中呢,可以很负责任的告诉大家,也是很简单的就可以实现,主要使用到了TabBar和TabBarView,举一个特别简单的例子,如下代码所示,就是非常简单的Tab选项卡+底部页面的效果。
Widgetbuild(BuildContextcontext) { List<Widget>tabs= []; //tab指示器List<Widget>bodyList= []; //tab指示器下面的内容Widgetfor (inti=0; i<9; i++) { tabs.add(Tab(text: "条目$i")); bodyList.add(Text("条目$i"));//内容可以是任意的Widget,比如列表等 } returnDefaultTabController( // 标签数量length: tabs.length, child: Scaffold( appBar: TabBar( // 多个标签时滚动加载isScrollable: true, // 标签指示器的颜色indicatorColor: Colors.red, // 标签的颜色labelColor: Colors.red, // 未选中标签的颜色unselectedLabelColor: Colors.black, // 指示器的大小indicatorSize: TabBarIndicatorSize.label, // 指示器的权重,即线条高度indicatorWeight: 4.0, tabs: tabs), // 标签页所对应的页面body: TabBarView(children: bodyList))); }
代码效果如下:
在Flutter当中实现起来是不是也是非常的简单呢,既然已经如此的简单了,为什么我们还要再封装一层呢?说白了一是为了扩展,扩展一下系统无法满足的功能,二是为了调用起来得心应手。ok,废话不多说,开始今天的概述。
今天的内容大概如下:
1、封装效果一览
2、确定封装属性和拓展属性
3、源码和具体使用
4、相关总结
一、封装效果一览
所有的效果都是基于原生而实现的,如下图所示:
二、确定封装属性和拓展属性
基本上封装的效果就如上图所示,要封装哪些属性,关于系统的属性,比如指示器的颜色,标签选中和未选中的颜色等等,都可以抛出去,让使用者选择性进行使用。
而需要的拓展的属性,就使得自定义的Tab更加的灵活,满足不同的实际的需求,比如,文本指示器,图片指示器,图文指示器等等,都可以灵活的添加一下。
具体的属性如下,大家在实际封装中,可以根据自身需要来动态的灵活的设置。
属性 |
类型 |
概述 |
tabTitleList |
List<String> |
tab指示器的标题集合,文字形式 |
tabImageList |
List<String> |
tab指示器的标题集合,图片形式 |
tabWidgetList |
List<Widget> |
tab指示器的标题集合,Widget形式 |
tabIconAndTextList |
List<TabBarBean> |
tab指示器的标题集合,左图右文形式 |
tabBodyList |
List<Widget> |
tab指示器对应的页面 |
onPageChange |
Function(int) |
页面滑动回调 |
indicatorColor |
Color |
指示器的颜色 |
labelColor |
Color |
标签的颜色 |
unselectedLabelColor |
Color |
未选中标签的颜色 |
indicatorSize |
TabBarIndicatorSize |
指示器的大小 是和文字宽度一样还是充满 |
indicatorHeight |
double |
indicatorHeight |
isScrollable |
bool |
指示器是否支持滑动 |
tabImageWidth |
double |
图片指示器的宽 仅用于图片指示器和图文指示器 |
tabImageHeight |
double |
图片指示器的高 仅用于图片指示器和图文指示器 |
tabIconAndTextMargin |
double |
左图右文指示器,icon距离文字的距离 |
tabHeight |
double |
tab高度 |
三、源码和具体使用
源码相对比较的简单,仅仅对TabBar和TabBarView做了简单的封装,支持了多种格式的Tab类型,由于需要Tab控制器,这里使用了有状态的StatefulWidget。源码整体如下:
import'package:flutter/material.dart'; import'package:vip_flutter/ui/widget/vip_text.dart'; ///AUTHOR:AbnerMing///DATE:2023/5/18///INTRODUCE:TabBar组件classVipTabBarViewextendsStatefulWidget { finalList<String>?tabTitleList; //tab指示器的标题集合,文字形式finalList<String>?tabImageList; //tab指示器的标题集合,图片形式finalList<Widget>?tabWidgetList; //tab指示器的标题集合,Widget形式finalList<VipTabBarBean>?tabIconAndTextList; //tab指示器的标题集合,左图右文形式finalList<Widget>?tabBodyList; //tab指示器的页面finalFunction(int)?onPageChange; //页面滑动回调finalColor?indicatorColor; //指示器的颜色finalColor?labelColor; //标签的颜色finalColor?unselectedLabelColor; //未选中标签的颜色finalTabBarIndicatorSize?indicatorSize; //指示器的大小 是和文字宽度一样还是充满finaldouble?indicatorHeight; //指示器的高度finalbool?isScrollable; //指示器是否支持滑动finaldouble?tabImageWidth; //图片指示器的宽 仅用于图片指示器和图文指示器finaldouble?tabImageHeight; //图片指示器的高 仅用于图片指示器和图文指示器finaldouble?tabIconAndTextMargin; //左图右文指示器,icon距离文字的距离finaldouble?tabHeight; //tab高度constVipTabBarView( {this.tabTitleList, this.tabImageList, this.tabWidgetList, this.tabIconAndTextList, this.tabBodyList, this.onPageChange, this.indicatorColor=Colors.black, this.labelColor=Colors.black, this.unselectedLabelColor=Colors.grey, this.indicatorSize=TabBarIndicatorSize.tab, this.indicatorHeight=2, this.isScrollable=true, this.tabImageWidth=15, this.tabImageHeight=15, this.tabIconAndTextMargin=5, this.tabHeight=44, super.key}); State<VipTabBarView>createState() =>_GWMTabBarViewState(); } ///左图右文的对象classVipTabBarBean { Stringtitle; Stringicon; VipTabBarBean(this.title, this.icon); } class_GWMTabBarViewStateextendsState<VipTabBarView>withSingleTickerProviderStateMixin { // 标签控制器lateTabController_tabController; voidinitState() { super.initState(); // 定义控制器_tabController=TabController( vsync: this, length: widget.tabBodyList!=null?widget.tabBodyList!.length : 0, ); // 添加监听事件_tabController.addListener(() { //滑动的索引if (widget.onPageChange!=null&&!_tabController.indexIsChanging) { widget.onPageChange!(_tabController.index); } }); } voiddispose() { super.dispose(); // 杀死控制器_tabController.dispose(); } /** 指示器点击*/voidonPage(position) {} Widgetbuild(BuildContextcontext) { List<Widget>tabList= []; //tab指示器List<Widget>bodyList= []; //tab指示器对应的页面//文字形式if (widget.tabTitleList!=null) { tabList=widget.tabTitleList! .map((e) =>Tab( text: e, height: widget.tabHeight, )) .toList(); } //图片形式if (widget.tabImageList!=null) { tabList=widget.tabImageList!.map((e) { Widgetview; if (e.contains("http")) { //网络图片view=Image.network( e, width: widget.tabImageWidth, height: widget.tabImageHeight, ); } else { view=Image.asset( e, width: widget.tabImageWidth, height: widget.tabImageHeight, ); } returnTab(icon: view, height: widget.tabHeight); }).toList(); } //自定义Widgetif (widget.tabWidgetList!=null) { tabList=widget.tabWidgetList!; } //左图右文形式if (widget.tabIconAndTextList!=null) { tabList=widget.tabIconAndTextList!.map((e) { returnVipText( e.title, leftIcon: e.icon, height: widget.tabHeight, leftIconWidth: widget.tabImageWidth, leftIconHeight: widget.tabImageHeight, iconMarginRight: widget.tabIconAndTextMargin, ); }).toList(); } //指示器对应的页面if (widget.tabBodyList!=null) { bodyList=widget.tabBodyList!.map((e) =>e).toList(); } returnScaffold( appBar: TabBar( // 加上控制器controller: _tabController, tabs: tabList, // 标签指示器的颜色indicatorColor: widget.indicatorColor, // 标签的颜色labelColor: widget.labelColor, // 未选中标签的颜色unselectedLabelColor: widget.unselectedLabelColor, // 指示器的大小indicatorSize: widget.indicatorSize, // 指示器的权重,即线条高度indicatorWeight: widget.indicatorHeight!, // 多个标签时滚动加载isScrollable: widget.isScrollable!, onTap: onPage, ), body: TabBarView( // 加上控制器controller: _tabController, children: bodyList, ), ); } }
简单使用
传一个标题集合和页面集合就可以轻松实现了。
Widgetbuild(BuildContextcontext) { returnconstVipTabBarView( tabTitleList: ["条目一", "条目二"], tabBodyList: [ Text("第一个页面"),//可以是任意的WidgetText("第二个页面"),//可以是任意的Widget ], ); }
所有案例
对应第一条的封装效果,可直接复制查看效果。
import'package:flutter/material.dart'; import'../widget/vip_tab_bar_view.dart'; import'../widget/vip_text.dart'; ///AUTHOR:AbnerMing///DATE:2023/5/20///INTRODUCE:TabBar组件效果页面classTabBarPageextendsStatefulWidget { constTabBarPage({super.key}); State<TabBarPage>createState() =>_TabBarPageState(); } class_TabBarPageStateextendsState<TabBarPage> { Widgetbuild(BuildContextcontext) { vartabs= ["条目一", "条目二", "条目三", "条目四", "条目五", "条目六", "条目七", "条目八"]; vartabs2= ["条目一", "条目二", "条目三"]; vartabImages= [ "https://www.vipandroid.cn/ming/pic/new_java.png", "https://www.vipandroid.cn/ming/pic/new_android.png", "https://www.vipandroid.cn/ming/pic/new_kotlin.png" ]; //图片指示器varbodyList=tabs .map((e) =>VipText(e, backgroundColor: Colors.amberAccent)) .toList(); varbodyList2=tabs2 .map((e) =>VipText(e, backgroundColor: Colors.amberAccent)) .toList(); returnColumn(children: [ constVipText("多个Tab滑动", alignment: Alignment.topLeft, marginTop: 10, style: TextStyle(fontWeight: FontWeight.bold)), SizedBox( height: 80, child: VipTabBarView( tabTitleList: tabs, tabBodyList: bodyList, onPageChange: ((position) { //页面滑动监听print(position); }), )), constVipText("固定Tab不滑动", alignment: Alignment.topLeft, marginTop: 10, style: TextStyle(fontWeight: FontWeight.bold)), SizedBox( height: 80, child: VipTabBarView( tabTitleList: tabs2, tabBodyList: bodyList2, isScrollable: false, )), constVipText("修改指示器颜色", alignment: Alignment.topLeft, marginTop: 10, style: TextStyle(fontWeight: FontWeight.bold)), SizedBox( height: 80, child: VipTabBarView( tabTitleList: tabs2, tabBodyList: bodyList2, isScrollable: false, labelColor: Colors.red, unselectedLabelColor: Colors.black, indicatorColor: Colors.red, )), constVipText("修改指示器大小", alignment: Alignment.topLeft, marginTop: 10, style: TextStyle(fontWeight: FontWeight.bold)), SizedBox( height: 80, child: VipTabBarView( tabTitleList: tabs2, tabBodyList: bodyList2, isScrollable: false, labelColor: Colors.red, unselectedLabelColor: Colors.black, indicatorColor: Colors.red, indicatorSize: TabBarIndicatorSize.label, )), constVipText("图片指示器", alignment: Alignment.topLeft, marginTop: 10, style: TextStyle(fontWeight: FontWeight.bold)), SizedBox( height: 80, child: VipTabBarView( tabImageList: tabImages, tabBodyList: bodyList2, isScrollable: false, labelColor: Colors.red, unselectedLabelColor: Colors.black, indicatorColor: Colors.red, indicatorSize: TabBarIndicatorSize.label, )), constVipText("左图右文指示器", alignment: Alignment.topLeft, marginTop: 10, style: TextStyle(fontWeight: FontWeight.bold)), SizedBox( height: 80, child: VipTabBarView( tabIconAndTextList: [ VipTabBarBean( "Java", "https://www.vipandroid.cn/ming/pic/new_java.png"), VipTabBarBean("Android", "https://www.vipandroid.cn/ming/pic/new_android.png"), VipTabBarBean("Kotlin", "https://www.vipandroid.cn/ming/pic/new_kotlin.png"), ], tabBodyList: bodyList2, isScrollable: false, labelColor: Colors.red, unselectedLabelColor: Colors.black, indicatorColor: Colors.red, indicatorSize: TabBarIndicatorSize.label, )) ]); } }
四、相关总结
在Flutter中我们使用Tab选项卡和底部的页面结合使用时,一定要考虑懒加载,否则,在有网络请求的时候,每次切换页面的时候,数据都会重新加载,这给用户体验是相当的不好,具体如何实现,大家可以去网上搜索搜索,有大把的文章概述,这里就不赘述了,好了铁子们,本篇文章就先到这里,希望可以帮助到大家。