概述
CustomTabIndicator
是一个Flutter自定义Tab指示器组件,用于在TabBar中显示自定义图片作为选中状态的指示器。该组件支持将图片显示在Tab的底部位置,并可以自定义图片的尺寸。
类结构
1. CustomTabIndicator 类
继承关系 : extends Decoration
功能: 自定义Tab指示器的主要类,负责创建绘制器
属性
| 属性名 | 类型 | 说明 |
|--------|------|------|
| imagePath
| String
| 指示器图片的路径(必需) |
| width
| double
| 指示器的宽度(必需) |
| height
| double
| 指示器的高度(必需) |
构造函数
dart
CustomTabIndicator({
required this.imagePath,
required this.width,
required this.height,
});
方法
createBoxPainter([VoidCallback? onChanged])
: 创建自定义绘制器实例
2. _CustomTabIndicatorPainter 类**
继承关系 : extends BoxPainter
功能: 负责实际的绘制逻辑,是私有类
属性
与 CustomTabIndicator
相同的属性:
-
imagePath
: 图片路径 -
width
: 指示器宽度 -
height
: 指示器高度
核心方法
paint(Canvas canvas, Offset offset, ImageConfiguration configuration)
这是绘制方法的核心实现:
- 计算绘制区域:
```dart
final rect = offset & configuration.size!;
```
- 计算指示器位置:
```dart
final indicatorRect = Rect.fromLTWH(
rect.left + (rect.width - width) / 2, // 水平居中
rect.bottom - height, // 贴近底部
width,
height,
);
```
- 绘制图片:
-
使用
AssetImage
加载图片资源 -
通过
ImageStreamListener
监听图片加载完成 -
使用
canvas.drawImageRect
绘制图片到指定区域
使用方式
基本用法
dart
TabBar(
indicator: CustomTabIndicator(
imagePath: 'assets/images/tab_indicator.png',
width: 30.0,
height: 4.0,
),
tabs: [
Tab(text: '首页'),
Tab(text: '分类'),
Tab(text: '我的'),
],
)
完整示例
dart
import 'package:flutter/material.dart';
import 'package:your_app/common/widget/custom_tab_indicator.dart';
class MyTabPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return DefaultTabController(
length: 3,
child: Scaffold(
appBar: AppBar(
title: Text('自定义Tab指示器'),
bottom: TabBar(
indicator: CustomTabIndicator(
imagePath: 'assets/images/custom_indicator.png',
width: 40.0,
height: 3.0,
),
tabs: [
Tab(icon: Icon(Icons.home), text: '首页'),
Tab(icon: Icon(Icons.category), text: '分类'),
Tab(icon: Icon(Icons.person), text: '我的'),
],
),
),
body: TabBarView(
children: [
Center(child: Text('首页内容')),
Center(child: Text('分类内容')),
Center(child: Text('我的内容')),
],
),
),
);
}
}
技术特点
1. 位置计算
-
水平居中 :
rect.left + (rect.width - width) / 2
-
底部对齐 :
rect.bottom - height
2. 图片加载
-
使用
AssetImage
加载本地资源 -
异步加载机制,通过
ImageStreamListener
处理加载完成事件
3. 绘制优化
-
使用
drawImageRect
进行精确的图片绘制 -
支持图片缩放和裁剪
注意事项
-
图片资源 : 确保
imagePath
指向的图片资源存在于assets
目录中 -
尺寸设置 :
width
和height
应该根据实际图片尺寸和设计需求进行设置 -
性能考虑: 图片加载是异步的,首次显示可能会有短暂延迟
-
资源管理: 图片资源会被自动缓存,无需手动管理
总结
CustomTabIndicator
提供了一个简单而灵活的方式来创建自定义的Tab指示器。通过继承 Decoration
和 BoxPainter
,实现了完全自定义的绘制逻辑,支持图片资源作为指示器,并提供了精确的位置控制。该组件适用于需要特殊视觉效果的应用场景。
代码
arduino
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
// 自定义 TabBarIndicator - 显示在底部
class CustomTabIndicator extends Decoration {
final String imagePath;
final double width;
final double height;
const CustomTabIndicator({
required this.imagePath,
required this.width,
required this.height,
});
@override
BoxPainter createBoxPainter([VoidCallback? onChanged]) {
return _CustomTabIndicatorPainter(
imagePath: imagePath,
width: width,
height: height,
onChanged: onChanged,
);
}
}
class _CustomTabIndicatorPainter extends BoxPainter {
final String imagePath;
final double width;
final double height;
ImageStream? _imageStream;
ImageStreamListener? _imageStreamListener;
ui.Image? _cachedImage;
bool _isDisposed = false;
_CustomTabIndicatorPainter({
required this.imagePath,
required this.width,
required this.height,
VoidCallback? onChanged,
}) : super(onChanged);
@override
void paint(Canvas canvas, Offset offset, ImageConfiguration configuration) {
if (_isDisposed) return;
final rect = offset & configuration.size!;
// 计算 indicator 的位置,贴近底部
final indicatorRect = Rect.fromLTWH(
rect.left + (rect.width - width) / 2, // 水平居中
rect.bottom - height, // 贴近底部
width,
height,
);
// 如果已经有缓存的图片,直接绘制
if (_cachedImage != null) {
try {
canvas.drawImageRect(
_cachedImage!,
Rect.fromLTWH(0, 0, _cachedImage!.width.toDouble(), _cachedImage!.height.toDouble()),
indicatorRect,
Paint(),
);
return;
} catch (e) {
// 如果绘制失败,清除缓存并重新加载
_cachedImage = null;
}
}
// 加载图片
_loadImage(configuration);
}
void _loadImage(ImageConfiguration configuration) {
if (_isDisposed) return;
// 清理之前的监听器
_disposeImageStream();
final image = AssetImage(imagePath);
_imageStream = image.resolve(configuration);
_imageStreamListener = ImageStreamListener(
(ImageInfo info, bool synchronousCall) {
if (_isDisposed) return;
try {
_cachedImage = info.image;
// 触发重绘
onChanged?.call();
} catch (e) {
// 忽略绘制错误
debugPrint('Error drawing custom tab indicator: $e');
}
},
onError: (dynamic exception, StackTrace? stackTrace) {
debugPrint('Error loading custom tab indicator image: $exception');
},
);
_imageStream!.addListener(_imageStreamListener!);
}
void _disposeImageStream() {
if (_imageStream != null && _imageStreamListener != null) {
_imageStream!.removeListener(_imageStreamListener!);
}
_imageStream = null;
_imageStreamListener = null;
}
@override
void dispose() {
_isDisposed = true;
_disposeImageStream();
_cachedImage = null;
super.dispose();
}
}