在这里插入图片描述

案例概述

本案例展示如何在列表中添加徽章,用于显示未读数量、状态指示、优先级等重要信息。徽章是一种轻量级的视觉指示器,用于吸引用户注意力并传达重要信息。在消息应用、电商应用、任务管理系统等中,徽章常用于显示未读消息数、待处理订单数、优先级标记等。

徽章系统需要处理动态更新、条件显示、响应式设计、性能优化等问题。此外,还应考虑动画效果、无障碍支持、主题适配等。在 PC 端应用中,徽章需要在不同屏幕尺寸下保持清晰可见,并提供良好的交互体验。

核心概念

1. Badge 组件与徽章类型

徽章有多种类型和用途:

  • 计数徽章:显示未读数量或待处理项数,如消息应用中的未读消息数
  • 状态徽章:表示项目的状态,如在线/离线、已读/未读等
  • 优先级徽章:表示项目的优先级,如高/中/低优先级
  • 标签徽章:显示分类或标签信息
  • 动态徽章:实时更新的徽章,如计时器或进度指示

2. 徽章位置与布局

徽章的位置影响用户体验:

  • 图标右上角:常见于应用图标或列表项图标
  • 列表项右侧:显示在列表项的尾部
  • 自定义位置:通过 Stack 和 Positioned 实现任意位置
  • 浮动徽章:在内容上方浮动显示

3. 徽章内容与样式

徽章可以包含多种内容和样式:

  • 数字内容:显示具体数量
  • 文本内容:显示状态或标签
  • 图标内容:显示状态图标
  • 自定义颜色:根据优先级或状态使用不同颜色
  • 动画效果:添加缩放、旋转等动画

代码详解

1. 基础徽章实现

创建一个基础的徽章列表需要使用 Badge 组件和 ListTile 的组合:

class BadgeListWidget extends StatefulWidget {
  
  State<BadgeListWidget> createState() => _BadgeListWidgetState();
}

class _BadgeListWidgetState extends State<BadgeListWidget> {
  List<BadgeItem> items = [
    BadgeItem(id: '1', title: '消息', icon: Icons.message, count: 5),
    BadgeItem(id: '2', title: '通知', icon: Icons.notifications, count: 3),
    BadgeItem(id: '3', title: '订单', icon: Icons.shopping_cart, count: 0),
    BadgeItem(id: '4', title: '评论', icon: Icons.comment, count: 12),
  ];

  
  Widget build(BuildContext context) {
    return ListView.builder(
      itemCount: items.length,
      itemBuilder: (context, index) {
        final item = items[index];
        return ListTile(
          leading: Badge(
            label: Text(item.count.toString()),
            isLabelVisible: item.count > 0,
            backgroundColor: _getBadgeColor(item.count),
            child: Icon(item.icon, size: 28),
          ),
          title: Text(item.title),
          subtitle: Text('${item.count} 条未读'),
          trailing: IconButton(
            icon: Icon(Icons.close),
            onPressed: () => _clearBadge(index),
          ),
        );
      },
    );
  }

  Color _getBadgeColor(int count) {
    if (count > 10) return Colors.red;
    if (count > 5) return Colors.orange;
    return Colors.blue;
  }

  void _clearBadge(int index) {
    setState(() {
      items[index].count = 0;
    });
  }
}

class BadgeItem {
  final String id;
  final String title;
  final IconData icon;
  int count;

  BadgeItem({
    required this.id,
    required this.title,
    required this.icon,
    required this.count,
  });
}

2. 自定义徽章样式

创建可自定义样式的徽章组件,支持不同的颜色、大小和形状:

class CustomBadgeWidget extends StatelessWidget {
  final String label;
  final Color backgroundColor;
  final Color textColor;
  final double size;
  final Widget child;
  final BadgeShape shape;

