Flutter 自定义 TabBar 指示器

Jih*_*aji 4 dart flutter flutter-layout flutter-widget

如何自定义Flutter TabBar中的Tab指示器以达到下面的目标结果?

当前状态 当前选项卡栏状态

目标 目标 TabBar 结果

我该如何执行以下操作:

  1. 更改未选定的选项卡,如目标图片所示
  2. 调整波纹边框以适合内容并具有圆形边缘。

这是我的代码

class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  var categoryTabs = <Tab>[...];

  @override
  Widget build(BuildContext context) {
    return DefaultTabController(
      length: categoryTabs.length,
      child: Scaffold(
        appBar: AppBar(
          title: Text('My App'),
          centerTitle: true,
          bottom: PreferredSize(
            preferredSize: Size(100, 70),
            child: Column(
              children: [
                TabBar(
                  indicatorSize: TabBarIndicatorSize.tab,
                  indicatorColor: Colors.transparent,
                  labelColor: colorPrimaryDark,
                  isScrollable: true,
                  unselectedLabelColor: Colors.white,
                  indicator: BoxDecoration(
                    borderRadius: BorderRadius.circular(50),
                    color: Colors.white,
                  ),
                  tabs: categoryTabs,
                ),
                SizedBox(height: 10)
              ],
            ),
          ),
        ),
        body: SafeArea(...),
      ),
    );
  }
}

Run Code Online (Sandbox Code Playgroud)

hie*_*trq 10

当选项卡控制器从一个索引动画到另一个索引时,您需要用于AnimatedBuilder监听选项卡控制器动画。使用这种方法,您可以自定义项目的过渡动画TabBar。例如:将您的TabBar实现更改为以下内容

TabBar(
    tabs: tabs
      .asMap().entries
      .map((entry) => AnimatedBuilder(
        animation: _tabController.animation,
        builder: (ctx, snapshot) {
          
          final forward = _tabController.offset > 0;
          final backward = _tabController.offset < 0;
          int _fromIndex;
          int _toIndex;
          double progress;

          // This value is true during the [animateTo] animation that's triggered when the user taps a [TabBar] tab. 
          // It is false when [offset] is changing as a consequence of the user dragging the [TabBarView].
          if (_tabController.indexIsChanging) {
            _fromIndex = _tabController.previousIndex;
            _toIndex = _tabController.index;
            _cachedFromIdx = _tabController.previousIndex;
            _cachedToIdx = _tabController.index;
            progress = (_tabController.animation.value - _fromIndex).abs() / (_toIndex - _fromIndex).abs();
          } else {
            if (_cachedFromIdx == _tabController.previousIndex && _cachedToIdx == _tabController.index) {
              // When user tap on a tab bar and the animation is completed, it will execute this block
              // This block will not be called when user draging the TabBarView
              _fromIndex = _cachedFromIdx;
              _toIndex = _cachedToIdx;
              progress = 1;
              _cachedToIdx = null;
              _cachedFromIdx = null;
            } else {
              _cachedToIdx = null;
              _cachedFromIdx = null;
              _fromIndex = _tabController.index;
              _toIndex = forward
                ? _fromIndex + 1
                  : backward
                    ? _fromIndex - 1
                    : _fromIndex;
              progress = (_tabController.animation.value - _fromIndex).abs();
            }
          }
          
          return Container(
            padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
            decoration: BoxDecoration(
              color: entry.key == _fromIndex
                ? Color.lerp(Colors.white, Colors.red.shade900, progress)
                : entry.key == _toIndex
                  ? Color.lerp(Colors.red.shade900, Colors.white, progress)
                  : Color.lerp(Colors.red.shade900, Colors.red.shade900, progress),
              borderRadius: BorderRadius.circular(200),
            ),
            child: Text(
              entry.value.toUpperCase(),
              style: TextStyle(
                fontSize: 10,
                letterSpacing: 0.4,
                fontWeight: FontWeight.w700,
              ),
            ),
          );
          
        },
      ))
      .toList(),
    controller: _tabController,
    isScrollable: true,
    indicatorSize: TabBarIndicatorSize.label,
    indicatorWeight: 0,
    indicator: BoxDecoration(
      borderRadius: BorderRadius.circular(100),
    ),
    physics: const ClampingScrollPhysics(),
    unselectedLabelColor: Colors.white,
    labelColor: Colors.red,
    labelPadding: EdgeInsets.only(left: 12),
  ),
Run Code Online (Sandbox Code Playgroud)

请注意,在上面的示例中,我使用 2 个局部变量来跟踪当前和先前的索引。这专门用于处理用户在TabBar选项卡上单击以在一个索引到另一个索引之间设置动画的情况。将 log 命令放入AnimatedBuilder的构建器方法中以更好地理解它是如何工作的。

 int _cachedFromIdx;
 int _cachedToIdx;
Run Code Online (Sandbox Code Playgroud)

这是结果

在此输入图像描述


Mr *_* vd 5

检查这个代码

  class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {

  @override
  Widget build(BuildContext context) {
    return DefaultTabController(
      length: 3,
      child: Scaffold(
        appBar: AppBar(
          backgroundColor: Colors.red,
          bottom: PreferredSize(
            preferredSize: Size(100,20),
            child: Column(
              children: [
                TabBar(
                 // indicatorSize: TabBarIndicatorSize.tab,
                  indicatorColor: Colors.transparent,
                  labelColor: Colors.white,
                  unselectedLabelColor: Colors.black,
                  indicator: BoxDecoration(
                      borderRadius: BorderRadius.circular(50),
                      color: Colors.redAccent),
                  tabs: [
                    Tab(icon: Icon(Icons.directions_car)),
                    Tab(icon: Icon(Icons.report_problem)),
                    Tab(icon: Icon(Icons.report_problem)),
                  ],
                ),
                SizedBox(height: 10,)
              ],
            ),
          ),
        ),
        body: TabBarView(
          children: [
            Icon(Icons.directions_car),
            Icon(Icons.directions_transit),
            Icon(Icons.directions_bike),
          ],
        )
      ),
    );
  }
}
Run Code Online (Sandbox Code Playgroud)