news 2026/4/3 1:46:01

商城App标签选择组件开发,如何React Native鸿蒙跨平台开发`react-native-tags`是一个流行的React Native库,用于实现标签选择功能

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
商城App标签选择组件开发,如何React Native鸿蒙跨平台开发`react-native-tags`是一个流行的React Native库,用于实现标签选择功能

在React Native中开发一个商城App的标签选择组件,你可以使用多种方法。这里将介绍几种常用的方法来实现标签选择功能,包括使用原生组件和第三方库。

方法1:使用原生组件

  1. 使用<TouchableOpacity><View>

这是最基本的实现方式,适合简单的标签选择。

importReactfrom'react';import{View,Text,TouchableOpacity,StyleSheet}from'react-native';constTagSelector=({tags,onSelect})=>(<View style={styles.container}>{tags.map((tag,index)=>(<TouchableOpacity key={index}style={[styles.tag,{backgroundColor:tag.selected?'007AFF':'E0E0E0'}]}onPress={()=>onSelect(tag)}><Text style={styles.tagText}>{tag.name}</Text></TouchableOpacity>))}</View>);conststyles=StyleSheet.create({container:{flexDirection:'row',flexWrap:'wrap',padding:10,},tag:{margin:5,paddingHorizontal:10,paddingVertical:5,borderRadius:15,},tagText:{color:'000',}});exportdefaultTagSelector;
  1. 使用<FlatList>优化性能

如果你有大量的标签,使用<FlatList>可以提高性能。