  const CustomBadgeWidget({
    required this.label,
    this.backgroundColor = Colors.red,
    this.textColor = Colors.white,
    this.size = 24,
    required this.child,
    this.shape = BadgeShape.circle,
  });

  
  Widget build(BuildContext context) {
    return Stack(
      alignment: Alignment.topRight,
      children: [
        child,
        if (label.isNotEmpty)
          Container(
            width: size,
            height: size,
            decoration: BoxDecoration(
              color: backgroundColor,
              shape: shape == BadgeShape.circle ? BoxShape.circle : BoxShape.rectangle,
              borderRadius: shape == BadgeShape.roundedSquare
                  ? BorderRadius.circular(size / 3)
                  : null,
              border: Border.all(color: Colors.white, width: 2),
            ),
            child: Center(
              child: Text(
                label,
                style: TextStyle(
                  color: textColor,
                  fontSize: size * 0.4,
                  fontWeight: FontWeight.bold,
                ),
                maxLines: 1,
                overflow: TextOverflow.ellipsis,
              ),
            ),
          ),
      ],
    );
  }
}

enum BadgeShape { circle, square, roundedSquare }

3. 动画徽章

为徽章添加动画效果,如缩放、旋转、闪烁等:

class AnimatedBadgeWidget extends StatefulWidget {
  final String label;
  final Widget child;
  final BadgeAnimationType animationType;

  const AnimatedBadgeWidget({
    required this.label,
    required this.child,
    this.animationType = BadgeAnimationType.scale,
  });

  
  State<AnimatedBadgeWidget> createState() => _AnimatedBadgeWidgetState();
}

class _AnimatedBadgeWidgetState extends State<AnimatedBadgeWidget>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _animation;

  
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: Duration(milliseconds: 1000),
      vsync: this,
    )..repeat(reverse: true);

    _animation = Tween<double>(begin: 1.0, end: 1.3).animate(
      CurvedAnimation(parent: _controller, curve: Curves.easeInOut),
    );
  }

  
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  
  Widget build(BuildContext context) {
    return Stack(
      alignment: Alignment.topRight,
      children: [
        widget.child,
        if (widget.label.isNotEmpty)
          AnimatedBuilder(
            animation: _animation,
            builder: (context, child) {
              if (widget.animationType == BadgeAnimationType.scale) {
                return Transform.scale(scale: _animation.value, child: child);
              } else if (widget.animationType == BadgeAnimationType.rotate) {
                return Transform.rotate(angle: _animation.value * 0.5, child: child);
              } else {
                return Opacity(opacity: _animation.value, child: child);
              }
            },
            child: Container(
              width: 24,
              height: 24,
              decoration: BoxDecoration(
                color: Colors.red,
                shape: BoxShape.circle,
                border: Border.all(color: Colors.white, width: 2),
              ),
              child: Center(
                child: Text(
                  widget.label,
                  style: TextStyle(
                    color: Colors.white,
                    fontSize: 10,
                    fontWeight: FontWeight.bold,
                  ),
                ),
              ),
            ),
          ),
      ],
    );
  }
}

enum BadgeAnimationType { scale, rotate, fade }

4. 响应式徽章列表

根据屏幕宽度调整徽章的大小和布局:

class ResponsiveBadgeListWidget extends StatefulWidget {
  
  State<ResponsiveBadgeListWidget> createState() => _ResponsiveBadgeListWidgetState();
}

class _ResponsiveBadgeListWidgetState extends State<ResponsiveBadgeListWidget> {
  List<BadgeItem> items = [
    BadgeItem(id: '1', title: '消息', icon: Icons.message, count: 5),
    BadgeItem(id: '2', title: '通知', icon: Icons.notifications, count: 3),
    BadgeItem(id: '3', title: '订单', icon: Icons.shopping_cart, count: 0),
  ];

  
  Widget build(BuildContext context) {
    final screenWidth = MediaQuery.of(context).size.width;
    final isMobile = screenWidth < 600;
    final isTablet = screenWidth < 1200;

    return LayoutBuilder(
      builder: (context, constraints) {
        if (isMobile) {
          return _buildMobileLayout();
        } else if (isTablet) {
          return _buildTabletLayout();
        } else {
          return _buildDesktopLayout();
        }
      },
    );
  }

  Widget _buildMobileLayout() {
    return ListView.builder(
      itemCount: items.length,
      itemBuilder: (context, index) {
        final item = items[index];
        return Padding(
          padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
          child: ListTile(
            leading: Badge(
              label: Text(item.count.toString()),
              isLabelVisible: item.count > 0,
              child: Icon(item.icon),
            ),
            title: Text(item.title),
            subtitle: Text('${item.count} 条'),
          ),
        );
      },
    );
  }

