React Native跨平台技术在开源鸿蒙中开发一个筛选标签组件(通常称为Tag组件或Chip组件),TouchableOpacity组件通过onPress事件实现点击事件选择
在React Native中开发一个筛选标签组件(通常称为Tag组件或Chip组件),可以帮助用户根据不同的条件来筛选信息。这类组件通常用于电商应用、新闻聚合应用、社交媒体等场景,以提供用户友好的交互方式。下面是如何实现这样一个筛选标签组件的步骤和示例代码。
- 准备环境
确保你的开发环境已经安装了React Native。如果还没有安装,可以通过以下命令来安装:
npx react-native init MyProject
cd MyProject
- 创建筛选标签组件
我们可以创建一个简单的筛选标签组件,用户可以通过点击不同的标签来筛选内容。
步骤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;
- 测试和调整样式
运行你的React Native应用,查看筛选标签组件是否按预期工作。根据需要调整样式和功能,例如增加更多的交互反馈(如动画)、动态加载标签等。你可以通过修改FilterTag组件的样式或增加额外的逻辑来实现这些功能。例如,你可以根据selected状态改变背景色、边框色或文本颜色等。
- 扩展功能(可选)
- 动态加载标签:从服务器获取标签列表,而不是硬编码在组件中。
- 搜索功能:添加输入框,允许用户输入关键词搜索特定的标签。
- 多选支持:允许用户选择多个标签而不是单一选择。这可能需要调整状态管理和组件逻辑。
- 响应式设计:确保在不同屏幕尺寸和方向上组件表现良好。可以通过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工程目录去:

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

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





所有评论(0)