Flutter Tabbar 自定义选中下标 自定义Indicator

Flutter Tabbar 自定义选中下标 自定义Indicator

思考

Flutter中的Tabbar为我们提供了十分方便的下标控制器indicator,只不过系统提供的只能设置颜色,尺寸等有限的参数,面对复杂的UI设计搞,系统提供的参数可能就没法实现了,这时候我们就需要自己想办法自己去实现这个下标了。

方案一

我们可以使用Stack这种布局,配合对tabController.animation这个动画的监听,通过堆叠widget的方式,来实现Tabbar的下标。不过这种方法的问题是,需要自己去管理下标的状态,位置,可复用性也不强。

方案二

找到系统实现indicator的方法,把他替换掉,这样子也就不用自己管理下标的位置了。

我们选择第二种方式来展开。

跟踪代码

我们知道可以通过设置indicatorColor来修改系统indicator的颜色。所以看看indicatorColor在哪里被用到就知道系统是怎么设置indicator了。
TabBar源码地址/tabs.dart

进到tabs.dart文件直接搜索indicatorColor,在下面这个地方找到了对他的调用

class _TabBarState extends State<TabBar> {
    ······
    Decoration get _indicator {
        ······
        Color color = widget.indicatorColor ?? Theme.of(context).indicatorColor;
        ······
        return UnderlineTabIndicator(
        borderSide: BorderSide(
            width: widget.indicatorWeight,
            color: color,
        ),
        );
    }
    ······
}

看起来是用来创建了一个_indicator对象,我们继续搜索_indicator,看看在哪儿用到了。

class _TabBarState extends State<TabBar> {
    ······
    void _initIndicatorPainter() {
    _indicatorPainter = !_controllerIsValid ? null : _IndicatorPainter(
        controller: _controller!,
        indicator: _indicator,
        indicatorSize: widget.indicatorSize ?? TabBarTheme.of(context).indicatorSize,
        indicatorPadding: widget.indicatorPadding,
        tabKeys: _tabKeys,
        old: _indicatorPainter,
        );
    }
    ······
}

找到了,代码也很好懂,是在_initIndicatorPainter方法里面用来创建_IndicatorPainter了,那么这个_IndicatorPainter是什么呢,继续跟踪。

class _IndicatorPainter extends CustomPainter {
  _IndicatorPainter({
    required this.controller,
    required this.indicator,
    required this.indicatorSize,
    required this.tabKeys,
    required _IndicatorPainter? old,
    required this.indicatorPadding,
  }) : assert(controller != null),
       assert(indicator != null),
       super(repaint: controller.animation) {}

这下子就豁然开朗了,_IndicatorPainter是继承自CustomPainter类,原来Tabbar原本的那条选中线,是用CustomPainter画出来的。既然是CustomPainter,我们直接看CustomPainter#paint方法就好了。

