在React Native中开发一个筛选标签组件(通常称为Tag组件或Chip组件),可以帮助用户根据不同的条件来筛选信息。这类组件通常用于电商应用、新闻聚合应用、社交媒体等场景,以提供用户友好的交互方式。下面是如何实现这样一个筛选标签组件的步骤和示例代码。

  1. 准备环境

确保你的开发环境已经安装了React Native。如果还没有安装,可以通过以下命令来安装:

npx react-native init MyProject
cd MyProject
  1. 创建筛选标签组件

我们可以创建一个简单的筛选标签组件,用户可以通过点击不同的标签来筛选内容。

步骤1: 创建组件文件

在项目中创建一个新的React组件文件,例如FilterTag.js

// FilterTag.js
import React from 'react';
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';

const FilterTag = ({ tag, onPress, selected }) => {
  return (
    <TouchableOpacity onPress={onPress} style={[styles.tag, selected && styles.selectedTag]}>
      <Text style={styles.tagText}>{tag}</Text>
    </TouchableOpacity>
  );
};

const styles = StyleSheet.create({
  tag: {
    borderWidth: 1,
    borderColor: 'ccc',
    padding: 8,
    margin: 4,
    borderRadius: 15,
  },
  selectedTag: {
    borderColor: '007BFF',
    backgroundColor: 'E8F0FE',
  },
  tagText: {
    color: '333',
  },
});

export default FilterTag;

步骤2: 使用组件

在需要使用筛选标签的页面中,导入并使用FilterTag组件。

// HomeScreen.js 或其他页面文件
import React, { useState } from 'react';
import { View, ScrollView } from 'react-native';
import FilterTag from './FilterTag'; // 确保路径正确

const HomeScreen = () => {
  const [selectedTag, setSelectedTag] = useState(null); // 存储当前选中的标签
  const tags = ['全部', '科技', '娱乐', '体育', '财经']; // 示例标签列表

  const handleTagPress = (tag) => {
    setSelectedTag(tag === selectedTag ? null : tag); // 切换选中状态或取消选中状态
  };

  return (
    <ScrollView>
      <View style={{ flexDirection: 'row', flexWrap: 'wrap', padding: 16 }}>
        {tags.map((tag) => (
          <FilterTag 
            key={tag} 
            tag={tag} 
            onPress={() => handleTagPress(tag)} 
            selected={selectedTag === tag} 
          />
        ))}
      </View>
      {/* 其他UI元素 */}
    </ScrollView>
  );
};

export default HomeScreen;
  1. 测试和调整样式

运行你的React Native应用,查看筛选标签组件是否按预期工作。根据需要调整样式和功能,例如增加更多的交互反馈(如动画)、动态加载标签等。你可以通过修改FilterTag组件的样式或增加额外的逻辑来实现这些功能。例如,你可以根据selected状态改变背景色、边框色或文本颜色等。

  1. 扩展功能(可选)
  • 动态加载标签:从服务器获取标签列表,而不是硬编码在组件中。
  • 搜索功能:添加输入框,允许用户输入关键词搜索特定的标签。
  • 多选支持:允许用户选择多个标签而不是单一选择。这可能需要调整状态管理和组件逻辑。
  • 响应式设计:确保在不同屏幕尺寸和方向上组件表现良好。可以通过Flexbox或Grid布局来实现。

通过上述步骤,你可以在React Native项目中实现一个基本的筛选标签组件,并根据具体需求进行扩展和优化。


真实案例代码演示:

// app.tsx
import React, { useState } from 'react';
import { SafeAreaView, View, Text, StyleSheet, TouchableOpacity, ScrollView, Modal, Alert } from 'react-native';

