Flutter 自定义TabBar指示器(indicator)实现秒杀UI样式

Posted Code-Porter

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Flutter 自定义TabBar指示器(indicator)实现秒杀UI样式相关的知识,希望对你有一定的参考价值。

一、话不多说,先来看下实现的交互效果,源码在文末

二、首先分解一下需求

  1. 自定义Tab指示器与Tab
  2. 当秒杀节点小于5个的时候,每一个Tab的宽度为平分屏幕宽度
  3. 当大于等于5个的时候,每一个Tab的宽度为固定宽度

1、先来看下最简单的TabBar与TabBarView需要如何实现

/// 省略若干代码...
@override
Widget build(BuildContext context) 
  return Scaffold(
    appBar: AppBar(
      title: Text('TabBar'),
      bottom: TabBar(
        controller: _tabController,
        tabs: [
          Tab(text: "热销"),
          Tab(text: "推荐"),
          Tab(text: "我的"),
        ],
      ),
    ),
    body: TabBarView(
      controller: _tabController,
      children: [
        Center(child: Text('热销')),
        Center(child: Text('推荐')),
        Center(child: Text('我的')),
      ],
    ),
  );

上面代码实现的效果

2、通过阅读TabBar的源码可以知道tabs属性接收widget集合,这也就是我们自定义Tab的突破口了

/// Typically a list of two or more [Tab] widgets.
///
/// The length of this list must match the [controller]'s [TabController.length]
/// and the length of the [TabBarView.children] list.
final List<Widget> tabs;

三、通过画图来分析一下整个Tab的层级结构

  • 白色层:就是TabBar所占的高度(Tab高度+三角箭头高度)
  • 灰色层:就是每一个Tab所占的高度
  • 红色层:就是自定义的TabBar indicator

1、通过上面的图我们需要定义如下一些数据:

  1. 每个Tab的宽度和高度
double _tabWidth = 82.0;
double _tabHeight = 70.0;
  1. 三角形的宽高
Size triangleSize = Size(10, 10);

2、每一个Tab通过Container组件来实现

class FlashSale extends StatefulWidget 
  @override
  FlashSaleState createState() => new FlashSaleState();

class FlashSaleState extends State<FlashSale>
    with SingleTickerProviderStateMixin 
    
  TabController _tabController;
  List<String> _list;
  double _tabWidth = 82.0;
  double _tabHeight = 70.0;
  ///定义三角的大小
  Size triangleSize = Size(10, 10);

  @override
  void initState() 
    super.initState();
    _list = ['10:00', '11:00', '12:00', '13:00', '14:00'];
    _tabController =
        TabController(initialIndex: 0, length: _list.length, vsync: this);
  
  
  @override
  Widget build(BuildContext context) 
    return Scaffold(
      ///省略....
      body: Column(
        children: [
          TabBar(
            isScrollable: true,
            controller: _tabController,
            tabs: _list.map((e) 
              return _tab(e);
            ).toList(),
          )
          ///省略TabBarView内容...
        ],
      ),
    );
  

  Widget _tab(String content) 
    return Container(
      width: _tabWidth,
      height: _tabHeight + triangleSize.height,
      child: Padding(
        padding: EdgeInsets.only(bottom: triangleSize.height),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(
              content,
              style: TextStyle(color: Colors.white, fontSize: 22),
            ),
            Text(
              '抢购中',
              style: TextStyle(color: Colors.white, fontSize: 14),
            )
          ],
        ),
      ),
    );
  
 

Container的高度就是上面说到的,需要加上三角形的高度;如下效果图
这里文字需要剧中,所以需要padding bottom 三角形的高度

3、现在就需要给每一个Tab添加灰色未选中的颜色了;很容易想到是加在Container中的,但是加在这里是有问题的我们需要把这个灰色加在TabBar这里;但是现在我们TabBar的高度=Tab高度 + 三角箭头高度所以加在这里会使得三角箭头区域也是灰色,那这样需要怎么处理呢?解决方法如下

  • 通过Stack布局方式在TabBar底部层叠个灰色的Container
