TabBar 是基本每個App都會使用到的一個控件,在官方內置的 TabBar 的高度只有兩種規格且是不可修改的:
//未設置 Icon 時的高度
const double _kTabHeight = 46.0;
//設置 Icon 之後的高度
const double _kTextAndIconTabHeight = 72.0;
標題與Icon之間的距離也是寫死的:
margin: const EdgeInsets.only(bottom: 10.0),
Tab高度/文本與Icon的距離 設置詳見 class Tab 源碼:
class Tab extends StatelessWidget {
const Tab({
Key key,
this.text,
this.icon,
this.child,
}) : assert(text != null || child != null || icon != null),
assert(!(text != null && null != child)),
super(key: key);
final String text;
final Widget child;
final Widget icon;
Widget _buildLabelText() {
return child ?? Text(text, softWrap: false, overflow: TextOverflow.fade);
}
@override
Widget build(BuildContext context) {
assert(debugCheckHasMaterial(context));
double height;
Widget label;
if (icon == null) {//當沒有設置 Icon 時,Tab 高度 height 取值 _kTabHeight
height = _kTabHeight;
label = _buildLabelText();
} else if (text == null && child == null) {//當沒有設置 text 和 child 時,Tab 高度 height 取值 _kTabHeight
height = _kTabHeight;
label = icon;
} else {//其它情況, Tab 高度 height 取值 _kTextAndIconTabHeight
height = _kTextAndIconTabHeight;
label = Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Container(
child: icon,
margin: const EdgeInsets.only(bottom: 10.0),//這裏設置的是 text 和 Icon 的距離
),
_buildLabelText(),
],
);
}
return SizedBox(
height: height,
child: Center(
child: label,
widthFactor: 1.0,
),
);
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(StringProperty('text', text, defaultValue: null));
properties.add(DiagnosticsProperty<Widget>('icon', icon, defaultValue: null));
}
}
但是很多時候,我們會需要調整一下TabBar的高度或者標題文本與Icon之間的距離以達到我們UI的要求,而內置的TabBar是無法自定義設置這些參數的,這時候我們就要使出我們的絕招:魔改。
首先上一下我的魔改效果:
-
第一步,把TabBar源碼複製一份,爲了避免和系統內置的TabBar衝突,把複製的文件裏面的class都改個名,比如我 把 class Tab 改爲 class ZTab、class TabBar 改爲 class ZTabBar 等等
-
第二步,class ZTab 新增我們需要的屬性 :
class ZTab extends StatelessWidget { const ZTab({ Key key, this.text, this.icon, this.child, this.tabHeight = _kTabHeight, this.textToIconMargin = 0.0, }) : assert(text != null || child != null || icon != null), assert(!(text != null && null != child)), assert(textToIconMargin >= 0.0), super(key: key); /// Tab高度,默認 _kTabHeight final double tabHeight; /// Tab 文本與圖標的距離,默認 0.0 final double textToIconMargin; ... }
然後在 class ZTab 的 build 裏面進行設置:
@override Widget build(BuildContext context) { assert(debugCheckHasMaterial(context)); double height; Widget label; if (icon == null) { height = _kTabHeight; label = _buildLabelText(); } else if (text == null && child == null) { height = _kTabHeight; label = icon; } else { height = _kTextAndIconTabHeight; label = Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: <Widget>[ Container( child: icon, ///這裏設置文字與圖片的距離 ///使用自定義的 textToIconMargin margin: EdgeInsets.only(bottom: textToIconMargin), ), _buildLabelText(), ], ); } ///如果Tab自定義的高度大於 _kTabHeight 則 Tab 使用自定義的高度 ///我在這裏做了限制,限制條件可以自己設置 if (tabHeight > _kTabHeight) { height = tabHeight; } return SizedBox( height: height, child: Center( child: label, widthFactor: 1.0, ), ); }
以上修改就可以實現TabBar高度和文本與Icon距離的自定義了,但是在使用的時候,每個Tab都需要設置tabHeight和textToIconMargin,相當繁瑣且可能造成設置不統一:
TabBar( controller: _tabController, tabs: [ ZTab(text: "Home", icon: Icon(Icons.home, size: 27,), tabHeight: 54.0, textToIconMargin: 0.0,), ZTab(text: "Business", icon: Icon(Icons.business, size: 27,), tabHeight: 54.0, textToIconMargin: 0.0,), ZTab(text: "Me", icon: Icon(Icons.person, size: 27,), tabHeight: 54.0, textToIconMargin: 0.0,), ] )
爲了方便使用和減少錯誤,繼續改造
-
新增 Tab 輔助類 ZTabHelper:
///Tab 輔助類 class ZTabHelper { const ZTabHelper({ this.text, this.icon, this.child, }) : assert(text != null || child != null || icon != null), assert(!(text != null && null != child)); final String text; final Widget child; final Widget icon; }
-
修改 class ZTabBar:
class ZTabBar extends StatefulWidget implements PreferredSizeWidget { const ZTabBar({ Key key, ... @required this.tabs, this.tabHeight = _kTabHeight, this.textToIconMargin = 0.0, }) : assert(tabs != null), assert(isScrollable != null), assert(dragStartBehavior != null), assert(indicator != null || (indicatorWeight != null && indicatorWeight > 0.0)), assert(indicator != null || (indicatorPadding != null)), super(key: key); /// Tab高度 和 文字與圖標的距離 統一提取到 ZTabBar 裏面設置 /// Tab高度 final double tabHeight; /// Tab 文字與圖標的距離 final double textToIconMargin; /// 從直接設置 Tab 列表改爲設置我們自定義的 ZTabHelper final List<ZTabHelper> tabs; ... }
修改 @override Size get preferredSize:
@override Size get preferredSize { for (ZTabHelper item in tabs) { if (item is ZTab) { final ZTabHelper tab = item; if (tab.text != null && tab.icon != null) { ///做一下判斷,是否使用的是自定義高度 if (tabHeight > _kTabHeight) { return Size.fromHeight(tabHeight + indicatorWeight); } else { return Size.fromHeight(_kTextAndIconTabHeight + indicatorWeight); } } } } ///做一下判斷,是否使用的是自定義高度 if (tabHeight > _kTabHeight) { return Size.fromHeight(tabHeight + indicatorWeight); } else { return Size.fromHeight(_kTabHeight + indicatorWeight); } }
-
修改 class _ZTabBarState
class _ZTabBarState extends State<ZTabBar> { ... ///新增一個存放Tab的列表 List<Widget> _tabs = <Widget>[]; @override void initState() { ///首先判斷 _textToIconMargin, 如果 _textToIconMargin 小於 0 會報錯 double _textToIconMargin = widget.textToIconMargin; if (_textToIconMargin < 0) { _textToIconMargin = 0.0; } ///循環創建 Tab ///必須在 super.initState(); 之前創建好 Tab 列表,不然 build 的時候報錯 widget.tabs.forEach((zTabHelper){ _tabs.add(ZTab(text: zTabHelper.text, icon: zTabHelper.icon, child: zTabHelper.child, tabHeight: widget.tabHeight, textToIconMargin: _textToIconMargin,)); }); _tabKeys = _tabs.map((Widget tab) => GlobalKey()).toList(); super.initState(); } ... @override Widget build(BuildContext context) { ... final List<Widget> wrappedTabs = List<Widget>(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: _tabs[i],//改爲我們在initState裏面創建的tab列表 ), ), ); } ... return tabBar; } }
如此,一個可實現 高度 和 標題與圖標距離 自定義的TabBar就改造好了。
使用:
ZTabBar(
controller: _tabController,
tabHeight: 54.0,//自定義Tab高度
textToIconMargin: 0.0,//自定義標題與圖標距離
tabs: [
ZTabHelper(text: "Home", icon: Icon(Icons.home, size: 27,),),
ZTabHelper(text: "Business", icon: Icon(Icons.business, size: 27,),),
ZTabHelper(text: "Me", icon: Icon(Icons.person, size: 27,),),
]
)
解決 iPhone X 等劉海屏手機導航欄/底部黑線遮擋佈局的方法
改變 TabBar 背景顏色的方法
源碼:https://github.com/zhumj/flutter_ZTabBar.git