
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
一、TabBar 系统架构深度解析
在现代移动应用中,标签导航是最常见的导航模式之一。从简单的固定标签到复杂的滑动标签,Flutter 提供了 TabBar 组件来实现各种标签导航效果。理解这套架构的底层原理,是构建高性能标签导航系统的基础。
📱 1.1 Flutter TabBar 架构
Flutter 的 TabBar 系统由多个核心层次组成,每一层都有其特定的职责:
┌─────────────────────────────────────────────────────────────────┐
│ 应用层 (Application Layer) │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ TabBar, TabBarView, TabController, DefaultTabController│ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 指示器层 (Indicator Layer) │ │
│ │ TabIndicator, UnderlineTabIndicator, BoxDecoration... │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 动画层 (Animation Layer) │ │
│ │ AnimationController, Tween, CurvedAnimation... │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 状态管理层 (State Management Layer) │ │
│ │ TabController, TickerProvider, ChangeNotifier... │ │
│ └─────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
🔬 1.2 TabBar 核心组件详解
Flutter TabBar 系统的核心组件包括以下几个部分:
TabBar(标签栏)
TabBar 是显示标签列表的组件,支持多种自定义样式。
dart
TabBar(
tabs: const [
Tab(text: '首页'),
Tab(text: '发现'),
Tab(text: '我的'),
],
controller: tabController,
indicatorColor: Colors.blue,
labelColor: Colors.blue,
unselectedLabelColor: Colors.grey,
indicatorSize: TabBarIndicatorSize.label,
)
TabBarView(标签视图)
TabBarView 是显示标签内容的组件,支持滑动切换。
dart
TabBarView(
controller: tabController,
children: [
HomePage(),
DiscoverPage(),
ProfilePage(),
],
)
TabController(标签控制器)
TabController 用于控制标签切换和监听标签变化。
dart
final tabController = TabController(
length: 3,
vsync: this,
);
tabController.addListener(() {
if (!tabController.indexIsChanging) {
print('当前标签: ${tabController.index}');
}
});
tabController.animateTo(1);
🎯 1.3 标签导航设计原则
设计优秀的标签导航需要遵循以下原则:
┌─────────────────────────────────────────────────────────────┐
│ 标签导航设计原则 │
├─────────────────────────────────────────────────────────────┤
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 1. 清晰性 - 标签名称简洁明了,图标含义清晰 │ │
│ └─────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 2. 一致性 - 标签样式统一,交互方式一致 │ │
│ └─────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 3. 反馈性 - 选中状态明显,切换动画流畅 │ │
│ └─────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 4. 可访问性 - 标签数量适中,易于点击 │ │
│ └─────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 5. 上下文 - 标签内容相关,导航逻辑清晰 │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
标签类型对比:
| 类型 | 特点 | 适用场景 |
|---|---|---|
| 固定标签 | 数量固定,等宽分布 | 底部导航栏 |
| 滚动标签 | 数量可变,支持滑动 | 顶部分类导航 |
| 图标标签 | 图标+文字,直观 | 主导航 |
| 纯文字标签 | 简洁,节省空间 | 次级导航 |
二、基础标签导航实现
基础标签导航包括固定标签、滚动标签和图标标签。这些是构建复杂标签导航系统的基础。
👆 2.1 固定标签导航
固定标签导航是最常见的标签模式,标签数量固定且等宽分布。
dart
import 'package:flutter/material.dart';
/// 固定标签导航示例
class FixedTabDemo extends StatefulWidget {
const FixedTabDemo({super.key});
@override
State<FixedTabDemo> createState() => _FixedTabDemoState();
}
class _FixedTabDemoState extends State<FixedTabDemo>
with SingleTickerProviderStateMixin {
late TabController _tabController;
@override
void initState() {
super.initState();
_tabController = TabController(length: 3, vsync: this);
}
@override
void dispose() {
_tabController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('固定标签导航'),
bottom: TabBar(
controller: _tabController,
tabs: const [
Tab(text: '首页'),
Tab(text: '发现'),
Tab(text: '我的'),
],
indicatorColor: Colors.blue,
labelColor: Colors.blue,
unselectedLabelColor: Colors.grey,
),
),
body: TabBarView(
controller: _tabController,
children: [
_buildTabPage('首页', Colors.blue),
_buildTabPage('发现', Colors.green),
_buildTabPage('我的', Colors.orange),
],
),
);
}
Widget _buildTabPage(String title, Color color) {
return ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: 20,
itemBuilder: (context, index) {
return Card(
margin: const EdgeInsets.only(bottom: 12),
child: ListTile(
leading: Container(
width: 50,
height: 50,
decoration: BoxDecoration(
color: color.withOpacity(0.2),
borderRadius: BorderRadius.circular(8),
),
),
title: Text('$title - ${index + 1}'),
subtitle: Text('这是 $title 的第 ${index + 1} 项'),
),
);
},
);
}
}
🔄 2.2 滚动标签导航
滚动标签导航支持大量标签,用户可以滑动查看更多标签。
dart
/// 滚动标签导航示例
class ScrollableTabDemo extends StatefulWidget {
const ScrollableTabDemo({super.key});
@override
State<ScrollableTabDemo> createState() => _ScrollableTabDemoState();
}
class _ScrollableTabDemoState extends State<ScrollableTabDemo>
with SingleTickerProviderStateMixin {
late TabController _tabController;
final List<String> _tabs = [
'推荐', '热门', '视频', '小说', '娱乐', '科技', '体育', '财经', '军事', '历史'
];
@override
void initState() {
super.initState();
_tabController = TabController(length: _tabs.length, vsync: this);
}
@override
void dispose() {
_tabController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('滚动标签导航'),
bottom: TabBar(
controller: _tabController,
isScrollable: true,
tabs: _tabs.map((tab) => Tab(text: tab)).toList(),
indicatorColor: Colors.teal,
labelColor: Colors.teal,
unselectedLabelColor: Colors.grey,
labelStyle: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
unselectedLabelStyle: const TextStyle(fontSize: 14),
),
),
body: TabBarView(
controller: _tabController,
children: _tabs.map((tab) => _buildTabPage(tab)).toList(),
),
);
}
Widget _buildTabPage(String title) {
return GridView.builder(
padding: const EdgeInsets.all(8),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
childAspectRatio: 0.8,
crossAxisSpacing: 8,
mainAxisSpacing: 8,
),
itemCount: 10,
itemBuilder: (context, index) {
return Card(
child: Column(
children: [
Expanded(
child: Container(
color: Colors.primaries[index % Colors.primaries.length].withOpacity(0.2),
child: Center(
child: Text('$title - ${index + 1}'),
),
),
),
Padding(
padding: const EdgeInsets.all(8),
child: Text('$title 内容 ${index + 1}'),
),
],
),
);
},
);
}
}
🌊 2.3 图标标签导航
图标标签导航结合图标和文字,提供更直观的导航体验。
dart
/// 图标标签导航示例
class IconTabDemo extends StatefulWidget {
const IconTabDemo({super.key});
@override
State<IconTabDemo> createState() => _IconTabDemoState();
}
class _IconTabDemoState extends State<IconTabDemo>
with SingleTickerProviderStateMixin {
late TabController _tabController;
@override
void initState() {
super.initState();
_tabController = TabController(length: 4, vsync: this);
}
@override
void dispose() {
_tabController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('图标标签导航'),
bottom: TabBar(
controller: _tabController,
tabs: const [
Tab(icon: Icon(Icons.home), text: '首页'),
Tab(icon: Icon(Icons.search), text: '搜索'),
Tab(icon: Icon(Icons.favorite), text: '收藏'),
Tab(icon: Icon(Icons.person), text: '我的'),
],
indicatorColor: Colors.purple,
labelColor: Colors.purple,
unselectedLabelColor: Colors.grey,
),
),
body: TabBarView(
controller: _tabController,
children: [
_buildIconPage(Icons.home, '首页', Colors.blue),
_buildIconPage(Icons.search, '搜索', Colors.green),
_buildIconPage(Icons.favorite, '收藏', Colors.red),
_buildIconPage(Icons.person, '我的', Colors.orange),
],
),
);
}
Widget _buildIconPage(IconData icon, String title, Color color) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
width: 100,
height: 100,
decoration: BoxDecoration(
color: color.withOpacity(0.1),
shape: BoxShape.circle,
),
child: Icon(icon, size: 50, color: color),
),
const SizedBox(height: 16),
Text(title, style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold)),
],
),
);
}
}
三、高级标签导航实现
高级标签导航包括自定义指示器、动画标签、分段标签和底部标签栏。
📊 3.1 自定义标签指示器
自定义标签指示器可以实现各种独特的视觉效果。
dart
/// 自定义指示器示例
class CustomIndicatorDemo extends StatefulWidget {
const CustomIndicatorDemo({super.key});
@override
State<CustomIndicatorDemo> createState() => _CustomIndicatorDemoState();
}
class _CustomIndicatorDemoState extends State<CustomIndicatorDemo>
with SingleTickerProviderStateMixin {
late TabController _tabController;
@override
void initState() {
super.initState();
_tabController = TabController(length: 4, vsync: this);
}
@override
void dispose() {
_tabController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('自定义指示器'),
bottom: TabBar(
controller: _tabController,
tabs: const [
Tab(text: '推荐'),
Tab(text: '热门'),
Tab(text: '最新'),
Tab(text: '关注'),
],
indicator: _CustomTabIndicator(
color: Colors.blue,
radius: 20,
),
labelColor: Colors.white,
unselectedLabelColor: Colors.grey,
),
),
body: TabBarView(
controller: _tabController,
children: List.generate(
4,
(index) => Center(child: Text('页面 ${index + 1}')),
),
),
);
}
}
class _CustomTabIndicator extends Decoration {
final Color color;
final double radius;
const _CustomTabIndicator({
required this.color,
this.radius = 20,
});
@override
BoxPainter createBoxPainter([VoidCallback? onChanged]) {
return _CustomTabIndicatorPainter(color, radius);
}
}
class _CustomTabIndicatorPainter extends BoxPainter {
final Color color;
final double radius;
_CustomTabIndicatorPainter(this.color, this.radius);
@override
void paint(Canvas canvas, Offset offset, ImageConfiguration configuration) {
final rect = offset & configuration.size!;
final paint = Paint()
..color = color
..style = PaintingStyle.fill;
final rrect = RRect.fromRectAndRadius(
Rect.fromCenter(
center: rect.center,
width: rect.width - 16,
height: rect.height - 8,
),
Radius.circular(radius),
);
canvas.drawRRect(rrect, paint);
}
}
📝 3.2 动画标签切换
动画标签切换通过动画效果增强用户体验。
dart
/// 动画标签切换示例
class AnimatedTabDemo extends StatefulWidget {
const AnimatedTabDemo({super.key});
@override
State<AnimatedTabDemo> createState() => _AnimatedTabDemoState();
}
class _AnimatedTabDemoState extends State<AnimatedTabDemo>
with SingleTickerProviderStateMixin {
late TabController _tabController;
final List<String> _tabs = ['消息', '通讯录', '发现', '我'];
int _currentIndex = 0;
@override
void initState() {
super.initState();
_tabController = TabController(length: _tabs.length, vsync: this);
_tabController.addListener(_onTabChanged);
}
void _onTabChanged() {
if (!_tabController.indexIsChanging) {
setState(() => _currentIndex = _tabController.index);
}
}
@override
void dispose() {
_tabController.removeListener(_onTabChanged);
_tabController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('动画标签切换')),
body: Column(
children: [
_buildAnimatedTabBar(),
Expanded(
child: TabBarView(
controller: _tabController,
children: _tabs.map((tab) => _buildTabPage(tab)).toList(),
),
),
],
),
);
}
Widget _buildAnimatedTabBar() {
return Container(
height: 56,
color: Colors.white,
child: Stack(
children: [
AnimatedPositioned(
duration: const Duration(milliseconds: 300),
curve: Curves.easeOutCubic,
left: _currentIndex * (MediaQuery.of(context).size.width / _tabs.length),
top: 0,
child: Container(
width: MediaQuery.of(context).size.width / _tabs.length,
height: 56,
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(color: Colors.blue, width: 3),
),
),
),
),
Row(
children: List.generate(_tabs.length, (index) {
final isSelected = index == _currentIndex;
return Expanded(
child: GestureDetector(
onTap: () => _tabController.animateTo(index),
behavior: HitTestBehavior.opaque,
child: AnimatedDefaultTextStyle(
duration: const Duration(milliseconds: 200),
style: TextStyle(
color: isSelected ? Colors.blue : Colors.grey,
fontSize: isSelected ? 16 : 14,
fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
),
child: Center(child: Text(_tabs[index])),
),
),
);
}),
),
],
),
);
}
Widget _buildTabPage(String title) {
return ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: 15,
itemBuilder: (context, index) {
return Card(
margin: const EdgeInsets.only(bottom: 12),
child: ListTile(
title: Text('$title - ${index + 1}'),
subtitle: Text('这是 $title 的内容'),
),
);
},
);
}
}
🔄 3.3 分段标签
分段标签类似于 iOS 的分段控制器,适合少量选项的切换。
dart
/// 分段标签示例
class SegmentedTabDemo extends StatefulWidget {
const SegmentedTabDemo({super.key});
@override
State<SegmentedTabDemo> createState() => _SegmentedTabDemoState();
}
class _SegmentedTabDemoState extends State<SegmentedTabDemo>
with SingleTickerProviderStateMixin {
late TabController _tabController;
final List<String> _tabs = ['日', '周', '月', '年'];
@override
void initState() {
super.initState();
_tabController = TabController(length: _tabs.length, vsync: this);
}
@override
void dispose() {
_tabController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('分段标签')),
body: Column(
children: [
Container(
margin: const EdgeInsets.all(16),
padding: const EdgeInsets.all(4),
decoration: BoxDecoration(
color: Colors.grey[200],
borderRadius: BorderRadius.circular(12),
),
child: TabBar(
controller: _tabController,
tabs: _tabs.map((tab) => Tab(text: tab)).toList(),
indicator: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(8),
boxShadow: [
BoxShadow(
color: Colors.blue.withOpacity(0.3),
blurRadius: 4,
offset: const Offset(0, 2),
),
],
),
labelColor: Colors.white,
unselectedLabelColor: Colors.grey[700],
dividerColor: Colors.transparent,
),
),
Expanded(
child: TabBarView(
controller: _tabController,
children: _tabs.map((tab) => _buildTabPage(tab)).toList(),
),
),
],
),
);
}
Widget _buildTabPage(String title) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.calendar_today, size: 80, color: Colors.blue),
const SizedBox(height: 16),
Text('$title 统计数据', style: const TextStyle(fontSize: 24)),
],
),
);
}
}
⬇️ 3.4 底部标签栏
底部标签栏是移动应用中最常见的导航模式。
dart
/// 底部标签栏示例
class BottomTabDemo extends StatefulWidget {
const BottomTabDemo({super.key});
@override
State<BottomTabDemo> createState() => _BottomTabDemoState();
}
class _BottomTabDemoState extends State<BottomTabDemo>
with SingleTickerProviderStateMixin {
late TabController _tabController;
int _currentIndex = 0;
final List<_TabItem> _tabs = [
_TabItem(icon: Icons.home, activeIcon: Icons.home_filled, title: '首页'),
_TabItem(icon: Icons.search, activeIcon: Icons.search, title: '发现'),
_TabItem(icon: Icons.add_box_outlined, activeIcon: Icons.add_box, title: '发布'),
_TabItem(icon: Icons.favorite_border, activeIcon: Icons.favorite, title: '消息'),
_TabItem(icon: Icons.person_outline, activeIcon: Icons.person, title: '我的'),
];
@override
void initState() {
super.initState();
_tabController = TabController(length: _tabs.length, vsync: this);
_tabController.addListener(() {
if (!_tabController.indexIsChanging) {
setState(() => _currentIndex = _tabController.index);
}
});
}
@override
void dispose() {
_tabController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: TabBarView(
controller: _tabController,
physics: const NeverScrollableScrollPhysics(),
children: _tabs.map((tab) => _buildTabPage(tab.title)).toList(),
),
bottomNavigationBar: Container(
decoration: BoxDecoration(
color: Colors.white,
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 10,
),
],
),
child: SafeArea(
child: SizedBox(
height: 60,
child: Row(
children: List.generate(_tabs.length, (index) {
final tab = _tabs[index];
final isSelected = index == _currentIndex;
return Expanded(
child: GestureDetector(
onTap: () => _tabController.animateTo(index),
behavior: HitTestBehavior.opaque,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
AnimatedContainer(
duration: const Duration(milliseconds: 200),
child: Icon(
isSelected ? tab.activeIcon : tab.icon,
color: isSelected ? Colors.blue : Colors.grey,
size: isSelected ? 28 : 24,
),
),
const SizedBox(height: 4),
AnimatedDefaultTextStyle(
duration: const Duration(milliseconds: 200),
style: TextStyle(
color: isSelected ? Colors.blue : Colors.grey,
fontSize: 12,
fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
),
child: Text(tab.title),
),
],
),
),
);
}),
),
),
),
),
);
}
Widget _buildTabPage(String title) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
width: 100,
height: 100,
decoration: BoxDecoration(
color: Colors.blue.withOpacity(0.1),
shape: BoxShape.circle,
),
child: const Icon(Icons.home, size: 50, color: Colors.blue),
),
const SizedBox(height: 16),
Text(title, style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold)),
],
),
);
}
}
class _TabItem {
final IconData icon;
final IconData activeIcon;
final String title;
const _TabItem({
required this.icon,
required this.activeIcon,
required this.title,
});
}
四、完整示例:TabBar 高级标签系统
下面是一个完整的 TabBar 高级标签系统示例:
dart
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
useMaterial3: true,
),
home: const TabBarHomePage(),
);
}
}
class TabBarHomePage extends StatelessWidget {
const TabBarHomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('📑 TabBar 高级标签系统')),
body: ListView(
padding: const EdgeInsets.all(16),
children: [
_buildSectionCard(context, title: '固定标签', description: '基础固定标签导航', icon: Icons.tab, color: Colors.blue, onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const FixedTabDemo()))),
_buildSectionCard(context, title: '滚动标签', description: '可滑动标签导航', icon: Icons.swipe, color: Colors.teal, onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const ScrollableTabDemo()))),
_buildSectionCard(context, title: '图标标签', description: '图标+文字标签', icon: Icons.image, color: Colors.purple, onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const IconTabDemo()))),
_buildSectionCard(context, title: '自定义指示器', description: '圆角背景指示器', icon: Icons.brush, color: Colors.orange, onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const CustomIndicatorDemo()))),
_buildSectionCard(context, title: '动画标签', description: '动画切换效果', icon: Icons.animation, color: Colors.pink, onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const AnimatedTabDemo()))),
_buildSectionCard(context, title: '分段标签', description: 'iOS风格分段', icon: Icons.view_module, color: Colors.indigo, onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const SegmentedTabDemo()))),
_buildSectionCard(context, title: '底部标签栏', description: '底部导航栏', icon: Icons.navigation, color: Colors.cyan, onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const BottomTabDemo()))),
],
),
);
}
Widget _buildSectionCard(BuildContext context, {required String title, required String description, required IconData icon, required Color color, required VoidCallback onTap}) {
return Card(
margin: const EdgeInsets.only(bottom: 12),
child: InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(12),
child: Padding(
padding: const EdgeInsets.all(16),
child: Row(
children: [
Container(width: 56, height: 56, decoration: BoxDecoration(color: color.withOpacity(0.1), borderRadius: BorderRadius.circular(12)), child: Icon(icon, color: color, size: 28)),
const SizedBox(width: 16),
Expanded(child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [Text(title, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold)), const SizedBox(height: 4), Text(description, style: TextStyle(fontSize: 13, color: Colors.grey[600]))])),
Icon(Icons.chevron_right, color: Colors.grey[400]),
],
),
),
),
);
}
}
class FixedTabDemo extends StatefulWidget {
const FixedTabDemo({super.key});
@override
State<FixedTabDemo> createState() => _FixedTabDemoState();
}
class _FixedTabDemoState extends State<FixedTabDemo> with SingleTickerProviderStateMixin {
late TabController _tabController;
@override
void initState() {
super.initState();
_tabController = TabController(length: 3, vsync: this);
}
@override
void dispose() { _tabController.dispose(); super.dispose(); }
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('固定标签导航'),
bottom: TabBar(controller: _tabController, tabs: const [Tab(text: '首页'), Tab(text: '发现'), Tab(text: '我的')], indicatorColor: Colors.blue, labelColor: Colors.blue),
),
body: TabBarView(
controller: _tabController,
children: [_buildTabPage('首页', Colors.blue), _buildTabPage('发现', Colors.green), _buildTabPage('我的', Colors.orange)],
),
);
}
Widget _buildTabPage(String title, Color color) {
return ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: 15,
itemBuilder: (context, index) => Card(
margin: const EdgeInsets.only(bottom: 12),
child: ListTile(
leading: Container(width: 50, height: 50, decoration: BoxDecoration(color: color.withOpacity(0.2), borderRadius: BorderRadius.circular(8))),
title: Text('$title - ${index + 1}'),
subtitle: Text('这是 $title 的第 ${index + 1} 项'),
),
),
);
}
}
class ScrollableTabDemo extends StatefulWidget {
const ScrollableTabDemo({super.key});
@override
State<ScrollableTabDemo> createState() => _ScrollableTabDemoState();
}
class _ScrollableTabDemoState extends State<ScrollableTabDemo> with SingleTickerProviderStateMixin {
late TabController _tabController;
final List<String> _tabs = ['推荐', '热门', '视频', '小说', '娱乐', '科技', '体育', '财经', '军事', '历史'];
@override
void initState() {
super.initState();
_tabController = TabController(length: _tabs.length, vsync: this);
}
@override
void dispose() { _tabController.dispose(); super.dispose(); }
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('滚动标签导航'),
bottom: TabBar(controller: _tabController, isScrollable: true, tabs: _tabs.map((tab) => Tab(text: tab)).toList(), indicatorColor: Colors.teal, labelColor: Colors.teal),
),
body: TabBarView(controller: _tabController, children: _tabs.map((tab) => Center(child: Text(tab, style: const TextStyle(fontSize: 24)))).toList()),
);
}
}
class IconTabDemo extends StatefulWidget {
const IconTabDemo({super.key});
@override
State<IconTabDemo> createState() => _IconTabDemoState();
}
class _IconTabDemoState extends State<IconTabDemo> with SingleTickerProviderStateMixin {
late TabController _tabController;
@override
void initState() {
super.initState();
_tabController = TabController(length: 4, vsync: this);
}
@override
void dispose() { _tabController.dispose(); super.dispose(); }
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('图标标签导航'),
bottom: TabBar(controller: _tabController, tabs: const [Tab(icon: Icon(Icons.home), text: '首页'), Tab(icon: Icon(Icons.search), text: '搜索'), Tab(icon: Icon(Icons.favorite), text: '收藏'), Tab(icon: Icon(Icons.person), text: '我的')], indicatorColor: Colors.purple, labelColor: Colors.purple),
),
body: TabBarView(
controller: _tabController,
children: [_buildIconPage(Icons.home, '首页', Colors.blue), _buildIconPage(Icons.search, '搜索', Colors.green), _buildIconPage(Icons.favorite, '收藏', Colors.red), _buildIconPage(Icons.person, '我的', Colors.orange)],
),
);
}
Widget _buildIconPage(IconData icon, String title, Color color) {
return Center(child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [Container(width: 100, height: 100, decoration: BoxDecoration(color: color.withOpacity(0.1), shape: BoxShape.circle), child: Icon(icon, size: 50, color: color)), const SizedBox(height: 16), Text(title, style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold))]));
}
}
class CustomIndicatorDemo extends StatefulWidget {
const CustomIndicatorDemo({super.key});
@override
State<CustomIndicatorDemo> createState() => _CustomIndicatorDemoState();
}
class _CustomIndicatorDemoState extends State<CustomIndicatorDemo> with SingleTickerProviderStateMixin {
late TabController _tabController;
@override
void initState() {
super.initState();
_tabController = TabController(length: 4, vsync: this);
}
@override
void dispose() { _tabController.dispose(); super.dispose(); }
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('自定义指示器'),
bottom: TabBar(controller: _tabController, tabs: const [Tab(text: '推荐'), Tab(text: '热门'), Tab(text: '最新'), Tab(text: '关注')], indicator: _CustomTabIndicator(color: Colors.blue), labelColor: Colors.white, unselectedLabelColor: Colors.grey),
),
body: TabBarView(controller: _tabController, children: List.generate(4, (index) => Center(child: Text('页面 ${index + 1}', style: const TextStyle(fontSize: 24))))),
);
}
}
class _CustomTabIndicator extends Decoration {
final Color color;
const _CustomTabIndicator({required this.color});
@override
BoxPainter createBoxPainter([VoidCallback? onChanged]) => _CustomTabIndicatorPainter(color);
}
class _CustomTabIndicatorPainter extends BoxPainter {
final Color color;
_CustomTabIndicatorPainter(this.color);
@override
void paint(Canvas canvas, Offset offset, ImageConfiguration configuration) {
final rect = offset & configuration.size!;
final paint = Paint()..color = color..style = PaintingStyle.fill;
final rrect = RRect.fromRectAndRadius(Rect.fromCenter(center: rect.center, width: rect.width - 16, height: rect.height - 8), const Radius.circular(20));
canvas.drawRRect(rrect, paint);
}
}
class AnimatedTabDemo extends StatefulWidget {
const AnimatedTabDemo({super.key});
@override
State<AnimatedTabDemo> createState() => _AnimatedTabDemoState();
}
class _AnimatedTabDemoState extends State<AnimatedTabDemo> with SingleTickerProviderStateMixin {
late TabController _tabController;
final List<String> _tabs = ['消息', '通讯录', '发现', '我'];
int _currentIndex = 0;
@override
void initState() {
super.initState();
_tabController = TabController(length: _tabs.length, vsync: this);
_tabController.addListener(() { if (!_tabController.indexIsChanging) setState(() => _currentIndex = _tabController.index); });
}
@override
void dispose() { _tabController.dispose(); super.dispose(); }
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('动画标签切换')),
body: Column(
children: [
Container(
height: 56,
color: Colors.white,
child: Stack(
children: [
AnimatedPositioned(
duration: const Duration(milliseconds: 300),
left: _currentIndex * (MediaQuery.of(context).size.width / _tabs.length),
child: Container(width: MediaQuery.of(context).size.width / _tabs.length, height: 56, decoration: const BoxDecoration(border: Border(bottom: BorderSide(color: Colors.pink, width: 3)))),
),
Row(
children: List.generate(_tabs.length, (index) {
final isSelected = index == _currentIndex;
return Expanded(
child: GestureDetector(
onTap: () => _tabController.animateTo(index),
child: Center(child: AnimatedDefaultTextStyle(duration: const Duration(milliseconds: 200), style: TextStyle(color: isSelected ? Colors.pink : Colors.grey, fontSize: isSelected ? 16 : 14, fontWeight: isSelected ? FontWeight.bold : FontWeight.normal), child: Text(_tabs[index]))),
),
);
}),
),
],
),
),
Expanded(child: TabBarView(controller: _tabController, children: _tabs.map((tab) => Center(child: Text(tab, style: const TextStyle(fontSize: 24)))).toList())),
],
),
);
}
}
class SegmentedTabDemo extends StatefulWidget {
const SegmentedTabDemo({super.key});
@override
State<SegmentedTabDemo> createState() => _SegmentedTabDemoState();
}
class _SegmentedTabDemoState extends State<SegmentedTabDemo> with SingleTickerProviderStateMixin {
late TabController _tabController;
final List<String> _tabs = ['日', '周', '月', '年'];
@override
void initState() {
super.initState();
_tabController = TabController(length: _tabs.length, vsync: this);
}
@override
void dispose() { _tabController.dispose(); super.dispose(); }
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('分段标签')),
body: Column(
children: [
Container(
margin: const EdgeInsets.all(16),
padding: const EdgeInsets.all(4),
decoration: BoxDecoration(color: Colors.grey[200], borderRadius: BorderRadius.circular(12)),
child: TabBar(controller: _tabController, tabs: _tabs.map((tab) => Tab(text: tab)).toList(), indicator: BoxDecoration(color: Colors.indigo, borderRadius: BorderRadius.circular(8)), labelColor: Colors.white, unselectedLabelColor: Colors.grey[700], dividerColor: Colors.transparent),
),
Expanded(child: TabBarView(controller: _tabController, children: _tabs.map((tab) => Center(child: Text('$tab 统计', style: const TextStyle(fontSize: 24)))).toList())),
],
),
);
}
}
class BottomTabDemo extends StatefulWidget {
const BottomTabDemo({super.key});
@override
State<BottomTabDemo> createState() => _BottomTabDemoState();
}
class _BottomTabDemoState extends State<BottomTabDemo> with SingleTickerProviderStateMixin {
late TabController _tabController;
int _currentIndex = 0;
final List<_TabItem> _tabs = [
_TabItem(icon: Icons.home_outlined, activeIcon: Icons.home, title: '首页'),
_TabItem(icon: Icons.search_outlined, activeIcon: Icons.search, title: '发现'),
_TabItem(icon: Icons.add_box_outlined, activeIcon: Icons.add_box, title: '发布'),
_TabItem(icon: Icons.favorite_border, activeIcon: Icons.favorite, title: '消息'),
_TabItem(icon: Icons.person_outline, activeIcon: Icons.person, title: '我的'),
];
@override
void initState() {
super.initState();
_tabController = TabController(length: _tabs.length, vsync: this);
_tabController.addListener(() { if (!_tabController.indexIsChanging) setState(() => _currentIndex = _tabController.index); });
}
@override
void dispose() { _tabController.dispose(); super.dispose(); }
@override
Widget build(BuildContext context) {
return Scaffold(
body: TabBarView(controller: _tabController, physics: const NeverScrollableScrollPhysics(), children: _tabs.map((tab) => Center(child: Text(tab.title, style: const TextStyle(fontSize: 24)))).toList()),
bottomNavigationBar: Container(
decoration: BoxDecoration(color: Colors.white, boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.1), blurRadius: 10)]),
child: SafeArea(
child: SizedBox(
height: 60,
child: Row(
children: List.generate(_tabs.length, (index) {
final tab = _tabs[index];
final isSelected = index == _currentIndex;
return Expanded(
child: GestureDetector(
onTap: () => _tabController.animateTo(index),
child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [Icon(isSelected ? tab.activeIcon : tab.icon, color: isSelected ? Colors.cyan : Colors.grey, size: isSelected ? 28 : 24), const SizedBox(height: 4), Text(tab.title, style: TextStyle(color: isSelected ? Colors.cyan : Colors.grey, fontSize: 12, fontWeight: isSelected ? FontWeight.bold : FontWeight.normal))]),
),
);
}),
),
),
),
),
);
}
}
class _TabItem {
final IconData icon;
final IconData activeIcon;
final String title;
const _TabItem({required this.icon, required this.activeIcon, required this.title});
}
五、最佳实践与性能优化
🎨 5.1 性能优化建议
- 使用 const 构造函数:对于不变的组件使用 const 构造函数
- 避免过度重建:使用 AutomaticKeepAliveClientMixin 保持标签页状态
- 合理设置 physics:根据需求设置滑动行为
- 控制标签数量:避免过多标签导致性能问题
🔧 5.2 状态保持
dart
class _TabPageState extends State<TabPage> with AutomaticKeepAliveClientMixin {
@override
bool get wantKeepAlive => true;
@override
Widget build(BuildContext context) {
super.build(context);
return ListView(...);
}
}
📱 5.3 OpenHarmony 适配
在 OpenHarmony 平台上,需要注意:
- 处理手势冲突
- 适配不同屏幕尺寸
- 优化动画性能
六、总结
本文详细介绍了 Flutter for OpenHarmony 的 TabBar 高级标签系统,包括:
| 组件类型 | 核心技术 | 应用场景 |
|---|---|---|
| 固定标签 | TabBar + TabController | 主导航 |
| 滚动标签 | isScrollable: true | 分类导航 |
| 图标标签 | Tab(icon:, text:) | 底部导航 |
| 自定义指示器 | Decoration + BoxPainter | 品牌定制 |
| 动画标签 | AnimatedPositioned | 增强交互 |
| 分段标签 | 圆角背景指示器 | iOS 风格 |
| 底部标签栏 | TabBarView + bottomNavigationBar | 主导航 |
参考资料
💡 提示:标签导航是移动应用的核心导航模式,合理设计可以显著提升用户体验。建议根据具体场景选择合适的标签类型,并注意状态管理和性能优化。