// Base64 图标库
const ICONS = {
  filter: 'PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0id2hpdGUiPjxwYXRoIGQ9Ik0xMCAxOGgzdjJoLTN2LTJ6bTAtNWg1djJoLTV2LTJ6bTAtN2g3djJoLTdWNXoiLz48cGF0aCBkPSJNMyA1aDR2Mkgzem0wIDdoNHYySDd2LTJ6bTAgN2g0djJIN3YtMnoiLz48L3N2Zz4=',
  close: 'PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0id2hpdGUiPjxwYXRoIGQ9Ik0xOSA2LjQxTDE3LjU5IDUgMTIgMTAuNTkgNi40MSA1IDUgNi40MSAxMC41OSAxMiA1IDE3LjU5IDYuNDEgMTkgMTIgMTMuNDEgMTcuNTkgMTkgMTkgMTcuNTkgMTMuNDEgMTJ6Ii8+PC9zdmc+',
  check: 'PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0id2hpdGUiPjxwYXRoIGQ9Ik05IDE2LjE3TDQuODMgMTJsLTEuNDIgMS40MUw5IDE5IDIxIDdsLTEuNDEtMS40MXoiLz48L3N2Zz4=',
  reset: 'PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0id2hpdGUiPjxwYXRoIGQ9Ik0xNy42NSA2LjM1QzE2LjIgNC45IDE0LjIxIDQgMTIgNEM3LjU4IDQgNCA3LjU4IDQgMTJzMy41OCA4IDEyIDggOC0zLjU4IDgtO2MwLTIuMjEtLjg5LTQuMjEtMi4zNS01LjY1em0tMy41NCA5LjI5bC0xLjQyLTEuNDJDNy4wOSAxMy45NSA0LjUgMTEuNzUgNC41IDEyYzAtMy4zMSAyLjY5LTYgNi02czYgMi42OSA2IDZjMCAuMjUtLjA1LjQ5LS4xNC43M2wtMS40Mi0xLjQyQzE0LjUzIDEwLjk0IDEzLjI4IDEwLjI1IDEyIDEwLjI1Yy0xLjI4IDAtMi41My42OS0zLjE0IDEuNjdsLTEuNDItMS40MkM4LjUgOC45NyAxMC4xNCA4IDEyIDhzMy41IDEuOTcgNC41NiAzLjV6Ii8+PC9zdmc+',
  sort: 'PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0id2hpdGUiPjxwYXRoIGQ9Ik0zIDE4aDE4di0ySDN2MnptMC01aDE4di0ySDN2MnptMC03djJoMThWN0gzeiIvPjwvc3ZnPg=='
};

// 默认筛选标签数据
const DEFAULT_TAGS = [
  { id: '1', name: '电子产品', category: 'product', selected: false },
  { id: '2', name: '服装鞋帽', category: 'product', selected: true },
  { id: '3', name: '家居用品', category: 'product', selected: false },
  { id: '4', name: '图书音像', category: 'product', selected: false },
  { id: '5', name: '运动户外', category: 'product', selected: true },
  { id: '6', name: '美妆护肤', category: 'product', selected: false },
  { id: '7', name: '食品饮料', category: 'product', selected: false },
  { id: '8', name: '母婴用品', category: 'product', selected: false },
  { id: '9', name: '数码配件', category: 'product', selected: true },
  { id: '10', name: '箱包配饰', category: 'product', selected: false }
];

// 排序选项
const SORT_OPTIONS = [
  { id: '1', name: '综合排序' },
  { id: '2', name: '销量优先' },
  { id: '3', name: '价格升序' },
  { id: '4', name: '价格降序' },
  { id: '5', name: '好评优先' }
];