  Widget _buildTabletLayout() {
    return ListView.builder(
      itemCount: items.length,
      itemBuilder: (context, index) {
        final item = items[index];
        return Padding(
          padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
          child: Card(
            child: ListTile(
              leading: Badge(
                label: Text(item.count.toString()),
                isLabelVisible: item.count > 0,
                child: Icon(item.icon, size: 32),
              ),
              title: Text(item.title, style: TextStyle(fontSize: 18)),
              subtitle: Text('${item.count} 条未读', style: TextStyle(fontSize: 14)),
              trailing: IconButton(
                icon: Icon(Icons.clear),
                onPressed: () => _clearBadge(index),
              ),
            ),
          ),
        );
      },
    );
  }

  Widget _buildDesktopLayout() {
    return GridView.builder(
      padding: EdgeInsets.all(24),
      gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
        crossAxisCount: 4,
        childAspectRatio: 1.2,
        crossAxisSpacing: 16,
        mainAxisSpacing: 16,
      ),
      itemCount: items.length,
      itemBuilder: (context, index) {
        final item = items[index];
        return Card(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Badge(
                label: Text(item.count.toString()),
                isLabelVisible: item.count > 0,
                child: Icon(item.icon, size: 40),
              ),
              SizedBox(height: 12),
              Text(item.title, style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
              SizedBox(height: 8),
              Text('${item.count} 条', style: TextStyle(color: Colors.grey)),
            ],
          ),
        );
      },
    );
  }

  void _clearBadge(int index) {
    setState(() {
      items[index].count = 0;
    });
  }
}

高级话题:徽章的企业级应用

1. 动态/响应式设计与多屏幕适配

class BadgeSizeManager {
  static const double MOBILE_BADGE_SIZE = 20;
  static const double TABLET_BADGE_SIZE = 24;
  static const double DESKTOP_BADGE_SIZE = 28;

  static double getBadgeSize(BuildContext context) {
    final width = MediaQuery.of(context).size.width;
    if (width < 600) return MOBILE_BADGE_SIZE;
    if (width < 1200) return TABLET_BADGE_SIZE;
    return DESKTOP_BADGE_SIZE;
  }

  static double getIconSize(BuildContext context) {
    final width = MediaQuery.of(context).size.width;
    if (width < 600) return 24;
    if (width < 1200) return 32;
    return 40;
  }

  static EdgeInsets getListPadding(BuildContext context) {
    final width = MediaQuery.of(context).size.width;
    if (width < 600) return EdgeInsets.symmetric(horizontal: 8);
    if (width < 1200) return EdgeInsets.symmetric(horizontal: 16);
    return EdgeInsets.symmetric(horizontal: 24);
  }
}

2. 动画与过渡效果

class AnimatedBadgeCountWidget extends StatefulWidget {
  final int count;
  final Duration duration;

  const AnimatedBadgeCountWidget({
    required this.count,
    this.duration = const Duration(milliseconds: 500),
  });

  
  State<AnimatedBadgeCountWidget> createState() => _AnimatedBadgeCountWidgetState();
}

class _AnimatedBadgeCountWidgetState extends State<AnimatedBadgeCountWidget>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _scaleAnimation;
  late Animation<double> _opacityAnimation;

  
  void initState() {
    super.initState();
    _controller = AnimationController(duration: widget.duration, vsync: this);
    _setupAnimations();
  }

  void _setupAnimations() {
    _scaleAnimation = Tween<double>(begin: 0.5, end: 1.0).animate(
      CurvedAnimation(parent: _controller, curve: Curves.elasticOut),
    );
    _opacityAnimation = Tween<double>(begin: 0, end: 1.0).animate(
      CurvedAnimation(parent: _controller, curve: Curves.easeIn),
    );
  }

  
  void didUpdateWidget(AnimatedBadgeCountWidget oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (oldWidget.count != widget.count) {
      _controller.forward(from: 0);
    }
  }

  
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  
  Widget build(BuildContext context) {
    return ScaleTransition(
      scale: _scaleAnimation,
      child: FadeTransition(
        opacity: _opacityAnimation,
        child: Badge(
          label: Text(widget.count.toString()),
          isLabelVisible: widget.count > 0,
        ),
      ),
    );
  }
}

3. 搜索/过滤/排序功能

class BadgeFilterManager {
  List<BadgeItem> _allItems;
  List<BadgeItem> _filteredItems;