///省略....
@override
Widget build(BuildContext context) 
  return Scaffold(
   ///省略....
    body: Column(
      children: [
        Stack(
          children: [
            Container(
              width: double.infinity,
              height: _tabHeight,
              color: Color(0xFF4D525C),
            ),
            TabBar(
              isScrollable: true,
              controller: _tabController,
              tabs: _list.map((e) 
                return _tab(e);
              ).toList(),
            )
          ],
        )
        ///省略TabBarView内容...
      ],
    ),
  );

四、现在重要的一步就是自定义TabBarindicator,通过源码可知是一个Decoration对象,所以我们只需继承它即可

1、一个继承Decoration最基本的结构就是下面的样子了

class TriangleIndicator extends Decoration 
  //三角形的大小
  final Size triangleSize;

  TriangleIndicator(this.triangleSize);

  @override
  BoxPainter createBoxPainter([VoidCallback onChanged]) 
    return _CustomBoxPainter(triangleSize);
  


class _CustomBoxPainter extends BoxPainter 
  final Size triangleSize;

  _CustomBoxPainter(this.triangleSize);
  
  @override
  void paint(Canvas canvas, Offset offset, ImageConfiguration configuration) 

  • 然后在TabBar处使用即可
//省略其它...
TabBar(
  isScrollable: true,
  controller: _tabController,
  indicator: TriangleIndicator(triangleSize),
  tabs: _list.map((e) 
    return _tab(e);
  ).toList(),
)

2、接下来就只需要在paint函数中将选中的Tab样式绘制出来即可(写过android自定义View看着就会显的很亲切了)

  • 绘制矩形
@override
void paint(Canvas canvas, Offset offset, ImageConfiguration configuration) 
  ///每个Tab的宽高
  Size tabSize = Size(configuration.size.width,
      configuration.size.height - triangleSize.height);
  final Rect rect = offset & tabSize;
  final Paint paint = Paint();
  paint.color = Color(0xFFFF443D);
  paint.style = PaintingStyle.fill;
  ///画Tab矩形
  canvas.drawRect(rect, paint);

  • offset参数:意思当前位置距离组建左上角的偏移量
  • configuration参数:当前画布的大小

  • 从图中发现绘制的高度减去了三角形区域的高度,可还是多出了一点,这是为什么呢?原因是TabBar的indicator默认给了高度为2,只需要将它设置为0即可
TabBar(
  //省略....
  indicatorWeight: 0,
)
  • 绘制三角形,这里通过Path来绘制,也就是三角形的 起点、中点、终点连接起来
@override
void paint(Canvas canvas, Offset offset, ImageConfiguration configuration) 
  ///每个Tab的宽高
  Size tabSize = Size(configuration.size.width,
      configuration.size.height - triangleSize.height);
  final Rect rect = offset & tabSize;
  final Paint paint = Paint();
  paint.color = Color(0xFFFF443D);
  paint.style = PaintingStyle.fill;
  ///画Tab矩形
  canvas.drawRect(rect, paint);
  ///画三角形
  double start = (tabSize.width - triangleSize.width) / 2;
  Path trianglePath = Path();
  ///起点
  trianglePath.moveTo(
      start + offset.dx, configuration.size.height - triangleSize.height);
  ///中点
  trianglePath.lineTo(
      (tabSize.width / 2) + offset.dx, configuration.size.height);
  ///终点
  trianglePath.lineTo(start + offset.dx + triangleSize.width,
      configuration.size.height - triangleSize.height);
  canvas.drawPath(trianglePath, paint);

五、最后一条需求动态改变每个Tab的宽高,这个也很简单:只需要判断当前有几个Tab然后动态计算即可,直接查看源码即可

本篇源码下载地址

以上是关于Flutter 自定义TabBar指示器(indicator)实现秒杀UI样式的主要内容,如果未能解决你的问题,请参考以下文章

六Flutter自定义Tabbar

Flutter——TabBar组件(顶部Tab切换组件)

flutter 自定义TabBar

如何像 Flutter 中的专业人士一样创建自定义大小的 TabBar?

Flutter TabBar 自定义点击事件

将自定义小部件添加到 Flutter TabBar 的活动选项卡