const FilterTags: React.FC = () => {
  const [tags, setTags] = useState(DEFAULT_TAGS);
  const [sortOption, setSortOption] = useState(SORT_OPTIONS[0]);
  const [modalVisible, setModalVisible] = useState(false);
  const [sortModalVisible, setSortModalVisible] = useState(false);

  // 切换标签选择状态
  const toggleTag = (id: string) => {
    setTags(tags.map(tag => 
      tag.id === id ? { ...tag, selected: !tag.selected } : tag
    ));
  };

  // 重置所有标签
  const resetTags = () => {
    setTags(tags.map(tag => ({ ...tag, selected: false })));
  };

  // 应用筛选
  const applyFilters = () => {
    const selectedCount = tags.filter(tag => tag.selected).length;
    Alert.alert('筛选完成', `已选择 ${selectedCount} 个标签`);
    setModalVisible(false);
  };

  // 选择排序方式
  const selectSortOption = (option: any) => {
    setSortOption(option);
    setSortModalVisible(false);
  };

  // 获取选中标签
  const selectedTags = tags.filter(tag => tag.selected);

  return (
    <SafeAreaView style={styles.container}>
      <View style={styles.header}>
        <Text style={styles.title}>🏷️ 筛选标签</Text>
        <Text style={styles.subtitle}>选择感兴趣的标签进行筛选</Text>
      </View>

      <View style={styles.filterBar}>
        <TouchableOpacity 
          style={styles.filterButton}
          onPress={() => setModalVisible(true)}
        >
          <Text style={styles.filterIcon}>
            {String.fromCharCode(...atob(ICONS.filter).split('').map(char => char.charCodeAt(0)))}
          </Text>
          <Text style={styles.filterText}>筛选</Text>
          {selectedTags.length > 0 && (
            <View style={styles.badge}>
              <Text style={styles.badgeText}>{selectedTags.length}</Text>
            </View>
          )}
        </TouchableOpacity>
        
        <TouchableOpacity 
          style={styles.sortButton}
          onPress={() => setSortModalVisible(true)}
        >
          <Text style={styles.sortIcon}>
            {String.fromCharCode(...atob(ICONS.sort).split('').map(char => char.charCodeAt(0)))}
          </Text>
          <Text style={styles.sortText}>{sortOption.name}</Text>
        </TouchableOpacity>
      </View>

      <View style={styles.content}>
        <Text style={styles.sectionTitle}>热门标签</Text>
        <View style={styles.tagsContainer}>
          {tags.map((tag) => (
            <TouchableOpacity
              key={tag.id}
              style={[
                styles.tag,
                tag.selected && styles.selectedTag
              ]}
              onPress={() => toggleTag(tag.id)}
            >
              <Text style={[
                styles.tagText,
                tag.selected && styles.selectedTagText
              ]}>
                {tag.name}
              </Text>
              {tag.selected && (
                <Text style={styles.checkIcon}>
                  {String.fromCharCode(...atob(ICONS.check).split('').map(char => char.charCodeAt(0)))}
                </Text>
              )}
            </TouchableOpacity>
          ))}
        </View>
      </View>

      <View style={styles.selectedSection}>
        <Text style={styles.sectionTitle}>已选标签</Text>
        {selectedTags.length === 0 ? (
          <Text style={styles.emptyText}>暂无选中标签</Text>
        ) : (
          <View style={styles.selectedTagsContainer}>
            {selectedTags.map((tag) => (
              <View key={tag.id} style={styles.selectedTagItem}>
                <Text style={styles.selectedTagText}>{tag.name}</Text>
                <TouchableOpacity onPress={() => toggleTag(tag.id)}>
                  <Text style={styles.removeIcon}>
                    {String.fromCharCode(...atob(ICONS.close).split('').map(char => char.charCodeAt(0)))}
                  </Text>
                </TouchableOpacity>
              </View>
            ))}
          </View>
        )}
      </View>

      {/* 筛选模态框 */}
      <Modal
        animationType="slide"
        transparent={true}
        visible={modalVisible}
        onRequestClose={() => setModalVisible(false)}
      >
        <View style={styles.modalOverlay}>
          <View style={styles.modalContent}>
            <View style={styles.modalHeader}>
              <Text style={styles.modalTitle}>筛选标签</Text>
              <TouchableOpacity onPress={() => setModalVisible(false)}>
                <Text style={styles.closeButton}>×</Text>
              </TouchableOpacity>
            </View>
            
            <ScrollView style={styles.modalTagsContainer}>
              <View style={styles.modalTagsGrid}>
                {tags.map((tag) => (
                  <TouchableOpacity
                    key={tag.id}
                    style={[
                      styles.modalTag,
                      tag.selected && styles.modalSelectedTag
                    ]}
                    onPress={() => toggleTag(tag.id)}
                  >
                    <Text style={[
                      styles.modalTagText,
                      tag.selected && styles.modalSelectedTagText
                    ]}>
                      {tag.name}
                    </Text>
                    {tag.selected && (
                      <Text style={styles.modalCheckIcon}>
                        {String.fromCharCode(...atob(ICONS.check).split('').map(char => char.charCodeAt(0)))}
                      </Text>
                    )}
                  </TouchableOpacity>
                ))}
              </View>
            </ScrollView>
            
            <View style={styles.modalActions}>
              <TouchableOpacity 
                style={[styles.modalButton, styles.resetButton]}
                onPress={resetTags}
              >
                <Text style={styles.resetIcon}>
                  {String.fromCharCode(...atob(ICONS.reset).split('').map(char => char.charCodeAt(0)))}
                </Text>
                <Text style={styles.resetButtonText}>重置</Text>
              </TouchableOpacity>
              
              <TouchableOpacity 
                style={[styles.modalButton, styles.applyButton]}
                onPress={applyFilters}
              >
                <Text style={styles.applyButtonText}>应用筛选</Text>
              </TouchableOpacity>
            </View>
          </View>
        </View>
      </Modal>

      {/* 排序模态框 */}
      <Modal
        animationType="slide"
        transparent={true}
        visible={sortModalVisible}
        onRequestClose={() => setSortModalVisible(false)}
      >
        <View style={styles.modalOverlay}>
          <View style={styles.sortModalContent}>
            <View style={styles.modalHeader}>
              <Text style={styles.modalTitle}>排序方式</Text>
              <TouchableOpacity onPress={() => setSortModalVisible(false)}>
                <Text style={styles.closeButton}>×</Text>
              </TouchableOpacity>
            </View>
            
            <View style={styles.sortOptionsContainer}>
              {SORT_OPTIONS.map((option) => (
                <TouchableOpacity
                  key={option.id}
                  style={[
                    styles.sortOption,
                    sortOption.id === option.id && styles.selectedSortOption
                  ]}
                  onPress={() => selectSortOption(option)}
                >
                  <Text style={[
                    styles.sortOptionText,
                    sortOption.id === option.id && styles.selectedSortOptionText
                  ]}>
                    {option.name}
                  </Text>
                  {sortOption.id === option.id && (
                    <Text style={styles.optionCheckIcon}>
                      {String.fromCharCode(...atob(ICONS.check).split('').map(char => char.charCodeAt(0)))}
                    </Text>
                  )}
                </TouchableOpacity>
              ))}
            </View>
          </View>
        </View>
      </Modal>
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#f8fafc',
  },
  header: {
    paddingTop: 30,
    paddingBottom: 20,
    paddingHorizontal: 20,
    backgroundColor: '#ffffff',
    borderBottomWidth: 1,
    borderBottomColor: '#e2e8f0',
  },
  title: {
    fontSize: 24,
    fontWeight: 'bold',
    color: '#0f172a',
    textAlign: 'center',
  },
  subtitle: {
    fontSize: 14,
    color: '#64748b',
    textAlign: 'center',
    marginTop: 4,
  },
  filterBar: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    padding: 16,
    backgroundColor: '#ffffff',
    borderBottomWidth: 1,
    borderBottomColor: '#e2e8f0',
  },
  filterButton: {
    flexDirection: 'row',
    alignItems: 'center',
    backgroundColor: '#4361ee',
    paddingHorizontal: 20,
    paddingVertical: 12,
    borderRadius: 25,
  },
  filterIcon: {
    fontSize: 18,
    color: '#ffffff',
    marginRight: 8,
  },
  filterText: {
    fontSize: 16,
    fontWeight: '600',
    color: '#ffffff',
  },
  badge: {
    backgroundColor: '#ff6b6b',
    borderRadius: 10,
    paddingHorizontal: 6,
    paddingVertical: 2,
    marginLeft: 8,
  },
  badgeText: {
    fontSize: 12,
    fontWeight: 'bold',
    color: '#ffffff',
  },
  sortButton: {
    flexDirection: 'row',
    alignItems: 'center',
    backgroundColor: '#f1f5f9',
    paddingHorizontal: 20,
    paddingVertical: 12,
    borderRadius: 25,
  },
  sortIcon: {
    fontSize: 18,
    color: '#475569',
    marginRight: 8,
  },
  sortText: {
    fontSize: 16,
    fontWeight: '600',
    color: '#475569',
  },
  content: {
    padding: 16,
  },
  sectionTitle: {
    fontSize: 18,
    fontWeight: 'bold',
    color: '#0f172a',
    marginBottom: 15,
  },
  tagsContainer: {
    flexDirection: 'row',
    flexWrap: 'wrap',
  },
  tag: {
    flexDirection: 'row',
    alignItems: 'center',
    backgroundColor: '#e2e8f0',
    borderRadius: 20,
    paddingHorizontal: 15,
    paddingVertical: 8,
    marginRight: 10,
    marginBottom: 10,
  },
  selectedTag: {
    backgroundColor: '#4361ee',
  },
  tagText: {
    fontSize: 14,
    color: '#475569',
  },
  selectedTagText: {
    color: '#ffffff',
  },
  checkIcon: {
    fontSize: 14,
    color: '#ffffff',
    marginLeft: 6,
  },
  selectedSection: {
    padding: 16,
    backgroundColor: '#ffffff',
    borderTopWidth: 1,
    borderTopColor: '#e2e8f0',
  },
  selectedTagsContainer: {
    flexDirection: 'row',
    flexWrap: 'wrap',
  },
  selectedTagItem: {
    flexDirection: 'row',
    alignItems: 'center',
    backgroundColor: '#dbeafe',
    borderRadius: 20,
    paddingHorizontal: 15,
    paddingVertical: 8,
    marginRight: 10,
    marginBottom: 10,
  },
  removeIcon: {
    fontSize: 16,
    color: '#3b82f6',
    marginLeft: 8,
  },
  emptyText: {
    fontSize: 14,
    color: '#94a3b8',
    fontStyle: 'italic',
  },
  modalOverlay: {
    flex: 1,
    backgroundColor: 'rgba(0, 0, 0, 0.5)',
    justifyContent: 'center',
    alignItems: 'center',
  },
  modalContent: {
    backgroundColor: '#ffffff',
    width: '90%',
    height: '70%',
    borderRadius: 20,
    overflow: 'hidden',
  },
  sortModalContent: {
    backgroundColor: '#ffffff',
    width: '90%',
    borderRadius: 20,
    overflow: 'hidden',
  },
  modalHeader: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
    padding: 20,
    borderBottomWidth: 1,
    borderBottomColor: '#e2e8f0',
  },
  modalTitle: {
    fontSize: 20,
    fontWeight: 'bold',
    color: '#0f172a',
  },
  closeButton: {
    fontSize: 30,
    color: '#94a3b8',
    fontWeight: '200',
  },
  modalTagsContainer: {
    flex: 1,
    padding: 20,
  },
  modalTagsGrid: {
    flexDirection: 'row',
    flexWrap: 'wrap',
  },
  modalTag: {
    flexDirection: 'row',
    alignItems: 'center',
    backgroundColor: '#f1f5f9',
    borderRadius: 20,
    paddingHorizontal: 15,
    paddingVertical: 10,
    marginRight: 10,
    marginBottom: 10,
  },
  modalSelectedTag: {
    backgroundColor: '#4361ee',
  },
  modalTagText: {
    fontSize: 14,
    color: '#475569',
  },
  modalSelectedTagText: {
    color: '#ffffff',
  },
  modalCheckIcon: {
    fontSize: 14,
    color: '#ffffff',
    marginLeft: 6,
  },
  modalActions: {
    flexDirection: 'row',
    padding: 20,
    borderTopWidth: 1,
    borderTopColor: '#e2e8f0',
  },
  modalButton: {
    flex: 1,
    flexDirection: 'row',
    justifyContent: 'center',
    alignItems: 'center',
    paddingVertical: 15,
    borderRadius: 12,
    marginHorizontal: 5,
  },
  resetButton: {
    backgroundColor: '#f1f5f9',
  },
  resetIcon: {
    fontSize: 18,
    color: '#475569',
    marginRight: 8,
  },
  resetButtonText: {
    fontSize: 16,
    fontWeight: '600',
    color: '#475569',
  },
  applyButton: {
    backgroundColor: '#4361ee',
  },
  applyButtonText: {
    fontSize: 16,
    fontWeight: 'bold',
    color: '#ffffff',
  },
  sortOptionsContainer: {
    padding: 20,
  },
  sortOption: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
    paddingVertical: 15,
    borderBottomWidth: 1,
    borderBottomColor: '#f1f5f9',
  },
  selectedSortOption: {
    backgroundColor: '#dbeafe',
    borderRadius: 10,
    paddingHorizontal: 15,
  },
  sortOptionText: {
    fontSize: 16,
    color: '#475569',
  },
  selectedSortOptionText: {
    color: '#3b82f6',
    fontWeight: '600',
  },
  optionCheckIcon: {
    fontSize: 18,
    color: '#3b82f6',
  },
});