  String _searchQuery = '';
  String _sortBy = 'count'; // count, title, id
  bool _sortAscending = false;
  bool _showOnlyWithBadges = false;

  BadgeFilterManager(this._allItems) : _filteredItems = _allItems;

  void setSearchQuery(String query) {
    _searchQuery = query;
    _applyFilters();
  }

  void setSortBy(String sortBy, bool ascending) {
    _sortBy = sortBy;
    _sortAscending = ascending;
    _applyFilters();
  }

  void setShowOnlyWithBadges(bool show) {
    _showOnlyWithBadges = show;
    _applyFilters();
  }

  void _applyFilters() {
    _filteredItems = _allItems.where((item) {
      // 搜索过滤
      final matchesSearch = _searchQuery.isEmpty ||
          item.title.toLowerCase().contains(_searchQuery.toLowerCase());

      // 徽章过滤
      final matchesBadgeFilter = !_showOnlyWithBadges || item.count > 0;

      return matchesSearch && matchesBadgeFilter;
    }).toList();

    // 排序
    _filteredItems.sort((a, b) {
      int comparison = 0;
      switch (_sortBy) {
        case 'count':
          comparison = a.count.compareTo(b.count);
          break;
        case 'title':
          comparison = a.title.compareTo(b.title);
          break;
        case 'id':
          comparison = a.id.compareTo(b.id);
          break;
      }
      return _sortAscending ? comparison : -comparison;
    });
  }

  List<BadgeItem> get filteredItems => _filteredItems;
}

4. 选择与批量操作

class SelectableBadgeListWidget extends StatefulWidget {
  final List<BadgeItem> items;

  const SelectableBadgeListWidget({required this.items});

  
  State<SelectableBadgeListWidget> createState() => _SelectableBadgeListWidgetState();
}

class _SelectableBadgeListWidgetState extends State<SelectableBadgeListWidget> {
  final Set<String> _selectedIds = {};

  void _toggleSelection(String id) {
    setState(() {
      if (_selectedIds.contains(id)) {
        _selectedIds.remove(id);
      } else {
        _selectedIds.add(id);
      }
    });
  }

  void _clearAllBadges() {
    // 批量清除选中项的徽章
    for (var item in widget.items) {
      if (_selectedIds.contains(item.id)) {
        item.count = 0;
      }
    }
    setState(() {
      _selectedIds.clear();
    });
  }

  
  Widget build(BuildContext context) {
    return Column(
      children: [
        if (_selectedIds.isNotEmpty)
          Container(
            padding: EdgeInsets.all(16),
            color: Colors.blue.shade50,
            child: Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: [
                Text('已选择 ${_selectedIds.length} 项'),
                ElevatedButton(
                  onPressed: _clearAllBadges,
                  child: Text('清除徽章'),
                ),
              ],
            ),
          ),
        Expanded(
          child: ListView.builder(
            itemCount: widget.items.length,
            itemBuilder: (context, index) {
              final item = widget.items[index];
              final isSelected = _selectedIds.contains(item.id);

              return CheckboxListTile(
                value: isSelected,
                onChanged: (_) => _toggleSelection(item.id),
                leading: Badge(
                  label: Text(item.count.toString()),
                  isLabelVisible: item.count > 0,
                  child: Icon(item.icon),
                ),
                title: Text(item.title),
                subtitle: Text('${item.count} 条'),
              );
            },
          ),
        ),
      ],
    );
  }
}

5. 加载与缓存策略

class BadgeDataManager {
  List<BadgeItem> _cachedItems = [];
  bool _isLoading = false;

  Future<List<BadgeItem>> loadBadgeItems() async {
    if (_cachedItems.isNotEmpty) {
      return _cachedItems;
    }

    _isLoading = true;
    try {
      // 模拟网络请求
      await Future.delayed(Duration(milliseconds: 500));

      _cachedItems = [
        BadgeItem(id: '1', title: '消息', icon: Icons.message, count: 5),
        BadgeItem(id: '2', title: '通知', icon: Icons.notifications, count: 3),
        BadgeItem(id: '3', title: '订单', icon: Icons.shopping_cart, count: 0),
      ];

      return _cachedItems;
    } finally {
      _isLoading = false;
    }
  }

