EduLoadingAnimation 组件采用了现代 React 函数组件架构,结合 useState Hook 实现了精细化的状态管理。组件通过多个状态变量控制不同的 UI 状态,包括当前选中的标签、页码、详情页显示状态、选中的项目、滚动位置、点赞记录和筛选标签等。这种状态分离设计使得组件逻辑清晰,易于维护和扩展。

在类型定义上,使用了 TypeScript 的 Item 接口明确数据结构,包含 idtitlesummaryimage 四个属性。这种类型定义在跨端开发中尤为重要,确保了在不同平台上的数据结构一致性,减少了类型错误的可能性。

图标系统

组件使用了 Base64 编码的图标资源,通过 ICONS 对象集中管理。这种资源管理方式在跨端开发中具有明显优势:

  1. 减少网络请求:Base64 编码的图标直接嵌入代码中,无需额外的网络请求,提高了应用加载速度。
  2. 跨平台兼容:避免了不同平台对资源文件格式和路径的差异,确保了图标在所有平台上的一致显示。
  3. 代码简洁:通过对象集中管理图标,使得代码更加模块化,易于维护。

视觉设计

应用采用了现代化的移动应用布局设计,主要包含以下几个部分:

  1. 头部区域:显示标题和操作按钮,支持根据滚动位置动态调整样式,如缩放效果和阴影透明度。
  2. 趋势卡片:通过水平 ScrollView 展示热门内容。
  3. 动效方案:根据当前页码显示不同的加载动画方案卡片,每张卡片包含标题、摘要、图标和操作按钮。
  4. 分页控制:实现页面切换功能。
  5. 标签筛选:通过可点击的标签实现内容筛选。

视觉设计上,使用了简洁明了的风格,通过不同的图标和样式区分不同的内容类型。动态样式(如 selectedTag === '动画' && styles.tagActive)实现了选中状态的视觉反馈,提升了用户体验。

用户体验

组件实现了丰富的交互功能,包括标签切换、页面切换、详情查看、点赞和标签筛选等。这些交互功能的实现遵循了 React 的最佳实践,通过状态更新驱动 UI 变化,确保了交互的一致性和可靠性。

特别是滚动位置的记录和头部样式的动态调整,为用户提供了流畅的视觉体验。点击反馈通过 TouchableOpacity 实现,为用户提供了明确的操作反馈。


在 React Native 与鸿蒙系统跨端开发中,该组件展现了多项兼容性设计:

  1. 基础组件选择:使用了 SafeAreaViewScrollViewTouchableOpacity 等基础组件,这些组件在 React Native 和鸿蒙系统中都有对应的实现。

  2. 样式管理:通过 StyleSheet.create 管理样式,确保了在不同平台上的一致表现。

  3. 资源管理:使用 Base64 编码的图标资源,避免了不同平台对资源文件格式和路径的差异。

  4. 状态管理:使用 useState Hook 进行状态管理,在鸿蒙系统中可以通过相应的状态管理机制(如 @State 装饰器)实现类似功能。

  5. 类型定义:使用 TypeScript 类型定义,确保了在不同平台上的数据结构一致性。

  6. 布局系统:使用了 Flexbox 布局系统,这是 React Native 和鸿蒙系统都支持的布局方式,确保了在不同平台上的一致布局效果。

该教育百科加载动画应用展示了一个功能完整、设计优雅的 React Native 应用实现,涵盖了状态管理、资源管理、布局设计、交互处理等多个方面的技术点。通过合理的组件架构和状态管理,以及对跨端兼容性的考虑,该应用不仅在 React Native 环境下运行良好,也为后续的鸿蒙系统适配奠定了基础。


本次实现的教育百科·加载动画专题应用,是面向开发者与设计从业者的技术向轻内容产品,聚焦移动端加载动效领域的骨架屏、渐变过渡、旋转指示器等核心实现方案,覆盖技术类轻内容应用高频的标签分类、分页浏览、详情穿透、点赞收藏、标签筛选、横向推荐、滚动动效、动效演示预览等核心功能。

应用基于React Native原生基础组件开发,引入TypeScript实现轻量类型约束,通过useState完成全量状态管理,全程遵循无第三方库依赖、样式全局归一、逻辑与视图解耦、跨端无平台耦合的开发原则,依托react-native-harmony桥接层的成熟组件与API映射能力,可实现鸿蒙端零核心逻辑修改、轻量样式微调的低成本适配,核心状态管理、布局逻辑、交互事件100%复用,为技术教程、设计指南、垂直领域技术百科类应用的React Native鸿蒙跨端开发,提供了可直接落地的技术参考与实操方案。

作为技术向垂直领域轻内容应用的典型案例,该实现充分发挥React Native快速开发、跨端兼容、原生体验贴近的技术优势,针对技术类内容专业性、碎片化、实操性的核心诉求做了针对性设计。以下从整体架构与跨端友好设计内核、核心技术实现细节、鸿蒙跨端适配核心要点、技术类轻内容应用跨端开发最佳实践四个维度,深度解读代码的技术设计与鸿蒙跨端落地逻辑,突出技术类轻内容应用的开发特色与React Native鸿蒙跨端的低成本实现思路。