importReactfrom'react';import{FlatList,TouchableOpacity,Text,StyleSheet}from'react-native';constTagSelector=({tags,onSelect})=>(<FlatList data={tags}keyExtractor={(item,index)=>index.toString()}renderItem={({item})=>(<TouchableOpacity style={[styles.tag,{backgroundColor:item.selected?'007AFF':'E0E0E0'}]}onPress={()=>onSelect(item)}><Text style={styles.tagText}>{item.name}</Text></TouchableOpacity>)}contentContainerStyle={styles.container}horizontal={true}// 如果需要水平滚动显示标签/>);conststyles=StyleSheet.create({container:{paddingHorizontal:10},tag:{marginRight:10,padding:10,borderRadius:15},tagText:{color:'000'}});

方法2:使用第三方库

使用react-native-tags

react-native-tags是一个流行的React Native库,用于实现标签选择功能。首先,你需要安装这个库:

npminstallreact-native-tags --save

或者使用yarn:

yarnaddreact-native-tags

然后,你可以这样使用它:

importReactfrom'react';import{Tags}from'react-native-tags';import{View}from'react-native';constTagSelector=({tags,onSelect})=>(<View><Tags initialTags={tags}onChange={onSelect}textStyle={{fontSize:16}}containerStyle={{flexWrap:'wrap'}}onTagPress={(index)=>console.log(`Tag pressed was${index}`)}/></View>);

方法3:自定义样式和交互逻辑更复杂的组件可以使用react-native-tag-selector等库。这些库通常提供了更多定制化的选项和更复杂的交互逻辑。例如:使用react-native-tag-selector:首先安装:bashnpm install react-native-tag-selector --save然后使用:```javascriptimport React from ‘react’;import { TagSelector } from ‘react-native-tag-selector’;const App = () => { const handleSelect = (selectedTags) => { console.log(selectedTags); }; return ( <TagSelector tags={[‘Tag1’, ‘Tag2’, ‘Tag3’]} onChange={handleSelect} initialSelected

--- ## 真实案例项目代码演示: ```js // app.tsx import React, { useState } from 'react'; import { View, Text, StyleSheet, TouchableOpacity, ScrollView, Image } from 'react-native'; const App = () => { const [selectedTags, setSelectedTags] = useState<string[]>([]); // 标签数据 const tags = [ { id: '1', name: '手机数码', icon: '📱' }, { id: '2', name: '家用电器', icon: '📺' }, { id: '3', name: '服装鞋帽', icon: '👕' }, { id: '4', name: '美妆护肤', icon: '💄' }, { id: '5', name: '家居用品', icon: '🏠' }, { id: '6', name: '运动户外', icon: '⚽' }, { id: '7', name: '图书音像', icon: '📚' }, { id: '8', name: '食品饮料', icon: '🍎' }, { id: '9', name: '母婴用品', icon: '👶' }, { id: '10', name: '汽车配件', icon: '🚗' }, { id: '11', name: '珠宝首饰', icon: '💍' }, { id: '12', name: '办公用品', icon: '📎' } ]; // Base64图标 const icons = { check: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiM0Mjg1RjQiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48cG9seWxpbmUgcG9pbnRzPSIyMCA2IDkgMTcgNCAxMiI+PC9wb2x5bGluZT48L3N2Zz4=', uncheck: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiNEN0Q4REEiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48Y2lyY2xlIGN4PSIxMiIgY3k9IjEyIiByPSIxMCI+PC9jaXJjbGU+PC9zdmc+' }; const toggleTag = (tagId: string) => { setSelectedTags(prev => { if (prev.includes(tagId)) { return prev.filter(id => id !== tagId); } else { return [...prev, tagId]; } }); }; const selectAll = () => { setSelectedTags(tags.map(tag => tag.id)); }; const clearAll = () => { setSelectedTags([]); }; return ( <ScrollView style={styles.container}> <View style={styles.header}> <Text style={styles.title}>商品标签选择</Text> <Text style={styles.subtitle}>请选择您感兴趣的商品类别</Text> </View> <View style={styles.selectionControls}> <TouchableOpacity style={styles.controlButton} onPress={selectAll}> <Text style={styles.controlButtonText}>全选</Text> </TouchableOpacity> <TouchableOpacity style={styles.controlButton} onPress={clearAll}> <Text style={styles.controlButtonText}>清空</Text> </TouchableOpacity> </View> <View style={styles.tagContainer}> {tags.map(tag => { const isSelected = selectedTags.includes(tag.id); return ( <TouchableOpacity key={tag.id} style={[ styles.tag, isSelected && styles.selectedTag ]} onPress={() => toggleTag(tag.id)} > <View style={styles.tagContent}> <Text style={styles.tagIcon}>{tag.icon}</Text> <Text style={[ styles.tagName, isSelected && styles.selectedTagName ]}> {tag.name} </Text> </View> <View style={styles.tagIndicator}> {isSelected ? ( <Image source={{ uri: icons.check }} style={styles.indicatorIcon} /> ) : ( <Image source={{ uri: icons.uncheck }} style={styles.indicatorIcon} /> )} </View> </TouchableOpacity> ); })} </View> <View style={styles.resultContainer}> <Text style={styles.resultTitle}>已选择 ({selectedTags.length})</Text> {selectedTags.length > 0 ? ( <View style={styles.selectedTagsContainer}> {selectedTags.map(tagId => { const tag = tags.find(t => t.id === tagId); return tag ? ( <View key={tagId} style={styles.selectedTagItem}> <Text style={styles.selectedTagText}>{tag.icon} {tag.name}</Text> </View> ) : null; })} </View> ) : ( <Text style={styles.noSelectionText}>暂未选择任何标签</Text> )} </View> </ScrollView> ); }; const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#f8f9fa', padding: 20 }, header: { alignItems: 'center', marginBottom: 25, paddingTop: 20 }, title: { fontSize: 26, fontWeight: 'bold', color: '#2c3e50' }, subtitle: { fontSize: 15, color: '#7f8c8d', marginTop: 6 }, selectionControls: { flexDirection: 'row', justifyContent: 'space-between', marginBottom: 20 }, controlButton: { backgroundColor: '#4285F4', paddingHorizontal: 20, paddingVertical: 12, borderRadius: 25 }, controlButtonText: { color: '#fff', fontWeight: '600', fontSize: 15 }, tagContainer: { flexDirection: 'row', flexWrap: 'wrap', justifyContent: 'space-between' }, tag: { width: '48%', backgroundColor: '#fff', borderRadius: 14, padding: 16, marginBottom: 15, flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', elevation: 2, shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.08, shadowRadius: 4 }, selectedTag: { backgroundColor: '#e8f0fe', borderColor: '#4285F4', borderWidth: 1 }, tagContent: { flexDirection: 'row', alignItems: 'center' }, tagIcon: { fontSize: 20, marginRight: 10 }, tagName: { fontSize: 16, color: '#34495e', fontWeight: '500' }, selectedTagName: { color: '#4285F4', fontWeight: '600' }, tagIndicator: { width: 24, height: 24, alignItems: 'center', justifyContent: 'center' }, indicatorIcon: { width: 20, height: 20 }, resultContainer: { backgroundColor: '#fff', borderRadius: 16, padding: 20, marginTop: 20, elevation: 2, shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.08, shadowRadius: 4 }, resultTitle: { fontSize: 20, fontWeight: 'bold', color: '#2c3e50', marginBottom: 15 }, selectedTagsContainer: { flexDirection: 'row', flexWrap: 'wrap' }, selectedTagItem: { backgroundColor: '#4285F4', borderRadius: 20, paddingHorizontal: 15, paddingVertical: 8, margin: 5 }, selectedTagText: { color: '#fff', fontSize: 14 }, noSelectionText: { color: '#95a5a6', fontStyle: 'italic', textAlign: 'center', paddingVertical: 15 } }); export default App;

这段React Native代码实现了一个商品标签选择功能,其核心原理基于React的状态管理和事件处理机制。toggleTag函数作为标签切换的核心逻辑,通过函数式更新确保状态变更的准确性和可预测性。该函数使用prev参数获取先前的selectedTags状态,然后根据当前标签是否已选中来决定是添加还是移除标签ID,这种实现方式避免了闭包陷阱,确保在并发更新场景下的状态一致性。

从鸿蒙系统适配的角度来看,该代码充分利用了React Native的跨平台特性,在鸿蒙设备上能够获得原生级的性能表现。鸿蒙系统的分布式架构强调组件间的低耦合和高内聚,而React的单向数据流和状态提升概念与这一理念高度契合。selectedTags状态作为单一数据源,通过props向下传递给各个子组件,确保了数据的一致性,这在鸿蒙系统的多设备协同场景中尤为重要。

标签选择功能的实现考虑了鸿蒙系统的触摸交互特性。TouchableOpacity组件提供了内置的触摸反馈效果,这种交互设计符合鸿蒙系统的UI规范。在鸿蒙设备上,触摸事件的处理经过了系统层面的优化,能够提供流畅的响应体验。toggleTag函数中的数组操作使用了不可变数据模式,通过扩展运算符和filter方法创建新数组,而不是直接修改原数组,这种做法在鸿蒙系统的状态管理中能够更好地配合其响应式更新机制。

全选和清空功能的实现体现了对用户操作效率的考量。selectAll函数通过map方法一次性获取所有标签ID,clearAll函数通过设置空数组实现状态重置,这两种操作在鸿蒙系统的批量数据处理场景中能够提供良好的性能表现。鸿蒙系统强调高效的任务处理能力,这种设计能够减少不必要的状态更新次数,提升应用响应速度。

UI渲染逻辑通过map方法遍历标签数组,动态生成标签组件。每个标签组件根据isSelected状态应用不同的样式,这种条件渲染模式在鸿蒙系统的动态UI构建中非常常见。标签组件内部采用了水平布局,左侧显示图标和名称,右侧显示选中状态指示器,这种设计在鸿蒙系统的不同屏幕尺寸设备上都能保持良好的视觉效果。

结果展示区域实时显示已选择的标签数量和具体标签内容,通过find方法从原始标签数组中获取选中标签的详细信息。这种数据处理方式在鸿蒙系统的数据同步机制中具有优势,能够确保展示数据与选择状态的一致性。当没有标签被选中时,显示友好的提示信息,体现了良好的用户体验设计。

鸿蒙系统的内存管理机制强调对象的复用和及时释放,代码中通过条件渲染和短路求值等方式避免了不必要的组件创建和销毁。标签组件的key属性使用标签ID,确保了React的diff算法能够正确识别组件身份,在鸿蒙设备的高性能渲染需求下能够提供稳定的性能表现。


打包

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

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

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

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

在React Native中开发一个运动App的步数计数器组件,你可以使用多种方法来实现这一功能。以下是几个步骤和示例代码,帮助你开始开发:

  1. 安装必要的库

首先,你需要安装一些库来帮助你处理设备的步数数据。最常用的是react-native-sensors库,它可以让你访问设备的传感器数据,包括步数传感器。

npminstallreact-native-sensors
  1. 配置权限

在你的Harmony/app/src/main/HarmonyManifest.xmlHarmony/项目名/Info.plist中添加必要的权限。

Harmony:

<uses-permissionHarmony:name="Harmony.permission.ACTIVITY_RECOGNITION"/>

Harmony:

对于Harmony,你可能需要使用CoreMotion框架,并且确保你的App有使用传感器数据的权限。在Info.plist中添加以下键值对:

<key>NSMotionUsageDescription</key><string>Your usage description</string>
  1. 使用react-native-sensors获取步数数据

在你的React Native组件中,你可以使用react-native-sensors来订阅步数数据。

importReact,{useEffect,useState}from'react';import{View,Text,StyleSheet}from'react-native';importSensorsfrom'react-native-sensors';constStepCounter=()=>{const[steps,setSteps]=useState(0);const[error,setError]=useState(null);const[isSubscribed,setIsSubscribed]=useState(false);useEffect(()=>{constsubscribe=()=>{Sensors.startStepCounter((err,step)=>{if(err){setError(err);}else{setSteps(step);}});setIsSubscribed(true);};constunsubscribe=()=>{Sensors.stopStepCounter();setIsSubscribed(false);};subscribe();// 启动步数计数器监听return()=>unsubscribe();// 清理操作,停止监听},[]);// 空依赖数组确保只运行一次if(error){return<Text>Error:{error.message}</Text>;}return(<View style={styles.container}><Text>Steps:{steps}</Text></View>);};conststyles=StyleSheet.create({container:{flex:1,justifyContent:'center',alignItems:'center',},});exportdefaultStepCounter;
  1. 测试你的应用

现在你可以在模拟器或真机上测试你的应用了。确保你的设备支持步数传感器的使用,并且你已经正确配置了所有必要的权限。

  1. 优化和扩展功能(可选)

你可以根据需要添加更多功能,如步数历史记录、步数目标设置、实时更新UI等。例如,你可以使用react-native-step-by-step库来获取更详细的步行数据。此外,你还可以使用reduxreact-query来管理状态,以便在应用的不同部分之间共享步数数据。

npminstallreact-native-step-by-step redux react-redux react-query @reduxjs/toolkit --save

通过以上步骤,你应该能够成功地在React Native中开发一个运动App的步数计数器组件。


// app.tsximportReact,{useState,useEffect}from'react';import{View,Text,StyleSheet,TouchableOpacity,ScrollView,Image,Alert}from'react-native';constApp=()=>{const[steps,setSteps]=useState(0);const[isCounting,setIsCounting]=useState(false);const[startTime,setStartTime]=useState<number|null>(null);const[elapsedTime,setElapsedTime]=useState(0);const[calories,setCalories]=useState(0);const[distance,setDistance]=useState(0);// Base64 iconsconsticons={play:'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiM0Mjg1RjQiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48cG9seWdvbiBwb2ludHM9IjUgMyAyMCAxMiA1IDIxIDUgMyI+PC9wb2x5Z29uPjwvc3ZnPg==',pause:'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiM0Mjg1RjQiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48cmVjdCB4PSI2IiB5PSI0IiB3aWR0aD0iNCIgaGVpZ2h0PSIxNiIgcng9IjEiIHJ5PSIxIj48L3JlY3Q+PHJlY3QgeD0iMTQiIHk9IjQiIHdpZHRoPSI0IiBoZWlnaHQ9IjE2IiByeD0iMSIgcnk9IjEiPjwvcmVjdD48L3N2Zz4=',reset:'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiM5OTkiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48cGF0aCBkPSJNMyAxMWE5IDkgMCAwIDEgOS05YzIuNjEgMCA0LjkxIDEuMDQgNi43IDEuODIiPjwvcGF0aD48cGF0aCBkPSJNMjEgMTVhOSA5IDAgMCAxLTkgOWMtMi42MSAwLTQuOTEtMS4wNC02LjctMS44MiI+PC9wYXRoPjxwYXRoIGQ9Ik04IDEyaDgiPjwvcGF0aD48cGF0aCBkPSJNMTIgOGw0IDQgLTQgNCI+PC9wYXRoPjwvc3ZnPg==',step:'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiM0Mjg1RjQiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48cGF0aCBkPSJNMTMgNGE0IDQgMCAxIDEtOCAwIDQgNCAwIDAgMSA4IDB6Ij48L3BhdGg+PHBhdGggZD0iTTE1IDEyYTQgNCAwIDEgMS04IDAgNCA0IDAgMCAxIDggMHoiPjwvcGF0aD48cGF0aCBkPSJNMTcgMjBhNCA0IDAgMSAxLTggMCA0IDQgMCAwIDEgOCAweiI+PC9wYXRoPjxsaW5lIHgxPSIxIiB5MT0iMSIgeDI9IjIzIiB5Mj0iMjMiPjwvbGluZT48L3N2Zz4=',calorie:'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiM0Mjg1RjQiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48Y2lyY2xlIGN4PSIxMiIgY3k9IjEyIiByPSIxMCI+PC9jaXJjbGU+PHBhdGggZD0iTTEyIDhhNCA0IDAgMCAxIDAgOGE0IDQgMCAwIDEgMC04eiI+PC9wYXRoPjxwYXRoIGQ9Ik0xMiA0YTYuOTMgNi45MyAwIDAgMCAwIDE2YTYuOTMgNi45MyAwIDAgMCAwLTE2eiI+PC9wYXRoPjwvc3ZnPg==',distance:'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiM0Mjg1RjQiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48cGF0aCBkPSJNMTIgMjF2LTIiPjwvcGF0aD48cGF0aCBkPSJNMTIgMTV2LTIiPjwvcGF0aD48cGF0aCBkPSJNMTIgOVY3Ij48L3BhdGg+PHBhdGggZD0iTTEyIDVWMiI+PC9wYXRoPjxwYXRoIGQ9Ik01IDEyaC0yIj48L3BhdGg+PHBhdGggZD0iTTE5IDEyaDIiPjwvcGF0aD48cGF0aCBkPSJNMTUgMTJhMyAzIDAgMSAxLTYgMCAzIDMgMCAwIDEgNiAweiI+PC9wYXRoPjwvc3ZnPg=='};// 计算时间格式化constformatTime=(seconds:number)=>{consthrs=Math.floor(seconds/3600);constmins=Math.floor((seconds%3600)/60);constsecs=seconds%60;return`${hrs.toString().padStart(2,'0')}:${mins.toString().padStart(2,'0')}:${secs.toString().padStart(2,'0')}`;};// 开始/暂停计步consttoggleCounting=()=>{if(isCounting){setIsCounting(false);}else{setIsCounting(true);if(startTime===null){setStartTime(Date.now());}}};// 重置计数器constresetCounter=()=>{Alert.alert('确认重置','确定要重置所有数据吗?',[{text:'取消',style:'cancel'},{text:'确定',onPress:()=>{setSteps(0);setIsCounting(false);setStartTime(null);setElapsedTime(0);setCalories(0);setDistance(0);}}]);};// 模拟增加步数constaddSteps=()=>{if(isCounting){constnewSteps=steps+1;setSteps(newSteps);// 每步约消耗0.04卡路里,行走距离约0.7米setCalories(parseFloat((newSteps*0.04).toFixed(2)));setDistance(parseFloat((newSteps*0.7/1000).toFixed(2)));// 转换为公里}};// 时间计算效果useEffect(()=>{letinterval:NodeJS.Timeout|null=null;if(isCounting&&startTime){interval=setInterval(()=>{setElapsedTime(Math.floor((Date.now()-startTime)/1000));},1000);}return()=>{if(interval)clearInterval(interval);};},[isCounting,startTime]);return(<ScrollView style={styles.container}><View style={styles.header}><Text style={styles.title}>步数计数器</Text><Text style={styles.subtitle}>记录您的每日运动</Text></View>{/* 步数主显示区 */}<View style={styles.mainCounter}><TouchableOpacity onPress={addSteps}style={styles.stepButton}><Image source={{uri:icons.step}}style={styles.stepIcon}/><Text style={styles.stepCount}>{steps.toLocaleString()}</Text><Text style={styles.stepLabel}>步数</Text></TouchableOpacity></View>{/* 控制按钮 */}<View style={styles.controls}><TouchableOpacity style={[styles.controlButton,styles.startButton]}onPress={toggleCounting}><Image source={{uri:isCounting?icons.pause:icons.play}}style={styles.controlIcon}/><Text style={styles.controlText}>{isCounting?'暂停':'开始'}</Text></TouchableOpacity><TouchableOpacity style={[styles.controlButton,styles.resetButton]}onPress={resetCounter}><Image source={{uri:icons.reset}}style={styles.controlIcon}/><Text style={styles.controlText}>重置</Text></TouchableOpacity></View>{/* 运动统计信息 */}<View style={styles.statsContainer}><Text style={styles.statsTitle}>运动统计</Text><View style={styles.statsGrid}><View style={styles.statCard}><View style={styles.statHeader}><Image source={{uri:icons.distance}}style={styles.statIcon}/><Text style={styles.statLabel}>距离</Text></View><Text style={styles.statValue}>{distance.toFixed(2)}km</Text></View><View style={styles.statCard}><View style={styles.statHeader}><Image source={{uri:icons.calorie}}style={styles.statIcon}/><Text style={styles.statLabel}>消耗</Text></View><Text style={styles.statValue}>{calories.toFixed(1)}kcal</Text></View><View style={styles.statCard}><View style={styles.statHeader}><Image source={{uri:icons.step}}style={styles.statIcon}/><Text style={styles.statLabel}>时长</Text></View><Text style={styles.statValue}>{formatTime(elapsedTime)}</Text></View></View></View>{/* 进度目标 */}<View style={styles.goalContainer}><View style={styles.goalHeader}><Text style={styles.goalTitle}>今日目标</Text><Text style={styles.goalValue}>10,000</Text></View><View style={styles.progressBar}><View style={[styles.progressFill,{width:`${Math.min(100,(steps/10000)*100)}%`}]}/></View><Text style={styles.progressText}>已完成{Math.min(100,Math.round((steps/10000)*100))}%</Text></View></ScrollView>);};conststyles=StyleSheet.create({container:{flex:1,backgroundColor:'#f0f8ff',padding:20},header:{alignItems:'center',marginBottom:30,paddingTop:20},title:{fontSize:28,fontWeight:'bold',color:'#2c3e50'},subtitle:{fontSize:16,color:'#7f8c8d',marginTop:6},mainCounter:{alignItems:'center',marginBottom:40},stepButton:{width:220,height:220,borderRadius:110,backgroundColor:'#4285F4',alignItems:'center',justifyContent:'center',elevation:8,shadowColor:'#4285F4',shadowOffset:{width:0,height:4},shadowOpacity:0.3,shadowRadius:8},stepIcon:{width:40,height:40,marginBottom:15},stepCount:{fontSize:48,fontWeight:'bold',color:'#fff'},stepLabel:{fontSize:18,color:'#e8f0fe'},controls:{flexDirection:'row',justifyContent:'center',marginBottom:30},controlButton:{flexDirection:'row',alignItems:'center',justifyContent:'center',paddingVertical:14,paddingHorizontal:30,borderRadius:30,marginHorizontal:10,elevation:4,shadowColor:'#000',shadowOffset:{width:0,height:2},shadowOpacity:0.1,shadowRadius:4},startButton:{backgroundColor:'#4285F4'},resetButton:{backgroundColor:'#fff'},controlIcon:{width:24,height:24,marginRight:10},controlText:{fontSize:18,fontWeight:'600'},statsContainer:{backgroundColor:'#fff',borderRadius:20,padding:20,marginBottom:25,elevation:4,shadowColor:'#000',shadowOffset:{width:0,height:2},shadowOpacity:0.1,shadowRadius:4},statsTitle:{fontSize:22,fontWeight:'bold',color:'#2c3e50',marginBottom:20,textAlign:'center'},statsGrid:{flexDirection:'row',justifyContent:'space-between'},statCard:{backgroundColor:'#f8f9fa',borderRadius:16,padding:16,width:'31%',alignItems:'center'},statHeader:{flexDirection:'row',alignItems:'center',marginBottom:10},statIcon:{width:20,height:20,marginRight:8},statLabel:{fontSize:14,color:'#7f8c8d'},statValue:{fontSize:18,fontWeight:'bold',color:'#4285F4'},goalContainer:{backgroundColor:'#fff',borderRadius:20,padding:20,elevation:4,shadowColor:'#000',shadowOffset:{width:0,height:2},shadowOpacity:0.1,shadowRadius:4},goalHeader:{flexDirection:'row',justifyContent:'space-between',marginBottom:15},goalTitle:{fontSize:18,fontWeight:'bold',color:'#2c3e50'},goalValue:{fontSize:16,color:'#7f8c8d'},progressBar:{height:12,backgroundColor:'#ecf0f1',borderRadius:6,marginBottom:10,overflow:'hidden'},progressFill:{height:'100%',backgroundColor:'#4285F4',borderRadius:6},progressText:{fontSize:14,color:'#7f8c8d',textAlign:'center'}});exportdefaultApp;

这段React Native步数计数器代码实现了一个完整的运动数据追踪功能,其核心原理基于React的状态管理和副作用处理机制。formatTime函数通过数学运算将秒数转换为标准的时分秒格式,采用padStart方法确保时间显示的格式统一性,这种时间处理方式在鸿蒙系统的多设备时间同步场景中具有重要意义。

toggleCounting函数作为计数器控制的核心,通过isCounting状态管理开始和暂停功能。当开始计数时,函数检查startTime是否为空来决定是否设置起始时间,这种设计避免了重复点击开始按钮时的时间重置问题。在鸿蒙系统的分布式任务调度中,这种状态管理模式能够确保计数任务在不同设备间的连续性。

resetCounter函数通过Alert组件提供用户确认机制,防止误操作导致数据丢失。确认后的重置操作将所有相关状态恢复初始值,包括步数、计数状态、起始时间、经过时间、消耗卡路里和行走距离。这种全面的状态重置机制在鸿蒙系统的应用生命周期管理中非常重要,能够确保应用在各种使用场景下的状态一致性。

addSteps函数模拟步数增加逻辑,通过简单的数学计算实时更新卡路里消耗和行走距离。函数中使用toFixed方法控制小数位数,确保数据显示的规范性。在鸿蒙系统的传感器数据处理中,这种数据计算方式能够与实际的运动传感器数据良好对接。

useEffect钩子实现了时间计算的核心逻辑,通过setInterval创建定时器来更新经过时间。定时器的创建和清理逻辑确保了组件卸载时不会出现内存泄漏问题。在鸿蒙系统的后台任务管理中,这种定时器处理方式能够与系统的省电机制良好配合,避免不必要的资源消耗。

UI布局采用ScrollView作为根容器,确保内容在不同屏幕尺寸设备上的可滚动性。主计数区域通过TouchableOpacity组件实现步数增加功能,用户点击即可增加步数,这种直观的交互设计符合鸿蒙系统的用户界面规范。控制按钮区域包含开始/暂停和重置按钮,通过条件渲染显示不同的图标和文本,提供清晰的操作反馈。

运动统计信息区域通过网格布局展示距离、消耗和时长三个核心数据,每个数据项包含图标、标签和数值,这种信息架构在鸿蒙系统的健康数据展示中非常常见。进度目标区域通过进度条直观显示用户完成度,宽度计算基于当前步数与目标步数的比例,这种可视化反馈能够激励用户达成运动目标。

从鸿蒙系统适配角度来看,该代码充分利用了React Native的跨平台特性,在鸿蒙设备上能够获得原生级的性能表现。鸿蒙系统的分布式数据管理能力能够与React的状态管理机制良好结合,确保运动数据在手机、手表等不同设备间的同步。组件的生命周期管理与鸿蒙系统的应用管理机制保持一致,能够在应用前后台切换时正确处理定时器和状态更新。


打包

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

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

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

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/3/28 20:14:50

FastReport开源报表生成器:.NET开发者的终极解决方案

还在为项目中的报表生成烦恼吗&#xff1f;FastReport Open Source 是一款专为 .NET 6/.NET Core/.NET Framework 设计的免费开源报表工具&#xff0c;能够快速为你的应用程序生成专业级文档报表。 【免费下载链接】FastReport Free Open Source Reporting tool for .NET6/.NET…

作者头像 李华
网站建设 2026/4/2 13:07:00

EmotiVoice与Azure TTS、Google Cloud Speech对比优劣分析

EmotiVoice 与 Azure TTS、Google Cloud TTS 的深度对比&#xff1a;个性化语音的破局者 在虚拟主播动辄收获百万粉丝、AI角色开始拥有“情绪起伏”的今天&#xff0c;文本转语音&#xff08;TTS&#xff09;早已不再是简单的“朗读机器”。用户不再满足于一个声音平直地念出文…

作者头像 李华
网站建设 2026/3/29 19:11:39

25、定制 gvim 的滚动条、菜单和工具栏

定制 gvim 的滚动条、菜单和工具栏 1. 滚动条定制 gvim 通常会有一个右侧滚动条。大写的 L 和 R 选项可让 gvim 仅在存在垂直分割窗口时显示左侧或右侧滚动条。水平滚动条则通过 guioptions 选项中是否包含 b 来控制。 值得一提的是,你可以同时滚动左右两侧的滚动条…

作者头像 李华
网站建设 2026/3/27 10:11:04

Time-Series-Library时序模型训练效率优化:早停策略实战指南

Time-Series-Library时序模型训练效率优化&#xff1a;早停策略实战指南 【免费下载链接】Time-Series-Library A Library for Advanced Deep Time Series Models. 项目地址: https://gitcode.com/GitHub_Trending/ti/Time-Series-Library 你是否遇到过模型训练时表现完…

作者头像 李华
网站建设 2026/3/15 13:44:32

5分钟快速上手:unrpa工具完整使用指南与技巧

5分钟快速上手&#xff1a;unrpa工具完整使用指南与技巧 【免费下载链接】unrpa A program to extract files from the RPA archive format. 项目地址: https://gitcode.com/gh_mirrors/un/unrpa unrpa是一款专门用于提取RPA档案格式文件的终极解决方案&#xff0c;特别…

作者头像 李华