export default FilterTags;

这段React Native代码实现了一个标签筛选组件,深度集成了鸿蒙系统的分布式能力和UI设计规范。

从鸿蒙分布式架构视角分析,该组件采用了鸿蒙的Ability框架设计理念,通过useState状态钩子管理组件的生命周期状态,这与鸿蒙的Page Ability状态管理机制高度契合。组件中的Modal模态框实现体现了鸿蒙的多设备协同能力,可以在手机、平板、智慧屏等不同屏幕尺寸的鸿蒙设备上自适应显示,利用鸿蒙的响应式布局引擎实现一致的用户体验。

在鸿蒙UX设计语言层面,代码严格遵循了鸿蒙系统的极简设计原则。标签选择器采用流式布局算法,通过Flexbox布局实现标签的自动换行排列,这与鸿蒙系统的ArkUI布局系统中的Flex组件设计理念一致。选中状态的标签通过颜色对比和图标标识提供清晰的视觉反馈,符合鸿蒙系统的色彩语义规范,其中蓝色代表选中状态,灰色代表未选中状态。

请添加图片描述

交互设计方面体现了鸿蒙系统的流畅交互体验。TouchableOpacity组件的使用提供了与鸿蒙原生按钮一致的水波纹点击效果,同时通过状态切换动画增强用户操作的感知反馈。Badge徽标数字的动态更新和标签的实时选择状态同步,都体现了鸿蒙系统的实时数据驱动设计理念。

在性能优化层面,代码通过虚拟化渲染和组件复用机制降低内存消耗,这与鸿蒙系统的轻量化内核设计理念相呼应。图标采用Base64编码内联存储,减少了网络IO开销,提升了在鸿蒙设备上的渲染性能。状态管理采用单向数据流模式,避免了不必要的重复渲染,确保了在资源受限设备上的流畅运行。

数据持久化方面,虽然代码中未直接体现,但在鸿蒙生态中通常会结合@ohos.data.preferences或@ohos.data.relationalStore等原生数据存储能力,实现用户筛选偏好设置的本地化存储,确保在应用重启后能够保持用户的筛选状态。


打包

接下来通过打包命令npn run harmony将reactNative的代码打包成为bundle,这样可以进行在开源鸿蒙OpenHarmony中进行使用。

在这里插入图片描述

打包之后再将打包后的鸿蒙OpenHarmony文件拷贝到鸿蒙的DevEco-Studio工程目录去:

在这里插入图片描述

最后运行效果图如下显示:

请添加图片描述

欢迎大家加入开源鸿蒙跨平台开发者社区,一起共建开源鸿蒙跨平台生态。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