【Flutter 專題】76 圖解基本 TabBar 標籤導航欄 (二)| 8月更文挑戰

阿策小和尚發表於2021-08-12

      “這是我參與8月更文挑戰的第12天,活動詳情檢視: 8月更文挑戰” juejin.cn/post/698796…

      小菜剛剛學習了 TabBar 標籤導航欄的使用,其中對於標籤指示器 indicator 的使用較少;小菜今天嘗試一下自定義標籤指示器;

      TabBar 提供了 indicator 指示器屬性;允許使用者自定義 indicator,但自定義的指示器會導致 indicatorColor / indicatorWeight / indicatorPadding 屬性失效;預設的指示器是 UnderlineTabIndicator

Decoration get _indicator {
  if (widget.indicator != null) return widget.indicator;
  final TabBarTheme tabBarTheme = TabBarTheme.of(context);
  if (tabBarTheme.indicator != null) return tabBarTheme.indicator;

  Color color = widget.indicatorColor ?? Theme.of(context).indicatorColor;
  if (color.value == Material.of(context).color?.value) color = Colors.white;

  return UnderlineTabIndicator(
      insets: widget.indicatorPadding,
      borderSide: BorderSide(width: widget.indicatorWeight, color: color));
}
複製程式碼

原始碼分析

class ACETabBarIndicator extends Decoration {
  @override
  BoxPainter createBoxPainter([onChanged]) => _ACETabBarIndicatorPainter(this, onChanged);
}

class _ACETabBarIndicatorPainter extends BoxPainter {
  final ACETabBarIndicator decoration;
  _ACETabBarIndicatorPainter(this.decoration, VoidCallback onChanged);

  @override
  void paint(Canvas canvas, Offset offset, ImageConfiguration configuration) {
    // TODO: implement paint
  }
}
複製程式碼

      分析原始碼可知,自定義 indicator 指示器均需繼承自 Decoration;其中繪製 BoxPainter 時可以通過 OffsetImageConfiguration 獲取對應 Tab 尺寸和所在位置;

案例嘗試

      小菜將自定義 ACETabBarIndicator 單獨出來,並未自定義 TabBar,因此不能直接使用 TabBar 中屬性,若需要直接使用 TabBar 中屬性可以嘗試將 ACETabBarIndicator 放置在 TabBar 原始碼中進行自定義;

      自定義主要是實現各種樣式的 paint 繪製過程,小菜簡單嘗試瞭如下幾種指示器樣式;

1. ACETabBarIndicatorType.circle -> 實心圓點

      小菜以設定 height 為直徑,在 Tab 底部中間位置繪製一個圓形,注意起始位置為底部中心位置減半徑;

canvas.drawCircle(
    Offset(offset.dx + (configuration.size.width) / 2, configuration.size.height - _height),
    _height, _paint);
複製程式碼

2. ACETabBarIndicatorType.triangle -> 上三角

      小菜通過繪製 Path 來生成一個上三角;其中需要相容一個三角高度上限;

if (_height > configuration.size.height) 
  _height = _kIndicatorHeight;
Path _path = Path()
  ..moveTo(offset.dx + (configuration.size.width) / 2 - _height, configuration.size.height)
  ..lineTo(_height * tan(pi / 6) + offset.dx + (configuration.size.width - _height) / 2,
      configuration.size.height - _height)
  ..lineTo(_height * tan(pi / 6) + offset.dx + (configuration.size.width + _height) / 2,
      configuration.size.height);
canvas.drawPath(_path, _paint);
複製程式碼

3. ACETabBarIndicatorType.rrect -> 圓角矩形(整個 Tab)

      小菜繪製一個圓角矩形,其中矩形的起始位置為 Offset 對應的 Tab 大小為 ImageConfiguration 尺寸;

canvas.drawRRect(
    RRect.fromRectAndRadius(
        Rect.fromLTWH(offset.dx, offset.dy, configuration.size.width, configuration.size.height),
        Radius.circular(_kIndicatorAngle)),
    _paint);
複製程式碼

4. ACETabBarIndicatorType.rrect_inner -> 圓角矩形(有內邊距)

      小菜無法準確獲取 TabWidgets 位置與尺寸,因此通過 height 來進行處理;其中矩形的起始高度需要減 1,因為 Paint 預設線寬為 1

canvas.drawRRect(
    RRect.fromRectAndRadius(
        Rect.fromLTWH(
            offset.dx + height, height - 1,
            configuration.size.width - height * 2,
            configuration.size.height - height * 2 - 2),
        Radius.circular(_kIndicatorAngle)),
    _paint);
複製程式碼

5. ACETabBarIndicatorType.runderline -> 圓角下劃線

      TabBar 預設的指示器樣式為 UnderlineTabIndicator;只需調整 Paint 筆觸線樣式為 round 即可;

canvas.drawLine(
    Offset(offset.dx, configuration.size.height - height / 2),
    Offset(offset.dx + configuration.size.width, configuration.size.height - height / 2),
    _paint..strokeWidth = height / 2);
複製程式碼

6. ACETabBarIndicatorType.runderline_fixed -> 定長圓角下劃線

      小菜設定定長的下劃線,需要注意的是若 Tab 寬度小於設定的 width 時,相容預設的寬度;

if (_width > configuration.size.width)
  _width = configuration.size.width / 3;
canvas.drawLine(
    Offset(offset.dx + (configuration.size.width - _width) / 2, configuration.size.height - height / 2),
    Offset(offset.dx + (configuration.size.width + _width) / 2, configuration.size.height - height / 2),
    _paint..strokeWidth = height / 2);
複製程式碼


      ACETabBarIndicator 案例原始碼


      小菜對自定義標籤指示器的適配測試還不夠完全,如有錯誤請多多指導!

來源: 阿策小和尚

相關文章