flutter 自定義TabBar,實現 高度 和 標題與圖標距離 可自定義的方案與實踐

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是無法自定義設置這些參數的,這時候我們就要使出我們的絕招:魔改。
首先上一下我的魔改效果:
在這裏插入圖片描述

  1. 第一步,把TabBar源碼複製一份,爲了避免和系統內置的TabBar衝突,把複製的文件裏面的class都改個名,比如我 把 class Tab 改爲 class ZTab、class TabBar 改爲 class ZTabBar 等等

  2. 第二步,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,),
      ]
    )
    

    爲了方便使用和減少錯誤,繼續改造

  3. 新增 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;
    }
    
  4. 修改 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);
      }
    }
    
  5. 修改 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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章