在这里插入图片描述

前言

表情是社交应用中丰富用户表达的重要元素。一个优秀的表情选择器需要支持多个表情分类、流畅的滚动体验、最近使用记录以及搜索功能。本文将详细讲解如何在Flutter和OpenHarmony平台上构建功能完善的表情选择器组件。

Flutter表情选择器实现

首先定义表情数据结构。

class EmojiCategory {
  final String name;
  final IconData icon;
  final List<String> emojis;
  
  EmojiCategory({
    required this.name,
    required this.icon,
    required this.emojis,
  });
}

final List<EmojiCategory> emojiCategories = [
  EmojiCategory(
    name: '笑脸',
    icon: Icons.emoji_emotions,
    emojis: ['😀', '😃', '😄', '😁', '😆', '😅', '🤣', '😂'],
  ),
  EmojiCategory(
    name: '手势',
    icon: Icons.waving_hand,
    emojis: ['👍', '👎', '👌', '✌️', '🤞', '🤟', '🤘', '👋'],
  ),
];

EmojiCategory定义表情分类,包含名称、图标和表情列表。预定义的分类数据可以根据需求扩展。

class EmojiPicker extends StatefulWidget {
  final Function(String) onEmojiSelected;
  
  const EmojiPicker({
    Key? key,
    required this.onEmojiSelected,
  }) : super(key: key);
  
  
  State<EmojiPicker> createState() => _EmojiPickerState();
}

class _EmojiPickerState extends State<EmojiPicker> {
  int _selectedCategoryIndex = 0;
  
  
  Widget build(BuildContext context) {
    return Container(
      height: 280,
      color: Colors.white,
      child: Column(
        children: [
          _buildCategoryBar(),
          Expanded(child: _buildEmojiGrid()),
        ],
      ),
    );
  }

EmojiPicker组件管理分类选择状态。固定高度280像素适合键盘弹出时的空间。Column垂直排列分类栏和表情网格。

  Widget _buildCategoryBar() {
    return Container(
      height: 44,
      decoration: BoxDecoration(
        border: Border(
          bottom: BorderSide(color: Colors.grey[200]!),
        ),
      ),
      child: Row(
        children: emojiCategories.asMap().entries.map((entry) {
          final index = entry.key;
          final category = entry.value;
          final isSelected = index == _selectedCategoryIndex;
          
          return Expanded(
            child: GestureDetector(
              onTap: () => setState(() => _selectedCategoryIndex = index),
              child: Container(
                decoration: BoxDecoration(
                  border: Border(
                    bottom: BorderSide(
                      color: isSelected ? Colors.blue : Colors.transparent,
                      width: 2,
                    ),
                  ),
                ),
                child: Icon(
                  category.icon,
                  color: isSelected ? Colors.blue : Colors.grey,
                ),
              ),
            ),
          );
        }).toList(),
      ),
    );
  }

分类栏使用Row均匀分布各分类图标。选中的分类显示蓝色下划线和蓝色图标,未选中显示灰色。asMap().entries获取索引和值的映射,方便判断选中状态。

  Widget _buildEmojiGrid() {
    final emojis = emojiCategories[_selectedCategoryIndex].emojis;
    
    return GridView.builder(
      padding: EdgeInsets.all(8),
      gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
        crossAxisCount: 8,
        mainAxisSpacing: 8,
        crossAxisSpacing: 8,
      ),
      itemCount: emojis.length,
      itemBuilder: (context, index) {
        return GestureDetector(
          onTap: () => widget.onEmojiSelected(emojis[index]),
          child: Center(
            child: Text(
              emojis[index],
              style: TextStyle(fontSize: 24),
            ),
          ),
        );
      },
    );
  }
}

表情网格使用GridView实现,每行8个表情。点击表情触发onEmojiSelected回调,将选中的表情传递给父组件。字号24像素确保表情清晰可见。

OpenHarmony ArkTS实现

鸿蒙系统上的表情选择器实现。

@Component
struct EmojiPicker {
  @State selectedCategoryIndex: number = 0
  onEmojiSelected: (emoji: string) => void = () => {}
  
  private categories: EmojiCategory[] = [
    { name: '笑脸', icon: $r('app.media.ic_emoji'), emojis: ['😀', '😃', '😄', '😁'] },
    { name: '手势', icon: $r('app.media.ic_hand'), emojis: ['👍', '👎', '👌', '✌️'] },
  ]
  
  build() {
    Column() {
      this.CategoryBar()
      this.EmojiGrid()
    }
    .height(280)
    .backgroundColor(Color.White)
  }

@State管理选中分类索引。categories定义表情分类数据。Column垂直排列分类栏和表情网格。

  @Builder
  CategoryBar() {
    Row() {
      ForEach(this.categories, (category: EmojiCategory, index: number) => {
        Column() {
          Image(category.icon)
            .width(24)
            .height(24)
            .fillColor(index === this.selectedCategoryIndex ? '#007AFF' : Color.Gray)
        }
        .layoutWeight(1)
        .height(44)
        .justifyContent(FlexAlign.Center)
        .border({
          width: { bottom: index === this.selectedCategoryIndex ? 2 : 0 },
          color: '#007AFF'
        })
        .onClick(() => {
          this.selectedCategoryIndex = index
        })
      })
    }
    .width('100%')
    .border({ width: { bottom: 1 }, color: '#E5E5EA' })
  }

CategoryBar使用ForEach遍历分类列表。选中分类显示蓝色图标和下划线。layoutWeight(1)让各分类等宽分布。

  @Builder
  EmojiGrid() {
    Grid() {
      ForEach(this.categories[this.selectedCategoryIndex].emojis, (emoji: string) => {
        GridItem() {
          Text(emoji)
            .fontSize(24)
        }
        .onClick(() => this.onEmojiSelected(emoji))
      })
    }
    .columnsTemplate('1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr')
    .rowsGap(8)
    .columnsGap(8)
    .padding(8)
    .layoutWeight(1)
  }
}

EmojiGrid使用Grid组件实现8列布局。ForEach遍历当前分类的表情列表。点击表情触发回调。layoutWeight(1)让网格占据剩余空间。

最近使用功能扩展

在Flutter中实现最近使用表情:

class RecentEmojis {
  static const String _key = 'recent_emojis';
  static const int _maxCount = 16;
  
  static Future<List<String>> getRecent() async {
    final prefs = await SharedPreferences.getInstance();
    return prefs.getStringList(_key) ?? [];
  }
  
  static Future<void> addRecent(String emoji) async {
    final prefs = await SharedPreferences.getInstance();
    final recent = await getRecent();
    recent.remove(emoji);
    recent.insert(0, emoji);
    if (recent.length > _maxCount) {
      recent.removeLast();
    }
    await prefs.setStringList(_key, recent);
  }
}

RecentEmojis类使用SharedPreferences存储最近使用的表情。addRecent方法将新表情添加到列表开头,并限制最大数量为16个。这种设计让用户能够快速访问常用表情。

总结

本文详细介绍了表情选择器组件在Flutter和OpenHarmony两个平台上的实现。表情是社交应用丰富用户表达的重要元素,需要支持分类浏览和快速选择。两个平台的实现都采用了分类导航和网格布局。在实际项目中,还可以扩展表情搜索、自定义表情包、GIF表情等功能。

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

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