TabBar在应用中是比较常见的一个控件,通常是配合TabBarView。TabBa作导航栏,TabBarView作导航栏当前所对应的内容区。来看一下TabBar配合TabBarView使用的效果:
TabBar的参数:
const TabBar({
Key key,
@required this.tabs,//子标签
this.controller,//控制器
this.isScrollable = false,//能否滑动,false:tab宽度则等比,true:tab宽度则包裹item
this.indicatorColor,//指示器颜色
this.indicatorWeight = 2.0,
this.indicatorPadding = EdgeInsets.zero,
this.indicator,
this.indicatorSize,//TabBarIndicatorSize.label:indicator与文字同宽,TabBarIndicatorSize.tab:与tab同宽
this.labelColor,//选中标签颜色
this.labelStyle,//选中标签样式
this.labelPadding,
this.unselectedLabelColor,//未选中标签颜色
this.unselectedLabelStyle,
this.dragStartBehavior = DragStartBehavior.down,
this.onTap,//点击事件
})
TabBar中主要有两个参数:
tabs
属性接受一个Widget数组,表示每一个Tab子菜单,我们可以自定义,也可以像示例中一样直接使用Tab
组件,它是Material组件库提供的Material风格的Tab菜单。Tab
组件有三个可选参数,除了可以指定文字外,还可以指定Tab菜单图标,或者直接自定义组件样式。
controller:用于控制/监听Tab
菜单切换,作用于TabBar和TabBarView,负责两者之间联动。TabBar与TabBarView通过index有一一对应关系,并且默认提供了DefaultTabController建立两者之间的关系,若要切换动画以及监听切换交互,可以自定义一个 Controller。
TabBarView的参数:
const TabBarView({
Key key,
@required this.children,
this.controller,
this.physics,
this.dragStartBehavior = DragStartBehavior.start,
});
这里可以看到TabBarView中也有controller参数,即上述所说通过Controller来控制二者之间联动交互。
这里有几个注意事项:
接下来我们看下具体在项目中是如何使用的:
List titleTabs = = [
Tab(text: '首页',
icon: new Icon(Icons.android)
),
Tab(text: '社群',
icon: new Icon(Icons.home)
),
Tab(text: '财务',
icon: new Icon(Icons.accessibility)
),
];
TabController tabcontroller = TabController(
length: 3, //Tab页的个数
vsync: this //动画效果的异步处理,默认格式
);
class HomePageState extends State with SingleTickerProviderStateMixin {
//Tab页的控制器,可以用来定义Tab标签和内容页的坐标
TabController tabcontroller;
List titleTabs;
//生命周期方法插入渲染树时调用,只调用一次
@override
void initState() {
super.initState();
tabcontroller = TabController(
length: 3, //Tab页的个数
vsync: this //动画效果的异步处理,默认格式
);
titleTabs = [
Tab(text: '首页',
icon: new Icon(Icons.android)
),
Tab(text: '社群',
icon: new Icon(Icons.home)
),
Tab(text: '财务',
icon: new Icon(Icons.accessibility)
),
];
}
@override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading:false,
elevation: 1,
title: Text("首页",
style: TextStyle(color: Color.fromARGB(255, 51, 51, 51))),
backgroundColor: Colors.white,
),
body: Center(
child: TabBarView(
controller: tabcontroller,
children: [
Center(child:Text('view1')),
Center(child:Text('view2')),
Center(child:Text('view3')),
]),
),
bottomNavigationBar: Material(
color: Colors.white,
child: TabBar(
controller: tabcontroller,
tabs: titleTabs,
//tab被选中时的颜色,设置之后选中的时候,icon和text都会变色
labelColor: Color.fromARGB(255, 163, 117, 136),
//tab未被选中时的颜色,设置之后选中的时候,icon和text都会变色
unselectedLabelColor: Colors.black,
),
),
);
}
//组件即将销毁时调用
@override
void dispose() {
//释放内存,节省开销
tabcontroller.dispose();
super.dispose();
}
}
这里注意在组件销毁的时候记得释放tabcontroller。
我们通过分析Tabcontroller是如何协调TabBar和TabBarView联动这条线来深入的学习一下TabBar。
首先看一下TabBar内部实现:
@override
Widget build(BuildContext context) {
final MaterialLocalizations localizations = MaterialLocalizations.of(context);
if (_controller.length == 0) {
return Container(
height: _kTabHeight + widget.indicatorWeight,
);
}
final TabBarTheme tabBarTheme = TabBarTheme.of(context);
final List wrappedTabs = List(widget.tabs.length);
for (int i = 0; i < widget.tabs.length; i += 1) {
wrappedTabs[i] = Center(
heightFactor: 1.0,
child: Padding(
padding: widget.labelPadding ?? tabBarTheme.labelPadding ?? kTabLabelPadding,
child: KeyedSubtree(
key: _tabKeys[i],
child: widget.tabs[i],
),
),
);
}
if (_controller != null) {
final int previousIndex = _controller.previousIndex;
if (_controller.indexIsChanging) {
final Animation animation = _ChangeAnimation(_controller);
wrappedTabs[_currentIndex] = _buildStyledTab(wrappedTabs[_currentIndex], true, animation);
wrappedTabs[previousIndex] = _buildStyledTab(wrappedTabs[previousIndex], false, animation);
} else {
final int tabIndex = _currentIndex;
final Animation centerAnimation = _DragAnimation(_controller, tabIndex);
wrappedTabs[tabIndex] = _buildStyledTab(wrappedTabs[tabIndex], true, centerAnimation);
if (_currentIndex > 0) {
final int tabIndex = _currentIndex - 1;
final Animation previousAnimation = ReverseAnimation(_DragAnimation(_controller, tabIndex));
wrappedTabs[tabIndex] = _buildStyledTab(wrappedTabs[tabIndex], false, previousAnimation);
}
if (_currentIndex < widget.tabs.length - 1) {
final int tabIndex = _currentIndex + 1;
final Animation nextAnimation = ReverseAnimation(_DragAnimation(_controller, tabIndex));
wrappedTabs[tabIndex] = _buildStyledTab(wrappedTabs[tabIndex], false, nextAnimation);
}
}
}
final int tabCount = widget.tabs.length;
for (int index = 0; index < tabCount; index += 1) {
wrappedTabs[index] = InkWell(
onTap: () { _handleTap(index); },
child: Padding(
padding: EdgeInsets.only(bottom: widget.indicatorWeight),
child: Stack(
children: [
wrappedTabs[index],
Semantics(
selected: index == _currentIndex,
label: localizations.tabLabel(tabIndex: index + 1, tabCount: tabCount),
),
],
),
),
);
if (!widget.isScrollable)
wrappedTabs[index] = Expanded(child: wrappedTabs[index]);
}
Widget tabBar = CustomPaint(
painter: _indicatorPainter,
child: _TabStyle(
animation: kAlwaysDismissedAnimation,
selected: false,
labelColor: widget.labelColor,
unselectedLabelColor: widget.unselectedLabelColor,
labelStyle: widget.labelStyle,
unselectedLabelStyle: widget.unselectedLabelStyle,
child: _TabLabelBar(
onPerformLayout: _saveTabOffsets,
children: wrappedTabs,
),
),
);
if (widget.isScrollable) {
_scrollController ??= _TabBarScrollController(this);
tabBar = SingleChildScrollView(
dragStartBehavior: widget.dragStartBehavior,
scrollDirection: Axis.horizontal,
controller: _scrollController,
child: tabBar,
);
}
return tabBar;
}
这里主要看下onTap()点击事件,它执行了_handleTap(index);来看下_handleTap方法:
void _handleTap(int index) {
assert(index >= 0 && index < widget.tabs.length);
_controller.animateTo(index);
if (widget.onTap != null) {
widget.onTap(index);
}
}
该方法实现比较简单,可以看到controller的身影,它执行了animateTo方法,继续看animateTo实现:
void animateTo(int value, { Duration duration = kTabScrollDuration, Curve curve = Curves.ease }) {
_changeIndex(value, duration: duration, curve: curve);
}
这里可以看到controller是通过_changeIndex方法来进行对TabBar及TabBarView的控制:
void _changeIndex(int value, { Duration duration, Curve curve }) {
if (value == _index || length < 2)
return;
_previousIndex = index;
_index = value;
if (duration != null) {
_indexIsChangingCount += 1;
notifyListeners(); // Because the value of indexIsChanging may have changed.
_animationController
.animateTo(_index.toDouble(), duration: duration, curve: curve)
.whenCompleteOrCancel(() {
_indexIsChangingCount -= 1;
notifyListeners();
});
} else {
_indexIsChangingCount += 1;
_animationController.value = _index.toDouble();
_indexIsChangingCount -= 1;
notifyListeners();
}
}
更新index,并通知TabBar及TabBarView注册的listener进行页面切换,流程还是比较简单清晰的。