    ······
    @override
    void paint(Canvas canvas, Size size) {
        _needsPaint = false;
        _painter ??= indicator.createBoxPainter(markNeedsPaint);

        final double index = controller.index.toDouble();
        final double value = controller.animation!.value;
        final bool ltr = index > value;
        final int from = (ltr ? value.floor() : value.ceil()).clamp(0, maxTabIndex).toInt();
        final int to = (ltr ? from + 1 : from - 1).clamp(0, maxTabIndex).toInt();
        final Rect fromRect = indicatorRect(size, from);
        final Rect toRect = indicatorRect(size, to);
        _currentRect = Rect.lerp(fromRect, toRect, (value - from).abs());
        assert(_currentRect != null);

        final ImageConfiguration configuration = ImageConfiguration(
        size: _currentRect!.size,
        textDirection: _currentTextDirection,
        );
        _painter!.paint(canvas, _currentRect!.topLeft, configuration);
    }
    ······

最后一行是调用了_painter!.paint方法来绘制选中下标的,如果我们在外面能自己控制这里canvas的绘制,不就想要什么样的下标都有了吗。

扩展

关于canvas的绘制,如果不熟悉可以看看以下资料,我百度上随便找来的,
https://juejin.cn/post/6844903805000089608

编写ExtendedTabs

我们不动flutter的源码,把flutter的tabs.dart文件复制出来,名字改为extened_tabs.dart单独修改。文件直接复制出来,会报一些错,我们一步步修改他们。

在复制tabs.dart的时候,可能会遇到空安全的问题,导致整个页面都是错误。建议将项目的dart版本升级到2.12+来支持空安全,或者自己手动去除tabs.dart里面的空安全

1.引用报错,extened_tabs.dart文件里面引用了一些源码里面的其他widget,我们项目目录下肯定是访问不到的,我们直接删了,然后引用import 'package:flutter/material.dart';

import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart' show DragStartBehavior;
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';

// import 'app_bar.dart';
// import 'colors.dart';
// import 'constants.dart';
// import 'debug.dart';
// import 'ink_well.dart';
// import 'material.dart';
// import 'material_localizations.dart';
// import 'material_state.dart';
// import 'tab_bar_theme.dart';
// import 'tab_controller.dart';
// import 'tab_indicator.dart';
// import 'theme.dart';

2.修改类名。为了不跟系统的冲突,我们把TabBar改名为ExtendedTabbar,需要注意同时要修改文件中其他用到的地方,比如

class _TabBarState extends State<ExtendedTabbar>

3.删除不用的类。extened_tabs文件中除了Tabbar,还有其他的一些公共类,为了不跟系统的冲突,我们把他们删掉。我这边只删了_TabBarState后面的所有类。大纲看起来是这个样子:
extened_tabs修改后的大纲

4。我们使用typedef定义一个绘制的回调方法。需要的参数有Canvas和对应的Rect

typedef CustomIndicatorPaint(Canvas canvas, Rect currentRect);

5.在_IndicatorPainter类中加上这个参数,并在CustomPainter#paint方法中使用它。


class _IndicatorPainter extends CustomPainter {
  _IndicatorPainter({
    this.customIndicatorPaint,
    ······
  })   : assert(controller != null),
        assert(indicator != null),
        super(repaint: controller.animation) {
    ······
    final CustomIndicatorPaint? customIndicatorPaint;
    ······
    @override
    void paint(Canvas canvas, Size size) {
        ······
        assert(_currentRect != null);
        //判断一下,如果传进来的customIndicatorPaint不为空就调用customIndicatorPaint方法,否则还是使用系统的绘制
        if (customIndicatorPaint != null) {
        customIndicatorPaint!(canvas, _currentRect!);
        } else {
        final ImageConfiguration configuration = ImageConfiguration(
            size: _currentRect!.size,
            textDirection: _currentTextDirection,
        );
        _painter!.paint(canvas, _currentRect!.topLeft, configuration);
        }
    }
    ······
  }

这样子_IndicatorPainter这个类就修改好了,但是_IndicatorPainter是个私有类,我们还需要从ExtendedTabbar中将customIndicatorPaint参数传进来。

6.修改ExtendedTabbar,将customIndicatorPaint从业务层传过来。

ExtendedTabbar

class ExtendedTabbar extends StatefulWidget implements PreferredSizeWidget {
    ······
    const ExtendedTabbar({
    ······
    this.physics,
    this.customIndicatorPaint,
  })  : 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);
    ······
}

接下来再修改_TabBarState,毕竟_IndicatorPainter是在这里面被创建的

_TabBarState

class _TabBarState extends State<ExtendedTabbar> {
    ······
    void _initIndicatorPainter() {
        _indicatorPainter = !_controllerIsValid
            ? null
            : _IndicatorPainter(
                ······
                old: _indicatorPainter,
                customIndicatorPaint: widget.customIndicatorPaint,
            );
    }
    ······
}

OK,大功告成,我们去用一下试试看。
7.使用方法

    Container(
        width: double.infinity,
        height: 68,
        child: ExtendedTabbar(
        controller: _tabController,
        indicatorColor: Colors.red,
        //重点看这里
        customIndicatorPaint: (canvas, currentRect) {
            Paint paint = Paint()
            ..isAntiAlias = true
            ..color = Colors.green
            ..strokeCap = StrokeCap.round
            ..strokeWidth = 2
            ..style = PaintingStyle.stroke;
            canvas.drawLine(
                currentRect.centerLeft, currentRect.bottomCenter, paint);
            canvas.drawLine(
                currentRect.bottomCenter, currentRect.centerRight, paint);
        },
        tabs: tabs
            .map((e) => Text(
                    e,
                    style: TextStyle(color: Colors.black),
                ))
            .toList(),
        ),
    ),

效果如下:

效果展示

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值