Flutter TabBar文字抖动问题及样式修改

1,435 阅读3分钟

需要解决的问题

1、当选择/未选中的字体大小不一样时,点击切换/PageView切换会出现字体抖动。

2、TabBar的item样式只能居中和滚动,无法像Row的MainAxisAlignment.spaceBetween属性一样排版。

当选择/未选中的字体大小不一样时,切换会出现字体抖动

切换TabBar的操作

操作A:点击TabBar

操作B:TabBar和PageView之类的组件一起使用时,PageView滑动从而带动TabBar切换

切换TabBar的操作共以上两种,而且两种操作需要分开处理,因为代码实现逻辑是不一样的。

源码修改

通过查阅TabBar的源码,可以知道,造成字体抖动的原因是:在字体样式发生变化时,选择了使用线性插值去实现一个渐变动画。本意是好的,但实际效果却不理想,出现了抖动问题。

image.png

此外,当TabBar和PageView之类的组件一起使用时,PageView滑动从而带动TabBar切换时,所执行的效果和直接点击TabBar的效果是不一样的,因为这两种情况用了两种不同的动画实现。

操作A执行的是下图if代码块,而操作B执行的是else代码块。操作A是使用了_ChangeAnimation,操作B则是使用了_DragAnimation

image.png

处理操作A

方法1:

通过代码可以知道,点击事件调用了_handleTap()方法,然后执行动画。

image.png

image.png

那么可以让这种切换过程改为即将完成才会改变大小。

image.png

方法2:

直接修改_TabStyle的代码,无视所做那些动画操作:

//是否是操作A
//isSelect可以在上图的if代码块赋值isSelect = true,else代码块赋值isSelect = false
bool isSelect;

@override
Widget build(BuildContext context) {
  final ThemeData themeData = Theme.of(context);
  final TabBarTheme tabBarTheme = TabBarTheme.of(context);
  final Animation<double> animation = listenable as Animation<double>;

  // To enable TextStyle.lerp(style1, style2, value), both styles must have
  // the same value of inherit. Force that to be inherit=true here.
  final TextStyle defaultStyle = (labelStyle ??
          tabBarTheme.labelStyle ??
          themeData.primaryTextTheme.bodyText1!)
      .copyWith(inherit: true);

  final TextStyle defaultUnselectedStyle = (unselectedLabelStyle ??
          tabBarTheme.unselectedLabelStyle ??
          labelStyle ??
          themeData.primaryTextTheme.bodyText1!)
      .copyWith(inherit: true);

  final Color selectedColor = labelColor ??
      tabBarTheme.labelColor ??
      themeData.primaryTextTheme.bodyText1!.color!;
  final Color unselectedColor = unselectedLabelColor ??
      tabBarTheme.unselectedLabelColor ??
      selectedColor.withAlpha(0xB2); // 70% alpha

  final Color color;
  final TextStyle textStyle;

  ///isisSelect:是否是操作A,如果想偷懒,不区分操作A和操作B,也是可以的
  ///这样的话,操作B就不执行animation
  if (isSelect) {
    ///去除点击时文字样式的动画
    textStyle = selected ? defaultStyle : defaultUnselectedStyle;

    ///去除点击时文字颜色的动画
    color = selected ? selectedColor : unselectedColor;
  } else {
    ///pageView滑动时文字样式的动画
    textStyle = selected
        ? TextStyle.lerp(
            defaultStyle, defaultUnselectedStyle, animation.value)!
        : TextStyle.lerp(
            defaultUnselectedStyle, defaultStyle, animation.value)!;

    ///pageView滑动时文字颜色的动画
    color = selected
        ? Color.lerp(selectedColor, unselectedColor, animation.value)!
        : Color.lerp(unselectedColor, selectedColor, animation.value)!;
  }

  return DefaultTextStyle(
    style: textStyle.copyWith(color: color),
    child: IconTheme.merge(
      data: IconThemeData(
        size: 24.0,
        color: color,
      ),
      child: child,
    ),
  );
}

处理操作B

操作B使用_DragAnimation来做动画,但是_DragAnimation的精度计算是有中间过渡,需要选择四舍五入的方式,取消中间过渡,这样就可以保留已有的代码,做最小的改动而实现我们需要的效果。偷懒的话,选用上面的代码不区分两种操作也ok。

image.png

修改代码
@override
double get value {
  assert(!controller.indexIsChanging);
  final double controllerMaxValue = (controller.length - 1).toDouble();
  final double controllerValue =
      controller.animation!.value.clamp(0.0, controllerMaxValue);
  // return (controllerValue - index.toDouble()).abs().clamp(0.0, 1.0);
  ///四舍五入,修复字体变化时,抖动问题
  return (controllerValue - index.toDouble())
      .abs()
      .clamp(0.0, 1.0)
      .roundToDouble();
}

至此,无论操作A还是操作B都不会出现字体抖动了。

TabBar的item样式只能居中和滚动,无法像Row的MainAxisAlignment.spaceBetween属性一样排版。

查阅代码,在_TabBarState中,可以看到当设置了isScrollable属性后,item的排列用了Expanded,所以就导致了item样式只能居中和滚动。

image.png

这里的修改就比较简单,自己新增一些参数,多加几个判断即可,如要实现Row的MainAxisAlignment.spaceBetween效果的排版,新增一个isBetweenTab参数来实现。

image.png