  void updateBadgeCount(String id, int count) {
    final index = _cachedItems.indexWhere((item) => item.id == id);
    if (index >= 0) {
      _cachedItems[index].count = count;
    }
  }

  void clearCache() {
    _cachedItems.clear();
  }

  bool get isLoading => _isLoading;
}

6. 键盘导航与快捷键

class KeyboardNavigableBadgeList extends StatefulWidget {
  final List<BadgeItem> items;

  const KeyboardNavigableBadgeList({required this.items});

  
  State<KeyboardNavigableBadgeList> createState() => _KeyboardNavigableBadgeListState();
}

class _KeyboardNavigableBadgeListState extends State<KeyboardNavigableBadgeList> {
  int _focusedIndex = 0;
  late FocusNode _focusNode;

  
  void initState() {
    super.initState();
    _focusNode = FocusNode();
  }

  
  void dispose() {
    _focusNode.dispose();
    super.dispose();
  }

  void _handleKeyEvent(RawKeyEvent event) {
    if (event.isKeyPressed(LogicalKeyboardKey.arrowDown)) {
      setState(() {
        _focusedIndex = (_focusedIndex + 1) % widget.items.length;
      });
    } else if (event.isKeyPressed(LogicalKeyboardKey.arrowUp)) {
      setState(() {
        _focusedIndex = (_focusedIndex - 1 + widget.items.length) % widget.items.length;
      });
    } else if (event.isKeyPressed(LogicalKeyboardKey.enter)) {
      // 清除当前项的徽章
      setState(() {
        widget.items[_focusedIndex].count = 0;
      });
    }
  }

  
  Widget build(BuildContext context) {
    return RawKeyboardListener(
      focusNode: _focusNode,
      onKey: _handleKeyEvent,
      child: ListView.builder(
        itemCount: widget.items.length,
        itemBuilder: (context, index) {
          final item = widget.items[index];
          final isFocused = index == _focusedIndex;

          return Container(
            decoration: BoxDecoration(
              border: isFocused ? Border.all(color: Colors.blue, width: 2) : null,
            ),
            child: ListTile(
              leading: Badge(
                label: Text(item.count.toString()),
                isLabelVisible: item.count > 0,
                child: Icon(item.icon),
              ),
              title: Text(item.title),
              subtitle: Text('${item.count} 条'),
            ),
          );
        },
      ),
    );
  }
}

7. 无障碍支持与屏幕阅读器

class AccessibleBadgeListWidget extends StatelessWidget {
  final List<BadgeItem> items;

  const AccessibleBadgeListWidget({required this.items});

  
  Widget build(BuildContext context) {
    return ListView.builder(
      itemCount: items.length,
      itemBuilder: (context, index) {
        final item = items[index];

        return Semantics(
          label: '${item.title},有 ${item.count} 条未读',
          button: true,
          enabled: true,
          onTap: () => print('选择: ${item.title}'),
          child: ListTile(
            leading: Semantics(
              label: '${item.title}徽章',
              child: Badge(
                label: Text(item.count.toString()),
                isLabelVisible: item.count > 0,
                child: Icon(item.icon),
              ),
            ),
            title: Semantics(
              label: '标题',
              child: Text(item.title),
            ),
            subtitle: Semantics(
              label: '未读数量',
              child: Text('${item.count} 条未读'),
            ),
          ),
        );
      },
    );
  }
}

8. 样式自定义与主题适配

class BadgeTheme {
  final Color badgeColor;
  final Color textColor;
  final double badgeSize;
  final double fontSize;
  final EdgeInsets padding;

  const BadgeTheme({
    this.badgeColor = Colors.red,
    this.textColor = Colors.white,
    this.badgeSize = 24,
    this.fontSize = 12,
    this.padding = const EdgeInsets.all(4),
  });

  static BadgeTheme light() {
    return BadgeTheme(
      badgeColor: Colors.red,
      textColor: Colors.white,
      badgeSize: 24,
      fontSize: 12,
    );
  }

  static BadgeTheme dark() {
    return BadgeTheme(
      badgeColor: Colors.red.shade700,
      textColor: Colors.white,
      badgeSize: 24,
      fontSize: 12,
    );
  }
}

class ThemedBadgeWidget extends StatelessWidget {
  final String label;
  final Widget child;
  final BadgeTheme theme;