教育百科加载动画:

教育百科加载动画应用作为典型的技术向垂直领域轻内容产品,核心设计诉求是视觉风格贴合技术产品的简约专业属性、内容模块按技术维度碎片化、操作路径极简、布局自适应多设备屏幕,同时需保证跨端适配时修改范围高度收敛,核心技术内容展示与交互体验多端一致。本次实现遵循单组件高内聚、极致极简技术栈、主题风格技术化、状态轻量管理、样式与逻辑分离的核心设计原则,将应用整体封装为单个函数式组件,按内容展示逻辑拆分为动态缩放头部导航区、横向技术推荐栏、动效方案列表区、分页控件区、技术标签筛选区、动效详情区、广告位、底部功能导航区八大模块,所有模块均采用卡片式流式布局实现视觉与功能的隔离,通过ScrollView实现纵向/横向滚动适配不同设备,结合useState实现轻量状态管理,覆盖所有标签切换、分页加载、详情跳转、点赞、筛选标签等交互需求,全程无复杂的组件嵌套与跨组件数据共享,所有视图渲染、样式设计、业务逻辑均收敛在组件内部。

这种设计方式既契合技术类轻内容应用视觉简约专业、内容按技术维度碎片化、操作路径直接的品类特性——开发者可快速完成技术标签切换、动效方案浏览、详情查看、点赞收藏预览等核心操作,无冗余交互流程,又严格遵循React Native鸿蒙跨端开发规范:单组件高内聚设计让跨端修改范围高度收敛,无跨组件逻辑依赖,桥接层解析与映射效率更高;卡片式流式布局让鸿蒙端适配可按模块独立微调,不影响整体应用的展示与交互效果;纯原生基础组件与通用API的使用,让桥接层可直接完成组件与API的映射,无需开发自定义桥接模块;TypeScript类型约束仅在开发阶段做类型校验,编译后为纯JS代码,不影响跨端运行。


