React Native鸿蒙跨平台实现饮水记录应用技术解读,实现饮水进度计算、快速补水记录、补水场景选择、操作反馈
本文介绍了一款基于React Native开发的健康管理类轻量应用——饮水记录应用,采用清新视觉风格和卡片式布局设计。应用聚焦饮水目标监控、补水记录等核心功能,通过全局主题管理、动态样式绑定和轻量状态管理实现高效开发。文章重点分析了该应用的跨端适配优势:使用原生基础组件和Base64图标确保跨平台兼容性,模块化设计使鸿蒙适配仅需轻量调整。作为健康类应用的典型案例,该实现展示了React Nativ
App 组件采用了现代 React 函数组件架构,结合 useState Hook 实现了精细化的状态管理。组件通过两个核心状态变量控制应用状态:selectedCategory 管理当前选中的饮水场景分类,consumed 存储当前已饮水量。这种状态分离设计使得组件逻辑清晰,易于维护和扩展。
在状态更新方面,组件实现了 onCategoryPress、addWater 和 onRecordAction 三个核心函数,分别处理分类选择、添加饮水记录和操作提示功能。特别是 addWater 函数,不仅更新了已饮水量状态,还通过 Alert.alert 提供了操作反馈,增强了用户体验。
动态样式
该组件的一个显著技术特点是实现了基于 PALETTE 对象的主题系统。通过从 PALETTE 中获取颜色值,组件可以动态调整界面颜色,实现了主题的可配置性。这种设计在跨端开发中尤为重要,因为不同平台可能有不同的设计规范和用户偏好。
组件通过创建多个样式对象(如 containerStyle、titleStyle、cardStyle 等),将主题颜色应用到不同的 UI 元素上。特别是进度条的颜色,会根据饮水进度动态变化:当进度达到 100% 时,使用成功颜色;否则使用强调色。这种动态样式设计增强了用户的视觉反馈,提升了用户体验。
布局
组件采用了卡片式布局设计,通过 SafeAreaView 和 ScrollView 确保在不同设备上的显示兼容性和良好的滚动体验。布局结构清晰,主要分为以下几个部分:
- 头部区域:显示应用标题和副标题,采用垂直排列,突出主题。
- 今日概览卡片:展示目标饮水量、已饮水量和进度,包含进度条和快捷添加按钮。
- 补水场景卡片:通过网格布局展示不同的饮水场景分类,用户可以点击选择。
视觉设计上,组件使用了柔和的绿色系作为主色调,营造出健康、清新的氛围。同时,通过不同的颜色和样式区分不同的功能区域和状态,如使用不同颜色的快捷按钮表示不同的饮水量,使用不同颜色的进度条表示不同的饮水进度。
交互
组件实现了丰富的交互功能,包括分类选择、添加饮水记录和操作提示等。这些交互设计遵循了移动应用的设计规范,提供了清晰的视觉反馈和操作引导。
当用户点击补水场景分类时,通过 onCategoryPress 函数更新选中状态,并通过 Alert.alert 提供操作反馈。当用户点击快捷添加按钮时,通过 addWater 函数更新已饮水量,并通过 Alert.alert 提供操作反馈。这些交互反馈增强了用户的操作信心,提升了用户体验。
进度条的设计是一个重要的用户体验亮点,它直观地显示了用户的饮水进度,并且通过颜色变化提供了视觉反馈。当用户的饮水量达到或超过目标时,进度条会变为成功颜色,同时已饮水量的数字也会变为成功颜色,给用户一种成就感。
在 React Native 与鸿蒙系统跨端开发中,该组件展现了多项兼容性设计:
-
组件选择:使用了
SafeAreaView、ScrollView、TouchableOpacity等基础组件,这些组件在 React Native 和鸿蒙系统中都有对应的实现。 -
样式适配:通过
StyleSheet定义样式,避免了直接内联样式可能带来的性能问题,同时确保了在不同平台上的一致表现。 -
主题系统:实现了基于
PALETTE对象的主题系统,使得主题可以在不同平台上轻松适配。 -
交互反馈:使用
Alert.alert提供操作反馈,这是一个跨平台的 API,在 React Native 和鸿蒙系统中都能正常工作。 -
图片资源:采用
uri方式加载 Base64 编码的图标,这种方式在跨平台开发中更为灵活,避免了不同平台资源管理的差异。 -
布局系统:使用了 Flexbox 布局系统,这是 React Native 和鸿蒙系统都支持的布局方式,确保了在不同平台上的一致布局效果。
-
状态管理优化:使用了
useStateHook 进行状态管理,对于这种中等复杂度的组件,避免了引入 Redux 等重型状态管理库的必要性。 -
样式复用:通过创建样式对象(如
containerStyle、titleStyle等),避免了在每次渲染时重新创建样式对象,提高了渲染性能。 -
计算缓存:在组件渲染前计算进度值(
progress),避免了在渲染过程中进行重复计算,提高了渲染性能。 -
条件渲染:通过条件样式(如
consumed >= target ? t.success : t.warn)实现了不同状态的视觉区分,避免了使用条件渲染可能带来的额外渲染开销。 -
组件结构:将 UI 拆分为多个逻辑块,如头部、今日概览卡片、补水场景卡片等,提高了组件的可维护性和可测试性。
在将该组件适配到鸿蒙系统时,需要注意以下几点:
-
组件映射:将 React Native 的
SafeAreaView、ScrollView、TouchableOpacity等组件映射到鸿蒙系统的对应组件。例如,SafeAreaView可以映射到鸿蒙的SafeArea组件,ScrollView可以映射到ListContainer。 -
样式转换:将
StyleSheet中的样式转换为鸿蒙系统支持的样式格式。例如,React Native 的flexDirection: 'row'对应鸿蒙的flexDirection: FlexDirection.Row。 -
主题系统:确保
PALETTE对象在鸿蒙系统中也能正常工作,或者根据鸿蒙系统的设计规范调整主题颜色。 -
API 适配:确保
Alert.alert等 API 在鸿蒙系统中有对应的实现。例如,可以使用鸿蒙的promptAction或自定义弹窗组件。 -
状态管理:鸿蒙系统的状态管理机制与 React Native 有所不同,需要进行适当的调整。例如,可以使用鸿蒙的
@State装饰器替代useStateHook。 -
性能优化:根据鸿蒙系统的特性,进行针对性的性能优化,确保组件在鸿蒙设备上运行流畅。例如,合理使用鸿蒙的缓存机制和渲染优化策略。
该饮水记录应用展示了一个功能完整、设计优雅的 React Native 应用实现,涵盖了状态管理、主题系统、布局设计、交互处理等多个方面的技术点。通过合理的组件架构和状态管理,以及对跨端兼容性的考虑,该应用不仅在 React Native 环境下运行良好,也为后续的鸿蒙系统适配奠定了基础。
本次实现的饮水记录应用是一款健康管理类轻量应用,聚焦每日饮水目标监控、快速补水记录、补水场景分类、饮水里程碑、历史记录管理五大核心能力,采用薄荷青与活力绿的清新视觉风格,通过卡片式布局、动态进度条、场景化网格、列表式记录等健康类应用高频设计范式,打造贴合移动端的轻量化交互体验。
该应用基于React Native纯原生基础组件开发,无第三方依赖,通过全局主题色统一管理、动态样式绑定、轻量状态管理实现视图与数据的联动,全程遵循Flex弹性布局完成多维度自适应设计,同时严格契合React Native鸿蒙跨端友好开发原则——Base64图标资源跨端复用、纯JSX/TS业务逻辑无平台依赖、原生基础组件可无缝桥接,鸿蒙端适配仅需轻量样式微调,核心逻辑完全复用。
作为健康管理类轻量应用的典型案例,该实现充分体现了React Native在小而美应用开发中的高效性,同时为健康管理、生活记录、轻量统计类应用的鸿蒙跨端开发提供了可复用的技术方案。以下从整体架构与设计原则、核心技术实现、跨端友好开发细节、鸿蒙端实操适配要点四个维度,深度解读该代码的技术设计与跨端适配逻辑,突出健康类应用的开发特色与鸿蒙跨端的低成本落地思路。
健康类:
健康管理类轻量应用的核心设计诉求是视觉清新、操作轻量化、数据展示直观、交互反馈及时,同时需保证跨端适配时修改范围高度收敛。本次饮水记录应用遵循轻量组件、数据驱动、主题统一、跨端通用的设计原则,将应用整体封装为单个函数式组件,按功能维度拆分为头部标题区、今日概览卡、补水场景卡、里程碑卡、记录列表卡、底部版权区六大模块,所有模块均采用卡片式设计风格,通过ScrollView实现纵向滚动适配,结合useState实现轻量状态管理,全程无复杂的组件嵌套与数据共享,所有视图渲染、样式设计、业务逻辑均收敛在组件内部。
这种设计方式既契合健康类应用的轻量化交互特性——用户可快速完成补水记录、场景选择、进度查看等核心操作,无冗余流程,又严格遵循React Native鸿蒙跨端规范:单组件高内聚设计让跨端修改范围高度收敛,无跨组件的逻辑依赖,桥接层解析与映射效率更高;同时卡片式模块化布局让鸿蒙端适配可按模块独立微调,不影响整体应用的展示与交互效果,保证跨端落地的高效率与高还原度。
基础设计:
- 极简依赖选型:仅引入React核心库、
useState轻量Hook与RN原生基础组件(SafeAreaView/View/Text/TouchableOpacity/ScrollView/Image),以及跨端通用的Dimensions/AlertAPI,无任何第三方UI库、状态管理库或端侧特有依赖。这些基础组件与API均已在华为开源的react-native-harmony桥接层中实现与ArkUI的一一映射,无需开发自定义桥接模块即可完成基础适配,从依赖层面规避跨端成本。 - 全局主题色统一管理:通过
PALETTE常量对象实现主题色全局统一封装,将背景色、卡片色、主色、辅助色、文字色、状态色(成功/警告/危险)、浅灰色等所有视觉颜色集中管理,采用语义化命名(如bg/primary/textMain/success)替代硬编码颜色值。该设计不仅让健康类应用的视觉风格高度统一(契合薄荷青与活力绿的清新定位),更从根本上解决了跨端样式适配的核心痛点——鸿蒙端若需贴合系统主题或调整视觉风格,仅需修改PALETTE中的颜色值,无需逐行修改组件样式,实现一处修改,全局生效。 - Base64图标资源全局复用:将应用中所有场景、记录的通用图标封装为全局
ICON_BASE64常量,以Base64格式替代传统本地图片资源。RN与ArkUI的Image组件均原生支持Base64格式的uri加载,且无需在各端分别配置资源目录与路径,实现一次编码,多端复用,彻底规避了健康类应用中高频小图标跨端资源适配的繁琐工作,同时通过Image组件的tintColor属性实现图标颜色的动态修改,无需为不同颜色场景制作多张图标,进一步降低资源成本。 - 轻量状态管理:仅通过
selectedCategory(选中的补水场景)、consumed(今日已饮水量)两个核心状态覆盖所有交互需求,无复杂的状态流转与数据持久化(健康类轻量应用的基础版可优先实现轻量化交互,持久化可后续通过跨端存储API扩展)。轻量状态管理让跨端时的状态逻辑完全复用,无需适配复杂的状态管理库,保证鸿蒙端的渲染与交互性能。 - 设备自适应布局:通过
Dimensions.get('window').width获取设备屏幕宽度,动态计算补水场景网格项的宽度,实现不同尺寸设备的网格等分布局,无挤压或留白;同时全程采用Flex弹性布局,通过flex/justifyContent/alignItems等核心属性实现所有模块的自适应,拒绝硬编码像素值,保证iOS/Android/鸿蒙各端在不同设备上的展示效果一致。
全局主题常量:
PALETTE按视觉用途将颜色分为7大类,每类颜色均采用语义化命名,完全贴合健康类应用的视觉设计需求:
- 基础色:
bg(页面背景)、card(卡片背景)——采用浅青与白色搭配,打造清新的健康类视觉风格; - 品牌色:
primary(主色)、accent(辅助色)——薄荷青与活力绿的核心搭配,契合应用的饮水健康定位; - 文字色:
textMain(主文字)、textSub(副文字)——深灰与中灰搭配,保证文字可读性,符合移动端视觉规范; - 状态色:
success(成功)、warn(警告)、danger(危险)——绿、黄、红三色,分别对应饮水达标、未达标、超限,实现数据状态与视觉的直观联动; - 辅助色:
muted(浅灰)——用于进度条背景、边框等,保证视觉层次的柔和过渡。
这种语义化的全局主题管理,让跨端样式微调效率提升至极致——鸿蒙端若需贴合系统的“鸿蒙绿”主题,仅需将primary/accent的颜色值修改为鸿蒙系统主色调,即可实现全局品牌色的统一替换,无需逐行修改组件中的颜色样式;若需适配鸿蒙端的深色模式,仅需新增一套深色主题常量,通过桥接层监听鸿蒙端的主题切换事件,实现主题色的动态切换,核心样式与布局逻辑无需任何修改。
动态样式对象:
为避免在JSX中频繁书写style={{ color: t.primary, backgroundColor: t.bg }}的冗余代码,同时保证主题色与基础样式的无缝融合,本次实现通过动态样式对象将基础样式(由StyleSheet.create创建)与主题色(PALETTE)进行融合,生成最终的组件样式,如containerStyle = { ...styles.container, backgroundColor: t.bg }、bigNumberConsumedStyle = { ...styles.bigNumber, color: consumed >= target ? t.success : t.warn }。
该实现方式的核心优势:
- 样式复用率提升:基础样式(如布局、尺寸、字体大小、圆角)由
StyleSheet.create统一管理,保证跨端通用的布局逻辑不变;主题色(如背景色、文字色、边框色)通过动态融合绑定,实现视觉风格的灵活调整; - 数据驱动样式动态变化:通过条件判断实现数据与样式的联动,如已饮水量达标时(
consumed >= target),已饮数字颜色变为成功绿(t.success),未达标时为警告黄(t.warn);进度条完成100%时,进度条颜色变为成功绿,未完成时为辅助绿(t.accent),让健康类应用的数据状态直观反馈为视觉样式,提升用户体验; - 跨端样式逻辑复用:动态样式对象的创建逻辑为纯JS实现,无任何端侧依赖,RN与ArkUI均支持通过对象展开实现样式融合,通过条件判断实现样式动态绑定,跨端时可直接复用该逻辑,仅需微调基础样式的部分属性即可。
条件样式绑定:
健康类应用的操作反馈及时性是提升用户体验的关键,本次实现通过条件样式绑定为补水场景的选中态、记录列表的按钮色实现了交互状态与视觉反馈的联动,且所有绑定逻辑均为跨端通用实现:
- 补水场景网格项:当场景被选中时(
selectedCategory === '早餐'),网格项的边框色变为主色(t.primary),背景色变为浅主色(#f0fdfa),未选中时为默认浅灰边框与白色背景,让用户清晰感知当前选中的场景; - 记录列表操作按钮:按钮的边框色与文字色根据记录的场景类型动态绑定(如早餐记录为橙色、午餐记录为绿色),与补水场景的图标颜色形成视觉呼应,保证健康类应用的视觉一致性;
- 进度条与数字颜色:根据饮水进度与达标状态动态切换颜色,实现数据状态的直观视觉反馈,符合用户对健康数据的感知习惯。
所有条件样式绑定均通过简单的三元表达式实现,无复杂的样式计算,RN与ArkUI均原生支持该种样式绑定方式,跨端时可直接复用,无需修改绑定逻辑。
核心业务:
饮水记录应用的核心业务逻辑围绕饮水数据的更新与展示展开,包括饮水进度计算、快速补水记录、补水场景选择、操作反馈四大模块,所有业务逻辑均为纯JS实现,无任何端侧依赖,跨端时可直接复用,若需扩展持久化、数据同步等能力,仅需对接跨端通用的存储/网络API,核心业务逻辑无需修改。
饮水进度计算:
通过已饮水量(consumed)与目标饮水量(target)计算饮水进度(progress),核心计算逻辑为Math.min(100, Math.round((consumed / target) * 100)),通过Math.min(100)限制进度不超过100%,避免进度条宽度超出容器,保证布局规整。该计算逻辑为纯数学计算,无任何端侧依赖,RN与ArkUI均支持该种数值计算,跨端时可直接复用,且进度值直接驱动进度条的宽度与颜色、已饮数字的颜色,实现数据驱动视图的跨端通用展示。
快速补水记录:
通过addWater函数实现快速补水记录,接收补水毫升数(ml)作为参数,更新已饮水量(consumed),并通过Alert实现操作反馈,告知用户本次补水毫升数与合计饮水量。该函数为纯JS实现,核心逻辑为状态更新与提示反馈,TouchableOpacity的onPress事件绑定补水毫升数(如onPress={() => addWater(100)}),实现轻量化的快速操作,符合健康类应用操作便捷性的设计需求。RN的Alert API已在react-native-harmony桥接层中映射为鸿蒙的AlertDialog.show API,跨端时无需修改Alert的调用逻辑,仅需桥接层轻量调整参数格式,核心反馈内容不变。
补水场景选择:
通过onCategoryPress函数实现补水场景的选择,接收场景名称作为参数,更新selectedCategory选中状态,并通过Alert实现选择反馈。场景化分类是健康类应用的典型设计,将饮水行为与生活场景绑定,让记录更具针对性,而选中态的轻量管理让用户清晰感知当前选择的场景,无冗余的交互流程。该函数的核心逻辑为状态更新与提示反馈,跨端时可直接复用,若需实现“场景与补水记录绑定”的增强功能,仅需在addWater函数中结合selectedCategory状态,核心逻辑无需修改。
记录列表操作:
记录列表模块为样式演示,通过onRecordAction函数实现操作反馈,接收操作标题作为参数,通过Alert提示操作内容。该函数为通用的操作反馈逻辑,若需实现真实的删除、编辑功能,仅需在函数中添加对应的业务逻辑(如删除数组中的记录项、更新状态),核心的事件绑定与反馈逻辑无需修改,且列表项的布局与样式为跨端通用实现,扩展后仍可保证鸿蒙端的适配兼容性。
Flex布局综合实战:
健康类应用的典型布局特征是卡片式模块化、网格式场景化、列表式记录化,本次实现充分发挥RN Flex布局的灵活性,实现了健康类应用的六大高频布局,所有布局均为跨端通用实现,无任何平台特有代码,可直接映射为ArkUI的Flex布局,实现与RN端高度一致的展示效果,同时贴合健康类应用视觉清新、布局规整、操作便捷的设计需求。
真实演示案例代码:
import React, { useState } from 'react';
import { SafeAreaView, View, Text, StyleSheet, TouchableOpacity, ScrollView, Image, Dimensions, Alert } from 'react-native';
const PALETTE = {
bg: '#f6fffb',
card: '#ffffff',
primary: '#0ea5a5',
accent: '#22c55e',
textMain: '#0b1021',
textSub: '#4b5563',
success: '#22c55e',
warn: '#f59e0b',
danger: '#ef4444',
muted: '#e5e7eb'
};
const ICON_BASE64 = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAusB9YpW2XcAAAAASUVORK5CYII=';
const App = () => {
const t = PALETTE;
const [selectedCategory, setSelectedCategory] = useState(null);
const [consumed, setConsumed] = useState(1200);
const target = 2000;
const progress = Math.min(100, Math.round((consumed / target) * 100));
const onCategoryPress = (name) => {
setSelectedCategory(name);
Alert.alert('分类选择', `已选择:${name}`);
};
const addWater = (ml) => {
const next = consumed + ml;
setConsumed(next);
Alert.alert('饮水记录', `已添加 ${ml} ml,合计:${next} ml`);
};
const onRecordAction = (title) => {
Alert.alert('操作提示', title);
};
const containerStyle = { ...styles.container, backgroundColor: t.bg };
const titleStyle = { ...styles.title, color: t.textMain };
const subtitleStyle = { ...styles.subtitle, color: t.textSub };
const cardStyle = { ...styles.card, backgroundColor: t.card };
const labelStyle = { ...styles.label, color: t.textSub };
const bigNumberTargetStyle = { ...styles.bigNumber, color: t.primary };
const bigNumberConsumedStyle = { ...styles.bigNumber, color: consumed >= target ? t.success : t.warn };
const progressBarStyle = { ...styles.progressBar, backgroundColor: t.muted };
const progressInnerStyle = { ...styles.progressInner, width: `${progress}%`, backgroundColor: progress >= 100 ? t.success : t.accent };
const chipStyle = { ...styles.chip, backgroundColor: '#dcfce7' };
const chipTextStyle = { ...styles.chipText, color: '#166534' };
const gridLabelStyle = { ...styles.gridLabel, color: t.textMain };
const actionTextStyle = { ...styles.actionText };
const footerTextStyle = { ...styles.footerText, color: t.textSub };
return (
<SafeAreaView style={containerStyle}>
<ScrollView contentContainerStyle={styles.content}>
<View style={styles.header}>
<Text style={titleStyle}>身体健康状况记录 · 饮水记录</Text>
<Text style={subtitleStyle}>薄荷青与活力绿 · 元素丰富 · 文案简洁</Text>
</View>
<View style={cardStyle}>
<Text style={styles.cardTitle}>今日概览</Text>
<View style={styles.topRow}>
<View style={styles.topCol}>
<Text style={labelStyle}>目标</Text>
<Text style={bigNumberTargetStyle}>{target} ml</Text>
</View>
<View style={styles.topCol}>
<Text style={labelStyle}>已饮</Text>
<Text style={bigNumberConsumedStyle}>{consumed} ml</Text>
</View>
<View style={styles.topCol}>
<Text style={labelStyle}>进度</Text>
<Text style={{ ...styles.value, color: t.textMain }}>{progress}%</Text>
</View>
</View>
<View style={styles.progressWrap}>
<View style={progressBarStyle} />
<View style={progressInnerStyle} />
</View>
<View style={styles.quickRow}>
<TouchableOpacity style={{ ...styles.quickBtn, backgroundColor: t.accent }} onPress={() => addWater(100)}>
<Text style={actionTextStyle}>+100 ml</Text>
</TouchableOpacity>
<TouchableOpacity style={{ ...styles.quickBtn, backgroundColor: t.primary }} onPress={() => addWater(200)}>
<Text style={actionTextStyle}>+200 ml</Text>
</TouchableOpacity>
<TouchableOpacity style={{ ...styles.quickBtn, backgroundColor: t.warn }} onPress={() => addWater(300)}>
<Text style={actionTextStyle}>+300 ml</Text>
</TouchableOpacity>
</View>
<View style={styles.chipsRow}>
<View style={chipStyle}>
<Text style={chipTextStyle}>晨间补水</Text>
</View>
<View style={{ ...styles.chip, backgroundColor: '#e0f2fe' }}>
<Text style={{ ...styles.chipText, color: '#0ea5e9' }}>午后补水</Text>
</View>
<View style={{ ...styles.chip, backgroundColor: '#fef9c3' }}>
<Text style={{ ...styles.chipText, color: '#854d0e' }}>晚间补水</Text>
</View>
</View>
</View>
<View style={cardStyle}>
<Text style={styles.cardTitle}>补水场景</Text>
<View style={styles.grid}>
<TouchableOpacity style={selectedCategory==='早餐' ? { ...styles.gridItem, borderColor: t.primary, backgroundColor: '#f0fdfa' } : styles.gridItem} onPress={() => onCategoryPress('早餐')}>
<Image source={{ uri: ICON_BASE64 }} style={{ ...styles.iconImg, tintColor: '#f59e0b' }} />
<Text style={gridLabelStyle}>早餐</Text>
</TouchableOpacity>
<TouchableOpacity style={selectedCategory==='午餐' ? { ...styles.gridItem, borderColor: t.primary, backgroundColor: '#f0fdfa' } : styles.gridItem} onPress={() => onCategoryPress('午餐')}>
<Image source={{ uri: ICON_BASE64 }} style={{ ...styles.iconImg, tintColor: '#fb7185' }} />
<Text style={gridLabelStyle}>午餐</Text>
</TouchableOpacity>
<TouchableOpacity style={selectedCategory==='晚餐' ? { ...styles.gridItem, borderColor: t.primary, backgroundColor: '#f0fdfa' } : styles.gridItem} onPress={() => onCategoryPress('晚餐')}>
<Image source={{ uri: ICON_BASE64 }} style={{ ...styles.iconImg, tintColor: '#34d399' }} />
<Text style={gridLabelStyle}>晚餐</Text>
</TouchableOpacity>
<TouchableOpacity style={selectedCategory==='运动后' ? { ...styles.gridItem, borderColor: t.primary, backgroundColor: '#f0fdfa' } : styles.gridItem} onPress={() => onCategoryPress('运动后')}>
<Image source={{ uri: ICON_BASE64 }} style={{ ...styles.iconImg, tintColor: '#60a5fa' }} />
<Text style={gridLabelStyle}>运动后</Text>
</TouchableOpacity>
<TouchableOpacity style={selectedCategory==='工作间隙' ? { ...styles.gridItem, borderColor: t.primary, backgroundColor: '#f0fdfa' } : styles.gridItem} onPress={() => onCategoryPress('工作间隙')}>
<Image source={{ uri: ICON_BASE64 }} style={{ ...styles.iconImg, tintColor: '#a78bfa' }} />
<Text style={gridLabelStyle}>工作间隙</Text>
</TouchableOpacity>
<TouchableOpacity style={selectedCategory==='睡前' ? { ...styles.gridItem, borderColor: t.primary, backgroundColor: '#f0fdfa' } : styles.gridItem} onPress={() => onCategoryPress('睡前')}>
<Image source={{ uri: ICON_BASE64 }} style={{ ...styles.iconImg, tintColor: '#fbbf24' }} />
<Text style={gridLabelStyle}>睡前</Text>
</TouchableOpacity>
<TouchableOpacity style={selectedCategory==='茶饮' ? { ...styles.gridItem, borderColor: t.primary, backgroundColor: '#f0fdfa' } : styles.gridItem} onPress={() => onCategoryPress('茶饮')}>
<Image source={{ uri: ICON_BASE64 }} style={{ ...styles.iconImg, tintColor: '#f472b6' }} />
<Text style={gridLabelStyle}>茶饮</Text>
</TouchableOpacity>
<TouchableOpacity style={selectedCategory==='纯水' ? { ...styles.gridItem, borderColor: t.primary, backgroundColor: '#f0fdfa' } : styles.gridItem} onPress={() => onCategoryPress('纯水')}>
<Image source={{ uri: ICON_BASE64 }} style={{ ...styles.iconImg, tintColor: '#06b6d4' }} />
<Text style={gridLabelStyle}>纯水</Text>
</TouchableOpacity>
</View>
</View>
<View style={cardStyle}>
<Text style={styles.cardTitle}>里程碑</Text>
<View style={styles.milestoneRow}>
<View style={{ ...styles.milestoneDot, backgroundColor: t.success }} />
<Text style={{ ...styles.milestoneText, color: t.textMain }}>连续 7 天达标</Text>
</View>
<View style={styles.milestoneRow}>
<View style={{ ...styles.milestoneDot, backgroundColor: t.warn }} />
<Text style={{ ...styles.milestoneText, color: t.textMain }}>单日饮水 2500 ml</Text>
</View>
<View style={styles.milestoneRow}>
<View style={{ ...styles.milestoneDot, backgroundColor: t.accent }} />
<Text style={{ ...styles.milestoneText, color: t.textMain }}>晨起一杯水 30 天</Text>
</View>
</View>
<View style={cardStyle}>
<Text style={styles.cardTitle}>记录列表(样式演示)</Text>
<View style={styles.recordRow}>
<View style={styles.recordLeft}>
<Image source={{ uri: ICON_BASE64 }} style={{ ...styles.recordIcon, tintColor: '#0ea5a5' }} />
<View style={styles.recordTextWrap}>
<Text style={{ ...styles.recordTitle, color: t.textMain }}>早餐 · 300 ml</Text>
<Text style={{ ...styles.recordSub, color: t.textSub }}>2026-01-03 08:10</Text>
</View>
</View>
<View style={styles.recordRight}>
<TouchableOpacity style={{ ...styles.recordBtn, borderColor: '#0ea5a5' }} onPress={() => onRecordAction('删除:早餐 300 ml')}>
<Text style={{ ...styles.recordBtnText, color: '#0ea5a5' }}>删除</Text>
</TouchableOpacity>
</View>
</View>
<View style={styles.recordRow}>
<View style={styles.recordLeft}>
<Image source={{ uri: ICON_BASE64 }} style={{ ...styles.recordIcon, tintColor: '#22c55e' }} />
<View style={styles.recordTextWrap}>
<Text style={{ ...styles.recordTitle, color: t.textMain }}>午餐 · 400 ml</Text>
<Text style={{ ...styles.recordSub, color: t.textSub }}>2026-01-03 12:35</Text>
</View>
</View>
<View style={styles.recordRight}>
<TouchableOpacity style={{ ...styles.recordBtn, borderColor: '#22c55e' }} onPress={() => onRecordAction('删除:午餐 400 ml')}>
<Text style={{ ...styles.recordBtnText, color: '#22c55e' }}>删除</Text>
</TouchableOpacity>
</View>
</View>
<View style={styles.recordRow}>
<View style={styles.recordLeft}>
<Image source={{ uri: ICON_BASE64 }} style={{ ...styles.recordIcon, tintColor: '#f59e0b' }} />
<View style={styles.recordTextWrap}>
<Text style={{ ...styles.recordTitle, color: t.textMain }}>晚餐 · 350 ml</Text>
<Text style={{ ...styles.recordSub, color: t.textSub }}>2026-01-03 19:20</Text>
</View>
</View>
<View style={styles.recordRight}>
<TouchableOpacity style={{ ...styles.recordBtn, borderColor: '#f59e0b' }} onPress={() => onRecordAction('删除:晚餐 350 ml')}>
<Text style={{ ...styles.recordBtnText, color: '#f59e0b' }}>删除</Text>
</TouchableOpacity>
</View>
</View>
<View style={styles.recordRow}>
<View style={styles.recordLeft}>
<Image source={{ uri: ICON_BASE64 }} style={{ ...styles.recordIcon, tintColor: '#06b6d4' }} />
<View style={styles.recordTextWrap}>
<Text style={{ ...styles.recordTitle, color: t.textMain }}>运动后 · 300 ml</Text>
<Text style={{ ...styles.recordSub, color: t.textSub }}>2026-01-03 21:05</Text>
</View>
</View>
<View style={styles.recordRight}>
<TouchableOpacity style={{ ...styles.recordBtn, borderColor: '#06b6d4' }} onPress={() => onRecordAction('删除:运动后 300 ml')}>
<Text style={{ ...styles.recordBtnText, color: '#06b6d4' }}>删除</Text>
</TouchableOpacity>
</View>
</View>
</View>
<View style={styles.footer}>
<Text style={footerTextStyle}>© 饮水记录 · 薄荷活力风格</Text>
</View>
</ScrollView>
</SafeAreaView>
);
};
const { width } = Dimensions.get('window');
const styles = StyleSheet.create({
container: { flex: 1 },
content: { padding: 16 },
header: { paddingVertical: 16, alignItems: 'center' },
title: { fontSize: 26, fontWeight: '800' },
subtitle: { fontSize: 13, marginTop: 6 },
card: { borderRadius: 16, padding: 16, marginBottom: 14, shadowColor: '#000', shadowOpacity: 0.06, shadowRadius: 8, shadowOffset: { width: 0, height: 4 } },
cardTitle: { fontSize: 18, fontWeight: '700', marginBottom: 10 },
topRow: { flexDirection: 'row', justifyContent: 'space-between' },
topCol: { flex: 1, marginRight: 10 },
label: { fontSize: 12 },
value: { fontSize: 14, fontWeight: '700', marginTop: 4 },
bigNumber: { fontSize: 20, fontWeight: '800', marginTop: 6 },
progressWrap: { height: 10, borderRadius: 8, marginTop: 12, position: 'relative', overflow: 'hidden' },
progressBar: { position: 'absolute', top: 0, left: 0, right: 0, bottom: 0 },
progressInner: { position: 'absolute', top: 0, left: 0, bottom: 0 },
quickRow: { flexDirection: 'row', justifyContent: 'space-between', marginTop: 10 },
quickBtn: { flex: 1, borderRadius: 12, paddingVertical: 10, alignItems: 'center', marginRight: 10 },
actionText: { color: '#ffffff', fontSize: 14, fontWeight: '600' },
chipsRow: { flexDirection: 'row', marginTop: 10 },
chip: { paddingHorizontal: 10, paddingVertical: 6, borderRadius: 999, marginRight: 8 },
chipText: { fontSize: 12, fontWeight: '600' },
grid: { flexDirection: 'row', flexWrap: 'wrap', justifyContent: 'space-between' },
gridItem: { width: (width - 16 * 2 - 12 * 3) / 4, borderWidth: 1, borderColor: '#e2e8f0', borderRadius: 14, paddingVertical: 14, alignItems: 'center', marginBottom: 12, backgroundColor: '#ffffff' },
iconImg: { width: 28, height: 28, borderRadius: 14, marginBottom: 8 },
gridLabel: { fontSize: 12, fontWeight: '600' },
milestoneRow: { flexDirection: 'row', alignItems: 'center', marginTop: 8 },
milestoneDot: { width: 8, height: 8, borderRadius: 4, marginRight: 8 },
milestoneText: { fontSize: 12 },
recordRow: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', paddingVertical: 10, borderBottomWidth: 1, borderBottomColor: '#f1f5f9' },
recordLeft: { flexDirection: 'row', alignItems: 'center' },
recordIcon: { width: 30, height: 30, borderRadius: 15 },
recordTextWrap: { marginLeft: 10 },
recordTitle: { fontSize: 14, fontWeight: '700' },
recordSub: { fontSize: 12, marginTop: 2 },
recordRight: { alignItems: 'flex-end' },
recordBtn: { borderWidth: 1, borderRadius: 999, paddingHorizontal: 10, paddingVertical: 4 },
recordBtnText: { fontSize: 12, fontWeight: '600' },
footer: { paddingVertical: 14, alignItems: 'center' },
footerText: { fontSize: 12 }
});
export default App;

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

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

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

本文介绍了一款基于React Native开发的健康管理类轻量应用——饮水记录应用,采用清新视觉风格和卡片式布局设计。应用聚焦饮水目标监控、补水记录等核心功能,通过全局主题管理、动态样式绑定和轻量状态管理实现高效开发。文章重点分析了该应用的跨端适配优势:使用原生基础组件和Base64图标确保跨平台兼容性,模块化设计使鸿蒙适配仅需轻量调整。作为健康类应用的典型案例,该实现展示了React Native在小而美应用开发中的高效性,为跨端开发提供了可复用的技术方案,同时详细阐述了从架构设计到具体实现的完整技术路线。
欢迎大家加入开源鸿蒙跨平台开发者社区,一起共建开源鸿蒙跨平台生态。
更多推荐




所有评论(0)