  const ThemedBadgeWidget({
    required this.label,
    required this.child,
    required this.theme,
  });

  
  Widget build(BuildContext context) {
    return Stack(
      alignment: Alignment.topRight,
      children: [
        child,
        if (label.isNotEmpty)
          Container(
            width: theme.badgeSize,
            height: theme.badgeSize,
            decoration: BoxDecoration(
              color: theme.badgeColor,
              shape: BoxShape.circle,
            ),
            child: Center(
              child: Text(
                label,
                style: TextStyle(
                  color: theme.textColor,
                  fontSize: theme.fontSize,
                  fontWeight: FontWeight.bold,
                ),
              ),
            ),
          ),
      ],
    );
  }
}

9. 数据持久化与导出

class BadgeDataExporter {
  static String exportToCSV(List<BadgeItem> items) {
    final buffer = StringBuffer();
    buffer.writeln('ID,标题,未读数');

    for (var item in items) {
      buffer.writeln('${item.id},${item.title},${item.count}');
    }

    return buffer.toString();
  }

  static String exportToJSON(List<BadgeItem> items) {
    final jsonList = items.map((item) => {
      'id': item.id,
      'title': item.title,
      'count': item.count,
    }).toList();

    return jsonEncode(jsonList);
  }

  static Future<void> saveToFile(String filename, String content) async {
    final directory = await getApplicationDocumentsDirectory();
    final file = File('${directory.path}/$filename');
    await file.writeAsString(content);
  }
}

10. 单元测试与集成测试

void main() {
  group('Badge Tests', () {
    test('徽章计数更新', () {
      final item = BadgeItem(
        id: '1',
        title: '消息',
        icon: Icons.message,
        count: 5,
      );

      expect(item.count, 5);
      item.count = 10;
      expect(item.count, 10);
    });

    test('徽章过滤', () {
      final items = [
        BadgeItem(id: '1', title: '消息', icon: Icons.message, count: 5),
        BadgeItem(id: '2', title: '通知', icon: Icons.notifications, count: 0),
        BadgeItem(id: '3', title: '订单', icon: Icons.shopping_cart, count: 3),
      ];

      final filtered = items.where((item) => item.count > 0).toList();
      expect(filtered.length, 2);
    });

    test('徽章排序', () {
      final items = [
        BadgeItem(id: '1', title: '消息', icon: Icons.message, count: 5),
        BadgeItem(id: '2', title: '通知', icon: Icons.notifications, count: 10),
        BadgeItem(id: '3', title: '订单', icon: Icons.shopping_cart, count: 3),
      ];

      items.sort((a, b) => b.count.compareTo(a.count));
      expect(items[0].count, 10);
      expect(items[1].count, 5);
      expect(items[2].count, 3);
    });
  });

  testWidgets('Badge List Widget 集成测试', (WidgetTester tester) async {
    final items = [
      BadgeItem(id: '1', title: '消息', icon: Icons.message, count: 5),
    ];

    await tester.pumpWidget(
      MaterialApp(
        home: Scaffold(
          body: BadgeListWidget(),
        ),
      ),
    );

    expect(find.text('消息'), findsOneWidget);
    expect(find.text('5 条未读'), findsOneWidget);
  });
}

OpenHarmony PC 端适配要点

  1. 屏幕宽度检测:根据不同屏幕宽度调整徽章大小和布局
  2. 响应式布局:在 PC 端使用网格布局充分利用屏幕空间
  3. 鼠标交互:使用 MouseRegion 提供悬停效果
  4. 键盘导航:支持方向键和 Enter 键操作
  5. 无障碍支持:为屏幕阅读器提供完整的语义标签

实际应用场景

  1. 消息应用:显示未读消息数
  2. 电商应用:显示购物车商品数、待处理订单数
  3. 任务管理:显示待完成任务数、优先级标记
  4. 社交应用:显示粉丝数、评论数、点赞数
  5. 系统通知:显示系统通知数、警告数

扩展建议

  1. 支持徽章动画和过渡
  2. 实现徽章的实时更新
  3. 添加徽章的分组和分类
  4. 支持徽章的自定义颜色和样式
  5. 实现徽章的数据持久化

总结

徽章是展示重要信息的有效方式。通过合理的设计和实现,可以创建出功能完整、用户体验良好的徽章系统。在 PC 端应用中,充分利用屏幕空间、提供键盘导航和无障碍支持是关键。

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