跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI 生图工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
Dart大前端

Flutter TabBar 标签系统实战:从基础到高级定制

Flutter TabBar 组件的完整实践记录,涵盖固定标签、滚动标签、图标标签、自定义指示器、动画切换、分段标签以及底部导航栏的实现代码。同时整理了性能优化技巧和 OpenHarmony 平台上的手势冲突、动画帧率等适配经验。

极客零度发布于 2026/6/21更新于 2026/10/1566 浏览
Flutter TabBar 标签系统实战:从基础到高级定制

干移动开发的都知道,标签导航是绕不开的。Flutter 提供了 TabBar 这一套组件,用熟了能省很多事,但真要定制出点花样,还是得把它的脾气摸透。下面是我折腾一套标签系统时积累的代码和体会,从固定标签、滚动标签,到自定义指示器、动画切换,再到 OpenHarmony 上的一些坑,都记下来了。

TabBar 是怎么转起来的

Flutter 的 TabBar 其实是一套协作体系:

┌─────────────────────────────────────────────────────────────────┐
│ 应用层:TabBar, TabBarView, TabController                      │
├─────────────────────────────────────────────────────────────────┤
│ 指示器层:UnderlineTabIndicator, BoxDecoration 等              │
├─────────────────────────────────────────────────────────────────┤
│ 动画层:AnimationController, Tween, CurvedAnimation            │
├─────────────────────────────────────────────────────────────────┤
│ 状态管理层:TabController, ChangeNotifier                      │
└─────────────────────────────────────────────────────────────────┘

核心就三个东西:TabBar 管显示标签,TabBarView 管内容切换,TabController 在中间协调。大部分需求靠 TabController 都能搞定,比如监听切换、程序跳转。

final tabController = TabController(length: 3, vsync: this);
tabController.addListener(() {
  if (!tabController.indexIsChanging) {
    print('当前标签:${tabController.index}');
  }
});
tabController.animateTo(1);

设计标签时别太花,几个原则心里有数就行:标签意思清楚、点击区域够大、切换状态明显、数量别太多。

先来几个基础的

固定标签

最常见不过了,底部三四个图标那种。

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} 项'),
          ),
        );
      },
    );
  }
}

滚动标签

如果标签多,最上面那种可滑动的分类栏,把 isScrollable 打开就行。

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}'),
              ),
            ],
          ),
        );
      },
    );
  }
}

图标标签

文字加个图标更直观,但别让图标抢了文字的戏。

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)),
        ],
      ),
    );
  }
}

搞点不一样的:自定义指示器、动画和底部栏

基础的用熟了,就该折腾一下视觉效果了。

自定义指示器

默认那条横线太素,可以自己画一个圆角背景。通过继承 Decoration 和 BoxPainter 来画。

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);
  }
}

动画标签切换

让选中态滑过去,用 AnimatedPositioned 配合自绘的指示条,比默认的切换更流畅。

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 的内容'),
          ),
        );
      },
    );
  }
}

分段标签

类似 iOS 那种圆角背景的分段控制器,设置 indicator 为 BoxDecoration,再调一下颜色和阴影。

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)),
        ],
      ),
    );
  }
}

底部标签栏

主屏幕最常用到的底部导航,其实可以完全用 TabBarView 加自定义底部栏来实现,灵活度更高。

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});
}

整个入口页面,把例子串起来

把上面的 Demo 全放进一个列表页,方便跳转查看。代码比较直白,就是一堆卡片。

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]),
            ],
          ),
        ),
      ),
    );
  }
}
// ... (此处省略重复的 Demo 类定义,参考上文各章节代码)

一些实践建议和 OpenHarmony 上的坑

性能方面,能加 const 的地方都加上;如果标签页需要保持状态,别忘 AutomaticKeepAliveClientMixin。

class _TabPageState extends State<TabPage> with AutomaticKeepAliveClientMixin {
  @override
  bool get wantKeepAlive => true;

  @override
  Widget build(BuildContext context) {
    super.build(context);
    return ListView(...);
  }
}

在 OpenHarmony 上跑的时候,遇到过手势冲突和动画帧率问题。尤其是滑动切换时和系统侧边栏手势容易打架,可以尝试调一下 physics。屏幕适配倒还好,Flutter 本身做得不错,但极少数设备上底部安全区需要额外处理一下。

总体来说,TabBar 这套东西覆盖面很广,大部分需求都能通过组合解决。别一上来就自己造轮子,先看看源码里能不能改。

参考资料

  • Flutter TabBar 官方文档
  • Flutter TabController 指南
  • Material Design Tabs
  • OpenHarmony 开发者文档

目录

  1. TabBar 是怎么转起来的
  2. 先来几个基础的
  3. 固定标签
  4. 滚动标签
  5. 图标标签
  6. 搞点不一样的:自定义指示器、动画和底部栏
  7. 自定义指示器
  8. 动画标签切换
  9. 分段标签
  10. 底部标签栏
  11. 整个入口页面,把例子串起来
  12. 一些实践建议和 OpenHarmony 上的坑
  13. 参考资料

更多推荐文章

查看全部
  • PaddleOCR-VL 0.9B 本地一键部署教程
  • Rust WebAssembly 开发实战:构建高性能前端应用
  • 9 篇必读的大模型前沿论文
  • 基于 YOLO12 的无人机航拍目标检测系统实战
  • 无人机“黑飞”正式入法:2026 年 1 月 1 日起违规飞行将面临拘留
  • Windows 系统安装并编译 llama.cpp 步骤详解
  • 自然语言处理在法律领域的应用与实战
  • C++ STL 排序及相关操作详解
  • PyCharm 在 macOS 系统下的安装与基础配置指南
  • LLM 微调实战指南:Pythia 模型 Fine Tuning 全流程解析
  • 从 SEO 到 GEO:315 晚会曝光的
  • 基于DeepSeek和Cursor构建智能代码审查工具实战
  • 分布式文件系统 HDFS 存储原理
  • 高铁轨道探伤:GLM-4.6V-Flash-WEB 识别钢轨磨损痕迹
  • Python 语言现状、优势及学习价值分析
  • 自进化医疗智能体:动态记忆与持续运行的 Python 架构设计
  • 裸金属到实时系统:C++ 内核稳定运行的关键控制点
  • LLaMA Factory 微调时报 disable multiprocessing 错误解决方法
  • C++ 短信验证码 API 示例代码:libcurl 调用流程实现
  • 昇腾 NPU 部署 Llama 2 模型的性能测试与优化实践

相关免费在线工具

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online

  • JSON 压缩

    通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online

  • JSON美化和格式化

    将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online