美文网首页Flutter
Flutter 自定义TabBar,实现 高度 和 标题与图标距

Flutter 自定义TabBar,实现 高度 和 标题与图标距

作者: 头发还没秃 | 来源:发表于2019-12-30 11:47 被阅读0次

    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是无法自定义设置这些参数的,这时候我们就要使出我们的绝招:魔改。
    首先上一下我的魔改效果:


    image

    第一步:把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,),),
      ]
    )
    

    相关文章

      网友评论

        本文标题:Flutter 自定义TabBar,实现 高度 和 标题与图标距

        本文链接:https://www.haomeiwen.com/subject/smgpoctx.html