本次实现从技术栈选型、资源管理、主题风格、状态管理、布局体系、类型约束六个核心维度做了跨端友好设计,从源头规避跨端适配的复杂问题,让鸿蒙端落地仅需轻量微调,核心逻辑完全复用,这也是技术类轻内容应用React Native鸿蒙跨端开发的核心基础:

  1. 极致极简技术栈:仅引入React核心库、useState轻量Hook、RN原生基础组件(SafeAreaView/ScrollView/TouchableOpacity等)、跨端通用的Dimensions/Alert API,以及TypeScript核心类型约束,无任何第三方UI/动效/状态管理库依赖。这些基础组件与API均已在华为开源的react-native-harmony桥接层中实现与ArkUI的一一映射,核心交互事件(onPress/onScroll)、布局属性(Flex相关)、样式属性均无缝兼容,无需开发自定义桥接模块即可完成基础适配,从依赖层面将鸿蒙适配成本降至最低。
  2. Base64图标语义化集中管理:将应用中所有功能图标(星星、爱心、播放、用户等)封装为全局ICONS对象,以Base64格式按技术功能/交互语义命名(star/heart/play)统一管理,替代传统本地图片资源。RN与ArkUI的Image组件均原生支持Base64格式的uri加载,且无需在各端分别配置资源目录与路径,实现一次编码,多端复用,彻底规避了技术类应用中高频功能小图标跨端资源适配的繁琐工作;同时所有图标均通过统一样式做尺寸、圆角、背景色约束,保证视觉一致性,跨端无需调整图标展示逻辑。
  3. 技术化全局主题色归一管理:通过palette常量对象实现主题色全局统一封装,针对技术类应用的简约、专业、科技感视觉属性,选用紫丁香色系(#7c3aed)作为主色,搭配浅灰蓝背景(#f7f8fc)、浅灰边框(#e5e7eb)打造贴合技术产品的视觉风格,同时将页面背景、头部背景、主文字、副文字、卡片背景、广告背景等所有视觉颜色按语义化命名(bg/primary/text/muted)集中管理。该设计不仅让技术类应用的视觉风格高度贴合品类属性,更从根本上解决了跨端样式适配的核心痛点——鸿蒙端若需贴合系统主题或调整视觉风格,仅需修改palette中的颜色值,无需逐行修改组件样式,实现一处修改,全局生效
  4. useState轻量管理全量交互状态:技术类轻内容应用的核心状态围绕标签切换、分页加载、详情跳转、点赞、筛选标签、滚动动效展开,本次实现仅通过7个核心状态(selectedTag/page/showDetail等),覆盖所有交互需求,无复杂的状态流转与数据持久化(技术类轻内容应用的基础版可优先实现轻量化交互,持久化可后续通过跨端存储API扩展)。轻量状态管理让跨端时的状态逻辑完全复用,无需适配复杂的状态管理库,保证鸿蒙端的渲染与交互性能。
  5. Flex弹性布局+动态尺寸计算:全程采用Flex弹性布局,通过flex/justifyContent/alignItems等核心属性实现所有模块的自适应,结合Dimensions.get('window')获取设备宽高,动态计算详情图片、画廊图片等元素的尺寸(width: width - 32/width: (width - 48)/3),拒绝硬编码像素值;同时通过ScrollViewhorizontal属性实现横向技术推荐栏,贴合技术类轻内容应用的横向优质内容推荐需求,且Flex布局与ScrollView的核心属性在鸿蒙端可无缝映射。
  6. TypeScript轻量类型约束:引入TypeScript定义Item数据类型,对动效方案列表数据、选中详情项做类型校验,避免开发阶段的类型错误,提升代码健壮性与可维护性。TypeScript编译后生成的纯JS代码可直接在RN与鸿蒙桥接层中运行,无任何跨端兼容问题,同时为后续多端开发的团队协作提供了类型规范,不增加跨端适配成本。

本次实现针对技术类轻内容应用视觉简约化、内容技术化、交互便捷化、实操性强的核心诉求,基于RN原生基础组件实现了全流程的轻量化交互,新增TypeScript类型约束、滚动联动动效、技术卡片式展示、动效演示预览区、品类化紫丁香主题风格等技术类应用高频设计点,同时扩展了收藏、预览等技术类应用高频交互功能,所有实现均遵循跨端通用原则,核心业务逻辑、样式设计、布局实现均为纯JS/JSX编写,无任何平台特有代码,鸿蒙端可直接复用核心逻辑,仅需轻量样式微调。

全局主题常量palette

palette针对技术类轻内容应用的简约专业属性做了专属的紫丁香色系配色设计,同时按视觉用途做语义化命名,既贴合技术品类特性,又为跨端适配提供了唯一的样式调整入口:

  • 基础色:bg(页面背景,浅灰蓝#f7f8fc)、header(头部背景,纯白)、card(卡片背景,纯白)——浅灰蓝背景打造简约科技的视觉氛围,纯白卡片保证技术内容的可读性,符合技术类应用的视觉定位;
  • 品牌色:primary(主色,紫丁香#7c3aed)——作为核心交互元素(选中标签、搜索按钮、分页按钮、卡片角标)的配色,实现视觉焦点的集中,紫丁香色低饱和度、高辨识度的特性,贴合技术类应用的简约、科技化属性;
  • 文字色:text(主文字,深灰蓝#0f172a)、muted(副文字,中灰#64748b)——深灰蓝与中灰搭配,保证文字可读性,符合移动端视觉规范,主文字用于技术标题,副文字用于动效描述、说明,实现技术内容的视觉分层;
  • 功能色:ad/adText(广告背景/文字,淡紫+深紫)、边框色(浅灰#e5e7eb)——为广告模块、边框配置与主色相呼应的紫调,保证整体视觉风格的统一性,同时实现模块间的视觉区分,且浅灰边框不破坏技术应用的简约感。

这种技术化+语义化的全局主题管理,让跨端样式微调效率提升至极致——鸿蒙端若需贴合系统的“鸿蒙蓝”主题,仅需将primary的颜色值修改为鸿蒙系统主色调,即可实现全局品牌色的统一替换;若需适配鸿蒙端的深色模式,仅需新增一套深色主题常量,通过桥接层监听鸿蒙端的主题切换事件,实现主题色的动态切换,核心样式与布局逻辑无需任何修改。

StyleSheet.create:

本次实现通过StyleSheet.create创建所有基础样式,将布局属性(尺寸、内边距、外边距、Flex相关)、视觉属性(圆角、边框、背景色)、文字属性(字号、字重、颜色) 按模块归一化管理,如card(技术卡片基础样式)、sectionTitle(技术章节标题样式)、actionBtn(操作按钮样式)等,保证同类型组件的样式统一,提升代码复用率,同时针对技术类应用新增了三按钮横向操作栏、技术标签专属样式等个性化设计,且所有基础样式均为跨端通用。

归一化样式设计在技术类轻内容应用中体现的核心优势尤为明显:

  1. 技术内容展示一致性:所有动效方案卡片、横向技术推荐卡片均复用统一的卡片基础样式(圆角12、内边距12),保证碎片化技术内容展示的规整性,提升开发者浏览体验,且基础样式为跨端通用,鸿蒙端可直接复用;
  2. 交互元素统一性:所有操作按钮(点赞、收藏、预览、返回、分页)均复用统一的按钮基础样式(圆角8、浅灰/淡紫背景),所有技术筛选标签、头部分类标签均复用统一的标签样式(圆角16、内边距水平10/垂直6),实现交互元素的视觉统一,让开发者形成操作记忆,提升技术类应用的操作效率;
  3. 跨端样式逻辑复用StyleSheet.create的创建逻辑为RN原生实现,react-native-harmony桥接层已将其映射为鸿蒙的StyleSheet.create,样式属性的命名与使用方式完全一致,跨端时可直接复用所有基础样式,仅需对部分端侧特有样式(如阴影elevation)做轻量微调。
条件样式绑定:

技术类轻内容应用的操作反馈及时性与状态识别清晰度是提升开发者体验的关键,本次实现通过条件样式绑定为头部技术标签、筛选技术标签实现了交互状态与视觉反馈的深度联动,且所有绑定逻辑均为跨端通用实现,鸿蒙端可直接复用:

  1. 头部技术标签选中态:当头部技术标签(动画/骨架/指示器)被选中时(selectedTag === '动画'),文字色变为主色(palette.primary)且字重加粗,未选中时为副文字色(palette.muted),让开发者清晰感知当前选中的技术内容分类,符合技术类应用内容按技术维度分类清晰的需求;
  2. 技术筛选标签选中态:筛选标签(骨架/指示器/渐变)通过chips.includes('骨架')判断选中状态,选中时背景色变为淡主色(#f5f3ff)、文字色变为主色且加粗,未选中时为浅灰背景、副文字色,实现技术筛选条件的视觉清晰反馈,同时支持多标签选中,贴合技术类应用按技术维度筛选内容的需求。

所有条件样式绑定均通过简单的三元表达式与数组包含判断实现,无复杂的样式计算,RN与ArkUI均原生支持该种样式绑定方式,跨端时可直接复用,无需修改绑定逻辑。

动态头部动效:

滚动联动动效是轻内容类应用提升沉浸式浏览体验的核心设计,本次实现基于ScrollViewonScroll事件,实现了头部导航缩放+阴影渐变的滚动联动动效,核心逻辑为滚动距离监听+样式动态计算,纯JS实现,无任何第三方动效库依赖,跨端可直接复用,鸿蒙端仅需保证onScroll事件的正常触发与样式属性的无缝映射。

核心实现逻辑为:通过ScrollViewonScroll事件监听纵向滚动距离scrollY,将scrollY作为参数动态计算头部的scale缩放比例与shadowOpacity阴影透明度——transform: [{ scale: Math.max(0.9, 1 - scrollY / 400) }]实现头部随滚动距离增加从1倍缩放到0.9倍,保证技术动效内容浏览时的视觉聚焦;shadowOpacity: Math.min(0.3, scrollY / 300)实现头部随滚动距离增加从0渐变到0.3的阴影,增强页面的层次感。同时设置scrollEventThrottle={16}保证滚动事件的触发频率与屏幕刷新率一致(60帧/秒),避免动效卡顿,提升体验流畅度。

该动效的核心优势在于:

  1. 纯JS实现,跨端通用:所有动效计算均为原生JS的数学计算,无任何端侧依赖,RN与鸿蒙桥接层均支持transform缩放与shadowOpacity阴影渐变,跨端可直接复用;
  2. 性能优化到位:通过Math.max/Math.min限制缩放比例与阴影透明度的取值范围,避免过度变形,同时scrollEventThrottle保证动效流畅度,无性能损耗;
  3. 体验贴合技术内容浏览:头部轻微缩放与阴影渐变不影响碎片化技术内容的浏览,同时提升了页面的动态感与层次感,契合技术类轻内容应用的沉浸式浏览需求。

  1. 技术标签切换逻辑:通过头部技术标签的onPress事件更新selectedTag状态,实现动画/骨架/指示器标签的切换,结合条件样式绑定实现选中态的视觉反馈,核心逻辑为状态更新与样式联动,纯JS实现,跨端可直接复用;
  2. 分页加载逻辑:通过page状态控制展示items1/items2两组加载动效相关模拟技术数据,点击分页按钮触发nextPage函数实现页码切换,核心逻辑为状态更新与数据切换,纯JS实现,跨端可直接复用,若需实现真实分页,仅需将模拟数据替换为网络请求的分页技术内容,核心切换逻辑不变;
  3. 详情跳转与返回逻辑:点击动效方案卡片触发openDetail函数,同步更新selectedItem为选中的Item对象、showDetailtrue,实现列表到技术详情的视图穿透切换;点击详情页返回按钮触发back函数,重置showDetailfalse,实现详情到列表的返回,核心逻辑为双状态驱动的条件渲染,通过{!showDetail && <列表区>}{showDetail && <详情区>}实现视图无缝切换,无路由依赖,轻量高效,且做了selectedItem非空校验,避免空值渲染错误,跨端可直接复用;
  4. 点赞逻辑:点赞功能通过like函数更新likes对象(以动效方案ID为键,点赞数为值),实现点赞数的累加,核心逻辑为对象式状态管理,利用ES6扩展运算符实现状态不可变更新,纯JS实现,跨端可直接复用,若需实现持久化,仅需将likes同步到跨端存储API即可;
  5. 收藏/预览逻辑:收藏与动效预览功能通过Alert实现操作反馈,核心逻辑为Alert.alert的调用,贴合技术类轻内容应用的基础交互需求,react-native-harmony桥接层已将其映射为鸿蒙的AlertDialog.show,跨端可直接复用,若需实现真实收藏/动效预览,仅需在对应事件中对接跨端存储/动效渲染API即可;
  6. 技术标签筛选逻辑:通过toggleChip函数实现技术筛选标签的添加/删除,判断标签是否在chips数组中,存在则通过filter过滤删除,不存在则通过扩展运算符添加,核心逻辑为数组的不可变增删操作,纯JS实现,跨端可直接复用,若需实现动效方案筛选,仅需根据chips数组过滤列表数据即可;
  7. 搜索与导航反馈逻辑:搜索按钮、底部导航的点击事件均通过Alert实现操作反馈,核心逻辑为Alert.alert的调用,跨端可直接复用,若需实现真实技术内容搜索,仅需在onSearch函数中添加动效方案搜索逻辑即可。

所有核心业务逻辑均围绕useState状态展开,通过状态更新驱动视图渲染,无复杂的逻辑嵌套与端侧依赖,这是技术类轻内容应用React Native鸿蒙跨端开发的核心设计思路,保证了多端核心逻辑的完全复用。


所有RN原生基础组件均通过桥接层无缝映射为ArkUI组件,组件的Props传值、事件绑定、布局逻辑、样式绑定完全复用,无需修改任何核心代码,核心映射关系与技术类轻内容应用的适配要点如下,完全贴合技术类应用内容展示、滚动浏览、简洁交互的核心需求:

RN原生组件 ArkUI映射组件 技术类轻内容应用适配要点
SafeAreaView SafeArea 布局逻辑完全复用,仅需微调内边距贴合鸿蒙端的安全区域规范,保证技术内容不被系统状态栏/导航栏遮挡,提升技术内容浏览的完整性
ScrollView ScrollView 纵向/横向滚动逻辑、onScroll事件、scrollEventThrottle属性无缝映射,头部滚动动效、横向技术推荐栏的滚动体验与RN端完全一致,可通过桥接层开启鸿蒙端特有滑动手势,提升技术内容浏览的流畅性
TouchableOpacity Button+Gesture onPress事件自动映射为onClick事件,点击透明度反馈由桥接层实现,技术卡片、按钮、标签的点击交互无感知适配,保证技术类轻内容应用简洁操作的体验一致性
Image Image Base64技术图标加载逻辑完全复用,source={{ uri: Base64字符串 }}的使用方式与RN端一致,无本地图片,零适配成本,保证技术应用图标的视觉一致性
View/Text View/Text 核心布局与文字展示逻辑完全复用,样式属性(字号、字重、颜色、行高)无缝映射,保证技术内容的文字可读性与布局规整性,内容展示无偏差

所有组件的核心Props均为跨端通用,无需修改,仅需对部分组件的次要Props(如elevation阴影、borderRadius圆角)进行轻量微调,贴合鸿蒙端的组件规范,同时保留技术类应用简约化、专业化的视觉特色。


真实演示案例代码:





import React, { useState } from 'react';
import { SafeAreaView, View, Text, StyleSheet, ScrollView, Dimensions, TouchableOpacity, Alert, Image } from 'react-native';
const { width } = Dimensions.get('window');
const ICONS = {
  star: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAAJUlEQVR42mNgGAWjYGBg+M/AwPD/TxgGf4ZgYGAEw6EIMqFQBQ0AAZbN1bqV5J2QAAAAASUVORK5CYII=',
  heart: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAAL0lEQVR42mP8z/D/PwMDA8MZGBgYGBg2gCEYwQhGgYHhGQwAAwQYkAxQwQwQAEbO4C6kzZL0AAAAAElFTkSuQmCC',
  play: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAK1BMVEUAAP8ZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRl0t6VtAAAADXRSTlMAAAECAwQGBwgJCgsM4sRjWgAAAD5JREFUGNNjYGBgYGLgZGJiYGBgEAVgYGBgYQkGQ2BiYGBgQmBiYGAwYGBgYQAFGhgYGRgYgQkAAwA9fQF5O3QKsgAAAABJRU5ErkJggg==',
  user: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAM1BMVEUAAP8ZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkYf4cYAAAADHRSTlMAAQIDBAUGBwgJCgsM2mC+WwAAAEJJREFUGNNjYGBgYGLgYGAQWBiY2NgYGJjYWBhYGBgECYwYGBgYQYwYGBgYQYgYGBgYgYgQGBgYGBgAAAFx2gH2fCqZHQAAAABJRU5ErkJggg==',
  bell: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAALVBMVEUAAP8ZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRlrYyTnAAAADnRSTlMAAQIDBAUGBwgJCgsMZr2qSgAAAEFJREFUGNNjYGBgYGLgYGAQWBiYmJgYGBgYGBgQmBiYGBgYQYwYGBgYQYgYGBgYgYgQGBgYGBgAAABeWwH8KxS9oQAAAABJRU5ErkJggg==',
  cart: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAMFBMVEUAAP8ZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRk3XKQkAAAAD3RSTlMAAQIDBAUGBwgJCgsMZb+VtwAAAEVJREFUGNNjYGBgYGLgYGAQWBiYmJgYGBgYGBgQmBiYGBgYQYwYGBgYQYgYGBgYgYgQGBgYGBgAAABf8gH9QjXQ8QAAAABJRU5ErkJggg==',
  book: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAL1BMVEUAAP8ZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRllx3McAAAAD3RSTlMAAQIDBAUGBwgJCgsMZrjM1wAAAEVJREFUGNNjYGBgYGLgYGAQWBiYmJgYGBgYGBgQmBiYGBgYQYwYGBgYQYgYGBgYgYgQGBgYGBgAAABf8gH9QjXQ8QAAAABJRU5ErkJggg=='
};
type Item = { id: string; title: string; summary: string; image: string };
export default function EduLoadingAnimation() {
  const [selectedTag, setSelectedTag] = useState('动画');
  const [page, setPage] = useState(1);
  const [showDetail, setShowDetail] = useState(false);
  const [selectedItem, setSelectedItem] = useState<Item | null>(null);
  const [scrollY, setScrollY] = useState(0);
  const [likes, setLikes] = useState<Record<string, number>>({});
  const [chips, setChips] = useState<string[]>(['骨架', '指示器', '渐变']);
  const items1: Item[] = [
    { id: '1', title: '加载骨架屏', summary: '骨架屏样式与布局', image: ICONS.star },
    { id: '2', title: '渐显渐隐', summary: '透明度变化与节奏', image: ICONS.heart },
    { id: '3', title: '旋转指示器', summary: '旋转动画与反馈', image: ICONS.play },
    { id: '4', title: '占位图设计', summary: '图片与文案占位', image: ICONS.user }
  ];
  const items2: Item[] = [
    { id: '5', title: '多色动效', summary: '色彩与层次搭配', image: ICONS.bell },
    { id: '6', title: '交互提示', summary: '点击加载与提示', image: ICONS.cart },
    { id: '7', title: '性能优化', summary: '减少重绘与抖动', image: ICONS.book }
  ];
  const items = page === 1 ? items1 : items2;
  const openDetail = (it: Item) => { setSelectedItem(it); setShowDetail(true); };
  const back = () => setShowDetail(false);
  const onSearch = () => Alert.alert('搜索', '当前标签: ' + selectedTag);
  const nextPage = () => setPage(page === 1 ? 2 : 1);
  const like = (id: string) => setLikes({ ...likes, [id]: (likes[id] || 0) + 1 });
  const toggleChip = (name: string) => {
    if (chips.includes(name)) setChips(chips.filter(c => c !== name));
    else setChips([...chips, name]);
  };
  return (
    <SafeAreaView style={styles.container}>
      <View style={[styles.header, { transform: [{ scale: Math.max(0.9, 1 - scrollY / 400) }], shadowOpacity: Math.min(0.3, scrollY / 300) }]}><Text style={styles.title}>教育百科:加载动画</Text><View style={styles.headerActions}><TouchableOpacity style={styles.searchButton} onPress={onSearch}><Text style={styles.searchText}>搜索</Text></TouchableOpacity><TouchableOpacity style={styles.tagButton} onPress={() => setSelectedTag('动画')}><Text style={[styles.tagText, selectedTag === '动画' && styles.tagActive]}>动画</Text></TouchableOpacity><TouchableOpacity style={styles.tagButton} onPress={() => setSelectedTag('骨架')}><Text style={[styles.tagText, selectedTag === '骨架' && styles.tagActive]}>骨架</Text></TouchableOpacity><TouchableOpacity style={styles.tagButton} onPress={() => setSelectedTag('指示器')}><Text style={[styles.tagText, selectedTag === '指示器' && styles.tagActive]}>指示器</Text></TouchableOpacity></View></View>
      <ScrollView style={styles.content} onScroll={e => setScrollY(e.nativeEvent.contentOffset.y)} scrollEventThrottle={16}>
        <ScrollView horizontal showsHorizontalScrollIndicator={false} style={styles.trending}>
          <View style={styles.trendingCard}><Image source={{ uri: ICONS.star }} style={styles.trendingIcon} /><View style={styles.trendingBody}><Text style={styles.trendingTitle}>骨架屏合集</Text><Text style={styles.trendingSub}>页面占位与层次</Text></View></View>
          <View style={styles.trendingCard}><Image source={{ uri: ICONS.play }} style={styles.trendingIcon} /><View style={styles.trendingBody}><Text style={styles.trendingTitle}>指示器集锦</Text><Text style={styles.trendingSub}>旋转与跳动</Text></View></View>
        </ScrollView>
        {!showDetail && (
          <View>
            <Text style={styles.sectionTitle}>动效方案</Text>
            {items.map(item => (
              <TouchableOpacity key={item.id} style={styles.card} onPress={() => openDetail(item)}>
                <View style={styles.cardHeader}><Image source={{ uri: item.image }} style={styles.cardImage} /><Text style={styles.cardTag}>动效</Text></View>
                <Text style={styles.cardTitle}>{item.title}</Text>
                <Text style={styles.cardSummary}>{item.summary}</Text>
                <View style={styles.cardActions}><TouchableOpacity style={styles.actionBtn} onPress={() => like(item.id)}><Text style={styles.actionText}>点赞 {likes[item.id] || 0}</Text></TouchableOpacity><TouchableOpacity style={styles.actionBtn} onPress={() => Alert.alert('收藏', item.title)}><Text style={styles.actionText}>收藏</Text></TouchableOpacity><TouchableOpacity style={styles.actionBtn} onPress={() => Alert.alert('预览', item.title)}><Text style={styles.actionText}>预览</Text></TouchableOpacity></View>
              </TouchableOpacity>
            ))}
            <View style={styles.pagination}><TouchableOpacity style={styles.pageBtn} onPress={nextPage}><Text style={styles.pageText}>{page === 1 ? '下一页' : '上一页'}</Text></TouchableOpacity><Text style={styles.pageIndicator}>{page}</Text></View>
            <View style={styles.chipsRow}>
              <TouchableOpacity style={[styles.chip, chips.includes('骨架') && styles.chipActive]} onPress={() => toggleChip('骨架')}><Text style={[styles.chipText, chips.includes('骨架') && styles.chipTextActive]}>骨架</Text></TouchableOpacity>
              <TouchableOpacity style={[styles.chip, chips.includes('指示器') && styles.chipActive]} onPress={() => toggleChip('指示器')}><Text style={[styles.chipText, chips.includes('指示器') && styles.chipTextActive]}>指示器</Text></TouchableOpacity>
              <TouchableOpacity style={[styles.chip, chips.includes('渐变') && styles.chipActive]} onPress={() => toggleChip('渐变')}><Text style={[styles.chipText, chips.includes('渐变') && styles.chipTextActive]}>渐变</Text></TouchableOpacity>
            </View>
          </View>
        )}
        {showDetail && selectedItem && (
          <View>
            <View style={styles.detailHeader}><TouchableOpacity style={styles.backBtn} onPress={back}><Text style={styles.backText}>返回</Text></TouchableOpacity><Text style={styles.detailTitle}>{selectedItem.title}</Text></View>
            <Image source={{ uri: selectedItem.image }} style={styles.detailImage} />
            <Text style={styles.detailContent}>详情展示加载动效的构成、层次关系与节奏控制,并通过占位元素提升用户感知。</Text>
            <View style={styles.videoBox}><Text style={styles.videoText}>动效演示 ▶︎</Text></View>
            <View style={styles.galleryRow}>
              <Image source={{ uri: ICONS.star }} style={styles.galleryImage} />
              <Image source={{ uri: ICONS.heart }} style={styles.galleryImage} />
              <Image source={{ uri: ICONS.play }} style={styles.galleryImage} />
            </View>
          </View>
        )}
        <View style={styles.adBanner}><Text style={styles.adText}>广告位:动效设计指南</Text></View>
      </ScrollView>
      <View style={styles.bottomNav}><TouchableOpacity style={styles.navItem} onPress={() => Alert.alert('主页', '返回')}><Text style={styles.navIcon}>🏠</Text><Text style={styles.navText}>主页</Text></TouchableOpacity><TouchableOpacity style={styles.navItem} onPress={() => Alert.alert('动效', '浏览动效')}><Text style={styles.navIcon}></Text><Text style={styles.navText}>动效</Text></TouchableOpacity><TouchableOpacity style={styles.navItem} onPress={() => Alert.alert('个人', '我的')}><Text style={styles.navIcon}>👤</Text><Text style={styles.navText}>个人</Text></TouchableOpacity></View>
    </SafeAreaView>
  );
}
const palette = { bg: '#f7f8fc', header: '#ffffff', primary: '#7c3aed', text: '#0f172a', muted: '#64748b', card: '#ffffff', ad: '#fae8ff', adText: '#6b21a8' };
const styles = StyleSheet.create({
  container: { flex: 1, backgroundColor: palette.bg },
  header: { padding: 16, backgroundColor: palette.header, borderBottomWidth: 1, borderBottomColor: '#e5e7eb', flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' },
  title: { fontSize: 20, fontWeight: '700', color: palette.text },
  headerActions: { flexDirection: 'row', alignItems: 'center' },
  searchButton: { paddingHorizontal: 12, paddingVertical: 8, backgroundColor: '#eef2ff', borderRadius: 8, marginRight: 8 },
  searchText: { color: palette.primary, fontWeight: '600' },
  tagButton: { marginLeft: 6, paddingHorizontal: 10, paddingVertical: 6, backgroundColor: '#f1f5f9', borderRadius: 16 },
  tagText: { color: palette.muted },
  tagActive: { color: palette.primary, fontWeight: '700' },
  content: { padding: 16 },
  trending: { marginBottom: 12 },
  trendingCard: { flexDirection: 'row', alignItems: 'center', backgroundColor: '#fff', borderRadius: 12, padding: 10, marginRight: 10, elevation: 1 },
  trendingIcon: { width: 36, height: 36, borderRadius: 8, marginRight: 10, backgroundColor: '#e5e7eb' },
  trendingBody: { },
  trendingTitle: { fontSize: 14, fontWeight: '600', color: palette.text },
  trendingSub: { fontSize: 12, color: palette.muted },
  sectionTitle: { fontSize: 18, fontWeight: '700', color: palette.text, marginVertical: 12 },
  card: { backgroundColor: palette.card, borderRadius: 12, padding: 12, marginBottom: 12 },
  cardHeader: { flexDirection: 'row', alignItems: 'center' },
  cardImage: { width: 48, height: 48, borderRadius: 8, marginRight: 12, backgroundColor: '#e5e7eb' },
  cardTag: { paddingHorizontal: 8, paddingVertical: 4, borderRadius: 12, backgroundColor: '#ede9fe', color: palette.primary },
  cardTitle: { fontSize: 16, fontWeight: '600', color: palette.text, marginTop: 8 },
  cardSummary: { fontSize: 13, color: palette.muted, marginVertical: 6 },
  cardActions: { flexDirection: 'row' },
  actionBtn: { marginRight: 8, paddingHorizontal: 10, paddingVertical: 6, backgroundColor: '#f1f5f9', borderRadius: 8 },
  actionText: { color: palette.muted },
  pagination: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', backgroundColor: '#f1f5f9', borderRadius: 12, paddingHorizontal: 12, paddingVertical: 8 },
  pageBtn: { paddingHorizontal: 12, paddingVertical: 6, backgroundColor: '#fff', borderRadius: 8 },
  pageText: { color: palette.primary, fontWeight: '600' },
  pageIndicator: { color: palette.muted },
  chipsRow: { flexDirection: 'row', marginTop: 8 },
  chip: { paddingHorizontal: 10, paddingVertical: 6, backgroundColor: '#f1f5f9', borderRadius: 16, marginRight: 8 },
  chipActive: { backgroundColor: '#f5f3ff' },
  chipText: { color: palette.muted },
  chipTextActive: { color: palette.primary, fontWeight: '600' },
  detailHeader: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' },
  backBtn: { paddingHorizontal: 12, paddingVertical: 6, backgroundColor: '#eef2ff', borderRadius: 8 },
  backText: { color: palette.primary, fontWeight: '600' },
  detailTitle: { fontSize: 18, fontWeight: '700', color: palette.text },
  detailImage: { width: width - 32, height: 200, borderRadius: 12, marginVertical: 12, backgroundColor: '#e5e7eb' },
  detailContent: { fontSize: 14, lineHeight: 22, color: palette.muted },
  videoBox: { height: 160, borderRadius: 12, backgroundColor: '#111827', alignItems: 'center', justifyContent: 'center', marginVertical: 12 },
  videoText: { color: '#fff', fontSize: 16 },
  galleryRow: { flexDirection: 'row', marginVertical: 8 },
  galleryImage: { width: (width - 48) / 3, height: 90, borderRadius: 8, marginRight: 8, backgroundColor: '#e5e7eb' },
  adBanner: { backgroundColor: palette.ad, borderRadius: 12, padding: 12, alignItems: 'center', marginTop: 12 },
  adText: { color: palette.adText },
  bottomNav: { flexDirection: 'row', justifyContent: 'space-around', backgroundColor: '#fff', borderTopWidth: 1, borderTopColor: '#e5e7eb', paddingVertical: 10 },
  navItem: { alignItems: 'center' },
  navIcon: { fontSize: 20, color: palette.muted },
  navText: { fontSize: 12, color: palette.muted }
});

请添加图片描述


打包

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

在这里插入图片描述

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

在这里插入图片描述

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

请添加图片描述
教育百科加载动画应用采用React Native函数组件架构,通过useState实现轻量状态管理,覆盖标签切换、分页浏览等核心功能。组件设计突出跨端友好性:使用Base64图标集中管理确保多平台兼容;采用Flex弹性布局和动态尺寸计算适配不同设备;引入TypeScript类型约束提升代码健壮性。应用针对技术类内容特点,采用简约专业的设计风格,通过卡片式流式布局展示动效方案,支持详情查看、点赞收藏等交互。该实现充分发挥React Native跨端优势,核心逻辑可100%复用至鸿蒙平台,为技术类轻内容应用开发提供了可落地的参考方案。

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

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