【精通篇】打造React Native鸿蒙跨平台开发高级复合组件库开发系列:Progress 进度条(展示操作的当前进度)
本文探讨了React Native在鸿蒙跨端开发中的关键技术实现。以通知中心组件为例,重点分析了气泡弹出框(Popover)和懒加载图片两大核心组件的设计思路。 气泡弹出框通过Animated API实现流畅的缩放/淡入动画,利用Modal透明遮罩解决跨端适配问题,并采用动态位置校准确保鸿蒙设备上的准确定位。懒加载图片组件则实现了完整的加载状态管理,通过Base64图标和错误处理机制优化鸿蒙环境下

在 React Native 鸿蒙跨端开发中,组件复用性、交互流畅度和多端适配性是核心诉求。本文将以一份完整的通知中心代码为例,从技术架构、核心组件设计、跨端适配技巧三个维度,深度解析如何在 React Native 中构建兼容鸿蒙系统的高质量交互组件,尤其聚焦气泡弹出框(Popover)和瀑布流布局两大核心场景。
1.1 技术栈
import React, { useState, useRef, useEffect } from 'react';
import {
View, Text, StyleSheet, ScrollView, SafeAreaView,
Image, Dimensions, TouchableOpacity, Animated,
FlatList, Modal
} from 'react-native';
技术选型
- 核心框架:React + React Native,利用 React 的声明式编程模型和组件化思想,结合 React Native 的原生渲染能力,实现 “一次编写,多端运行”
- 关键 API 依赖:
Animated:用于气泡弹出框的缩放/淡入淡出动画,保证鸿蒙系统下的流畅交互FlatList:实现瀑布流布局,相比 ScrollView 具备懒加载特性,优化鸿蒙设备的内存占用Modal:作为气泡弹出框的底层容器,提供跨端一致的遮罩层实现Dimensions:适配鸿蒙设备的屏幕尺寸,实现响应式布局
- 鸿蒙适配核心:基于 React Native 的跨端特性,通过 StyleSheet 样式隔离、原生组件 API 封装,确保组件在鸿蒙系统(HarmonyOS)上的兼容性,无需额外编写鸿蒙原生代码
1.2 项目结构
App.tsx
├── 常量定义(POPOVER_ICONS):Base64 图标库,减少网络请求
├── 类型接口(Interface):PopoverOption、NoticeItem 等,强类型约束
├── 核心组件:
│ ├── Popover:气泡弹出框组件(支持动画、位置校准)
│ ├── LazyImage:懒加载图片组件(适配鸿蒙网络环境)
│ ├── WaterfallNoticeItem:瀑布流通知项(长按触发 Popover)
│ └── App:主应用组件(状态管理、数据渲染)
└── 工具函数:getTypeColor、getTypeIcon,统一样式逻辑
架构设计
- 组件职责单一:每个组件聚焦特定功能,便于鸿蒙跨端场景下的维护和复用
- 强类型约束:通过 TypeScript 接口定义数据结构,减少跨端开发中的类型错误
- Base64 图标方案:避免鸿蒙系统下的图标资源路径问题,提升组件加载速度
2.1 气泡弹出框(Popover)组件:
气泡弹出框是跨端应用中高频交互组件,需解决鸿蒙系统下的动画流畅度、位置校准、遮罩层交互三大核心问题。
2.1.1 组件接口
interface PopoverOption {
id: string;
label: string;
icon?: string;
action: () => void;
}
interface PopoverProps {
visible: boolean;
options: PopoverOption[];
onClose: () => void;
anchorPosition: { x: number; y: number };
}
- 接口设计思路:通过
anchorPosition实现弹出框与触发元素的位置绑定,options支持自定义操作项,满足跨端场景下的灵活配置需求 - 鸿蒙适配考量:接口设计与 React Native 原生组件保持一致,避免鸿蒙系统下的 API 兼容问题
2.1.2 动画效果
const scaleAnim = useRef(new Animated.Value(0)).current;
const fadeAnim = useRef(new Animated.Value(0)).current;
useEffect(() => {
if (visible) {
Animated.parallel([
Animated.spring(scaleAnim, {
toValue: 1,
tension: 300,
friction: 20,
useNativeDriver: true
}),
Animated.timing(fadeAnim, {
toValue: 1,
duration: 200,
useNativeDriver: true
})
]).start();
} else {
Animated.parallel([
Animated.timing(scaleAnim, {
toValue: 0,
duration: 150,
useNativeDriver: true
}),
Animated.timing(fadeAnim, {
toValue: 0,
duration: 150,
useNativeDriver: true
})
]).start();
}
}, [visible]);
动画实现
- 并行动画:使用
Animated.parallel同时执行缩放和淡入淡出动画,提升鸿蒙系统下的交互体验 - Spring 动画:通过
tension(张力)和friction(摩擦力)参数,模拟物理弹性效果,比线性动画更自然 - useNativeDriver: true:关键优化!让动画在原生线程执行,避免鸿蒙系统下的 JS 线程阻塞,保证 60fps 流畅度
2.1.3 遮罩层设计
<Modal transparent={true} animationType="none" visible={visible} onRequestClose={onClose}>
<TouchableOpacity
style={styles.popoverBackdrop}
activeOpacity={1}
onPress={onClose}
>
<Animated.View
style={[
styles.popoverContainer,
{
top: anchorPosition.y,
left: anchorPosition.x,
transform: [{ scale: scaleAnim }],
opacity: fadeAnim
}
]}
>
<View style={styles.popoverTriangle} />
<View style={styles.popoverContent}>
{/* 操作项列表 */}
</View>
</Animated.View>
</TouchableOpacity>
</Modal>
适配技巧
- 透明 Modal 遮罩:
transparent={true}实现半透明遮罩,animationType="none"避免鸿蒙系统下的默认动画冲突 - 位置校准:通过
anchorPosition动态计算弹出框位置,在瀑布流组件中通过pageX/pageY获取触发元素坐标,解决鸿蒙设备不同屏幕尺寸下的位置偏移问题 - 三角形指示符:使用 CSS 边框实现弹出框指向箭头,通过
transform: [{ rotate: '180deg' }]适配不同触发位置,避免鸿蒙系统下的 SVG 兼容性问题
2.2 懒加载图片(LazyImage)组件:
在鸿蒙跨端应用中,图片加载是性能瓶颈之一,尤其在弱网络环境下,需实现加载中、加载失败、成功加载的完整状态管理。
核心实现代码
const LazyImage = ({ source, style }: { source: { uri: string }; style: any }) => {
const [loading, setLoading] = useState(true);
const [error, setError] = useState(false);
return (
<View style={style}>
{loading && !error && (
<View style={[StyleSheet.absoluteFill, { backgroundColor: '#334155', justifyContent: 'center', alignItems: 'center' }]}>
<Image source={{ uri: POPOVER_ICONS.notification }} style={{ width: 30, height: 30, tintColor: '#94a3b8' }} />
</View>
)}
{error && (
<View style={[StyleSheet.absoluteFill, { backgroundColor: '#dc2626', justifyContent: 'center', alignItems: 'center' }]}>
<Image source={{ uri: POPOVER_ICONS.close }} style={{ width: 30, height: 30, tintColor: '#ffffff' }} />
</View>
)}
<Image
source={source}
style={StyleSheet.absoluteFill}
onLoad={() => setLoading(false)}
onError={() => {
setLoading(false);
setError(true);
}}
resizeMode="cover"
/>
</View>
);
};
技术亮点
- 状态管理:通过
loading和error状态,覆盖图片加载全生命周期,提升鸿蒙用户体验 - 占位图优化:使用 Base64 图标作为占位图,避免鸿蒙系统下的图片资源加载失败问题
- 性能优化:
StyleSheet.absoluteFill实现图片与占位图的绝对定位,避免布局抖动;resizeMode="cover"适配鸿蒙设备的不同屏幕比例
2.3 瀑布流通知列表:
瀑布流布局是通知中心的核心UI,需解决鸿蒙系统下的多列布局、数据渲染、长按交互三大问题。
2.3.1 瀑布流
<FlatList
data={waterfallNotices}
renderItem={({ item }) => <WaterfallNoticeItem notice={item} onLongPress={() => {}} />}
keyExtractor={item => item.id}
numColumns={2}
contentContainerStyle={styles.waterfallListContainer}
showsVerticalScrollIndicator={false}
/>
关键技术点
- numColumns={2}:实现两列瀑布流,React Native 的 FlatList 原生支持多列布局,无需额外第三方库,鸿蒙系统兼容性更好
- keyExtractor:指定唯一 key,优化鸿蒙系统下的列表渲染性能,避免重复渲染
- contentContainerStyle:统一列表内边距,适配鸿蒙设备的屏幕尺寸
2.3.2 通知项组件(WaterfallNoticeItem)
const handleLongPress = (event: any) => {
const { pageX, pageY } = event.nativeEvent;
setAnchorPosition({ x: pageX - 150, y: pageY - 200 }); // 位置校准
setShowPopover(true);
};
- 长按触发逻辑:通过
onLongPress事件获取触摸点坐标,计算 Popover 弹出位置,解决鸿蒙系统下的触摸事件兼容性问题 - 跨端交互一致性:长按触发气泡弹出框的交互逻辑,与 iOS/Android 保持一致,降低鸿蒙用户的学习成本
2.4 样式设计:
const { width, height } = Dimensions.get('window');
const styles = StyleSheet.create({
waterfallNoticeItem: {
backgroundColor: '#1e293b',
margin: 5,
borderRadius: 12,
overflow: 'hidden',
borderWidth: 1,
borderColor: '#334155',
position: 'relative',
flex: 1, // 关键:实现两列等宽布局
},
// 其他样式...
});
样式适配
- Flex 布局优先:使用
flex: 1实现两列等宽布局,适配鸿蒙设备的不同屏幕宽度 - 颜色方案:采用深色主题(#0f172a 背景),符合鸿蒙系统的设计规范,同时减少屏幕功耗
- 边框与圆角:统一使用
borderWidth: 1和borderRadius: 12,避免鸿蒙系统下的样式渲染差异 - Dimensions 动态适配:通过
Dimensions.get('window')获取屏幕尺寸,实现响应式布局,适配鸿蒙手机、平板等不同设备
3.1 组件复用
- Base64 图标替代本地资源:避免鸿蒙系统下的资源路径问题,同时减少网络请求,提升加载速度
- 原生组件 API 封装:对 React Native 原生组件(如 Modal、Animated)进行二次封装,屏蔽鸿蒙系统的 API 差异
- 样式隔离:使用 StyleSheet.create 定义样式,避免 inline 样式在鸿蒙系统下的渲染性能问题
3.2 性能
- FlatList 懒加载:相比 ScrollView,FlatList 支持item 懒加载,减少鸿蒙设备的内存占用,尤其适合长列表场景
- useNativeDriver 动画优化:所有动画均设置
useNativeDriver: true,避免 JS 线程阻塞,保证鸿蒙系统下的动画流畅度 - 图片懒加载:通过 LazyImage 组件实现图片按需加载,减少鸿蒙弱网络环境下的流量消耗和加载延迟
3.3 交互
- 触摸事件统一:使用 React Native 标准的
onLongPress、onPress事件,避免鸿蒙系统下的触摸事件兼容性问题 - 动画效果统一:采用一致的动画参数(如 duration: 200ms),保证在鸿蒙、iOS、Android 上的交互体验一致
- 弹窗层级管理:通过
zIndex: 1000确保 Popover 组件在鸿蒙系统下的层级最高,避免被其他组件遮挡
本文通过一份完整的通知中心代码,深度解析了 React Native 鸿蒙跨端开发中气泡弹出框、瀑布流布局、图片懒加载三大核心场景的实现方案。关键亮点包括:
- 组件化架构:职责单一的组件设计,便于鸿蒙跨端场景下的维护和复用
- 动画与交互优化:使用 Animated 实现流畅动画,通过位置校准确保 Popover 组件的交互体验
- 鸿蒙适配细节:通过 Base64 图标、Flex 布局、原生 API 封装等技巧,解决鸿蒙系统的兼容性问题
- 性能优化:FlatList 懒加载、图片懒加载、useNativeDriver 动画优化,提升鸿蒙设备的运行效率
真实演示案例代码:
// App.tsx
import React, { useState, useRef, useEffect } from 'react';
import {
View,
Text,
StyleSheet,
ScrollView,
SafeAreaView,
Image,
Dimensions,
TouchableOpacity,
Animated,
FlatList,
Modal
} from 'react-native';
// Base64 Icons for progress components
const PROGRESS_ICONS = {
loading: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAACXBIWXMAAAsTAAALEwEAmpwYAAAFHmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDIgNzkuMTYwOTI0LCAyMDE3LzA3LzEzLTAxOjA2OjM5ICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOCAoV2luZG93cykiIHhtcDpDcmVhdGVEYXRlPSIyMDE5LTAxLTAzVDE2OjU1OjQ5KzA4OjAwIiB4bXA6TW9kaWZ5RGF0ZT0iMjAxOS0wMS0wM1QxNjo1NjoxNCswODowMCIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAxOS0wMS0wM1QxNjo1NjoxNCswODowMCIgZGM6Zm9ybWF0PSJpbWFnZS9wbmciIHBob3Rvc2hvcDpDb2xvck1vZGU9IjMiIHBob3Rvc2hvcDpJQ0NQcm9maWxlPSJzUkdCIElFQzYxOTY2LTIuMSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDoyZjA5Y2Y0OS01YjVlLWY5NDctYjg3Yi0yZDM5YjQxMjQyZWIiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MmYwOWNmNDktNWI1ZS1mOTQ3LWI4N2ItMmQzOWI0MTI0MmViIiB4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ9InhtcC5kaWQ6MmYwOWNmNDktNWI1ZS1mOTQ3LWI4N2ItMmQzOWI0MTI0MmViIj4gPHhtcE1NOkhpc3Rvcnk+IDxyZGY6U2VxPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0iY3JlYXRlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDoyZjA5Y2Y0OS01YjVlLWY5NDctYjg3Yi0yZDM5YjQxMjQyZWIiIHN0RXZ0OndoZW49IjIwMTktMDEtMDNUMTY6NTU6NDlaIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOCAoV2luZG93cykiLz4gPC9yZGY6U2VxPiA8L3htcE1NOkhpc3Rvcnk+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+Af/+/fz7+vn49/b19PPy8fDv7u3s6+rp6Ofm5eTj4uHg397d3Nva2djX1tXU09LR0M/OzczLysnIx8bFxMPCwcC/vr28u7q5uLe2tbSzsrGwr66trKuqqainpqWko6KhoJ+enZybmpmYl5aVlJOSkZCPjo2Mi4qJiIeGhYSDgoGAf359fHt6eXh3dnV0c3JxcG9ubWxramloZ2ZlZGNiYWBfXl1cW1pZWFdWVVRTUlFQT05NTEtKSUhHRkVEQ0JBQD8+PTw7Ojk4NzY1NDMyMTAvLi0sKyopKCcmJSQjIiEgHx4dHBsaGRgXFhUUExIREA8ODQwLCgkIBwYFBAMCAQAA//+gB5ZbAAAJRElEQVRoBe1ZaWxUVRQ+97030+k6U6Z0hk5npksptKW0bFJAWRB3UEFFjQuJKIoLJCZqYhRjNC5RFCMuaKJREhMXjAuKggsuRI0bGkEpWymltNBS2ulMZ9qZmfbe9+699715MzPTmdLS6STnfPPee+fc755zz3nnPcNwHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMx......',
success: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAACXBIWXMAAAsTAAALEwEAmpwYAAAFHmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDIgNzkuMTYwOTI0LCAyMDE3LzA3LzEzLTAxOjA2OjM5ICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOCAoV2luZG93cykiIHhtcDpDcmVhdGVEYXRlPSIyMDE5LTAxLTAzVDE2OjU2OjIwKzA4OjAwIiB4bXA6TW9kaWZ5RGF0ZT0iMjAxOS0wMS0wM1QxNjo1Njo0NSswODowMCIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAxOS0wMS0wM1QxNjo1Njo0NSswODowMCIgZGM6Zm9ybWF0PSJpbWFnZS9wbmciIHBob3Rvc2hvcDpDb2xvck1vZGU9IjMiIHBob3Rvc2hvcDpJQ0NQcm9maWxlPSJzUkdCIElFQzYxOTY2LTIuMSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDoyZjE5Y2Y0OS01YjVlLWY5NDctYjg3Yi0yZDM5YjQxMjQyZWIiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MmYxOWNmNDktNWI1ZS1mOTQ3LWI4N2ItMmQzOWI0MTI0MmViIiB4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ9InhtcC5kaWQ6MmYxOWNmNDktNWI1ZS1mOTQ3LWI4N2ItMmQzOWI0MTI0MmViIj4gPHhtcE1NOkhpc3Rvcnk+IDxyZGY6U2VxPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0iY3JlYXRlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDoyZjE5Y2Y0OS01YjVlLWY5NDctYjg3Yi0yZDM5YjQxMjQyZWIiIHN0RXZ0OndoZW49IjIwMTktMDEtMDNUMTY6NTY6MjBaIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOCAoV2luZG93cykiLz4gPC9yZGY6U2VxPiA8L3htcE1NOkhpc3Rvcnk+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+Af/+/fz7+vn49/b19PPy8fDv7u3s6+rp6Ofm5eTj4uHg397d3Nva2djX1tXU09LR0M/OzczLysnIx8bFxMPCwcC/vr28u7q5uLe2tbSzsrGwr66trKuqqainpqWko6KhoJ+enZybmpmYl5aVlJOSkZCPjo2Mi4qJiIeGhYSDgoGAf359fHt6eXh3dnV0c3JxcG9ubWxramloZ2ZlZGNiYWBfXl1cW1pZWFdWVVRTUlFQT05NTEtKSUhHRkVEQ0JBQD8+PTw7Ojk4NzY1NDMyMTAvLi0sKyopKCcmJSQjIiEgHx4dHBsaGRgXFhUUExIREA8ODQwLCgkIBwYFBAMCAQAA//+gB5ZbAAAJRElEQVRoBe1ZaWxUVRQ+97030+k6U6Z0hk5npksptKW0bFJAWRB3UEFFjQuJKIoLJCZqYhRjNC5RFCMuaKJREhMXjAuKggsuRI0bGkEpWymltNBS2ulMZ9qZmfbe9+699715MzPTmdLS6STnfPPee+fc755zz3nnPcNwHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMx......',
error: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAACXBIWXMAAAsTAAALEwEAmpwYAAAFHmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDIgNzkuMTYwOTI0LCAyMDE3LzA3LzEzLTAxOjA2OjM5ICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOCAoV2luZG93cykiIHhtcDpDcmVhdGVEYXRlPSIyMDE5LTAxLTAzVDE2OjU2OjUxKzA4OjAwIiB4bXA6TW9kaWZ5RGF0ZT0iMjAxOS0wMS0wM1QxNjo1NzoxNiswODowMCIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAxOS0wMS0wM1QxNjo1NzoxNiswODowMCIgZGM6Zm9ybWF0PSJpbWFnZS9wbmciIHBob3Rvc2hvcDpDb2xvck1vZGU9IjMiIHBob3Rvc2hvcDpJQ0NQcm9maWxlPSJzUkdCIElFQzYxOTY2LTIuMSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDoyZjI5Y2Y0OS01YjVlLWY5NDctYjg3Yi0yZDM5YjQxMjQyZWIiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MmYyOWNmNDktNWI1ZS1mOTQ3LWI4N2ItMmQzOWI0MTI0MmViIiB4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ9InhtcC5kaWQ6MmYyOWNmNDktNWI1ZS1mOTQ3LWI4N2ItMmQzOWI0MTI0MmViIj4gPHhtcE1NOkhpc3Rvcnk+IDxyZGY6U2VxPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0iY3JlYXRlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDoyZjI5Y2Y0OS01YjVlLWY5NDctYjg3Yi0yZDM5YjQxMjQyZWIiIHN0RXZ0OndoZW49IjIwMTktMDEtMDNUMTY6NTY6NTFaIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOCAoV2luZG93cykiLz4gPC9yZGY6U2VxPiA8L3htcE1NOkhpc3Rvcnk+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+Af/+/fz7+vn49/b19PPy8fDv7u3s6+rp6Ofm5eTj4uHg397d3Nva2djX1tXU09LR0M/OzczLysnIx8bFxMPCwcC/vr28u7q5uLe2tbSzsrGwr66trKuqqainpqWko6KhoJ+enZybmpmYl5aVlJOSkZCPjo2Mi4qJiIeGhYSDgoGAf359fHt6eXh3dnV0c3JxcG9ubWxramloZ2ZlZGNiYWBfXl1cW1pZWFdWVVRTUlFQT05NTEtKSUhHRkVEQ0JBQD8+PTw7Ojk4NzY1NDMyMTAvLi0sKyopKCcmJSQjIiEgHx4dHBsaGRgXFhUUExIREA8ODQwLCgkIBwYFBAMCAQAA//+gB5ZbAAAJRElEQVRoBe1ZaWxUVRQ+97030+k6U6Z0hk5npksptKW0bFJAWRB3UEFFjQuJKIoLJCZqYhRjNC5RFCMuaKJREhMXjAuKggsuRI0bGkEpWymltNBS2ulMZ9qZmfbe9+699715MzPTmdLS6STnfPPee+fc755zz3nnPcNwHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMx......',
menu: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAACXBIWXMAAAsTAAALEwEAmpwYAAAFHmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDIgNzkuMTYwOTI0LCAyMDE3LzA3LzEzLTAxOjA2OjM5ICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOCAoV2luZG93cykiIHhtcDpDcmVhdGVEYXRlPSIyMDE5LTAxLTAzVDE2OjU3OjIyKzA4OjAwIiB4bXA6TW9kaWZ5RGF0ZT0iMjAxOS0wMS0wM1QxNjo1Nzo0OCswODowMCIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAxOS0wMS0wM1QxNjo1Nzo0OCswODowMCIgZGM6Zm9ybWF0PSJpbWFnZS9wbmciIHBob3Rvc2hvcDpDb2xvck1vZGU9IjMiIHBob3Rvc2hvcDpJQ0NQcm9maWxlPSJzUkdCIElFQzYxOTY2LTIuMSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDoyZjM5Y2Y0OS01YjVlLWY5NDctYjg3Yi0yZDM5YjQxMjQyZWIiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MmYzOWNmNDktNWI1ZS1mOTQ3LWI4N2ItMmQzOWI0MTI0MmViIiB4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ9InhtcC5kaWQ6MmYzOWNmNDktNWI1ZS1mOTQ3LWI4N2ItMmQzOWI0MTI0MmViIj4gPHhtcE1NOkhpc3Rvcnk+IDxyZGY6U2VxPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0iY3JlYXRlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDoyZjM5Y2Y0OS01YjVlLWY5NDctYjg3Yi0yZDM5YjQxMjQyZWIiIHN0RXZ0OndoZW49IjIwMTktMDEtMDNUMTY6NTc6MjJaIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOCAoV2luZG93cykiLz4gPC9yZGY6U2VxPiA8L3htcE1NOkhpc3Rvcnk+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+Af/+/fz7+vn49/b19PPy8fDv7u3s6+rp6Ofm5eTj4uHg397d3Nva2djX1tXU09LR0M/OzczLysnIx8bFxMPCwcC/vr28u7q5uLe2tbSzsrGwr66trKuqqainpqWko6KhoJ+enZybmpmYl5aVlJOSkZCPjo2Mi4qJiIeGhYSDgoGAf359fHt6eXh3dnV0c3JxcG9ubWxramloZ2ZlZGNiYWBfXl1cW1pZWFdWVVRTUlFQT05NTEtKSUhHRkVEQ0JBQD8+PTw7Ojk4NzY1NDMyMTAvLi0sKyopKCcmJSQjIiEgHx4dHBsaGRgXFhUUExIREA8ODQwLCgkIBwYFBAMCAQAA//+gB5ZbAAAJRElEQVRoBe1ZaWxUVRQ+97030+k6U6Z0hk5npksptKW0bFJAWRB3UEFFjQuJKIoLJCZqYhRjNC5RFCMuaKJREhMXjAuKggsuRI0bGkEpWymltNBS2ulMZ9qZmfbe9+699715MzPTmdLS6STnfPPee+fc755zz3nnPcNwHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMx......',
settings: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAACXBIWXMAAAsTAAALEwEAmpwYAAAFHmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDIgNzkuMTYwOTI0LCAyMDE3LzA3LzEzLTAxOjA2OjM5ICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOCAoV2luZG93cykiIHhtcDpDcmVhdGVEYXRlPSIyMDE5LTAxLTAzVDE2OjU3OjUzKzA4OjAwIiB4bXA6TW9kaWZ5RGF0ZT0iMjAxOS0wMS0wM1QxNjo1ODoxOCswODowMCIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAxOS0wMS0wM1QxNjo1ODoxOCswODowMCIgZGM6Zm9ybWF0PSJpbWFnZS9wbmciIHBob3Rvc2hvcDpDb2xvck1vZGU9IjMiIHBob3Rvc2hvcDpJQ0NQcm9maWxlPSJzUkdCIElFQzYxOTY2LTIuMSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDoyZjQ5Y2Y0OS01YjVlLWY5NDctYjg3Yi0yZDM5YjQxMjQyZWIiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MmY0OWNmNDktNWI1ZS1mOTQ3LWI4N2ItMmQzOWI0MTI0MmViIiB4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ9InhtcC5kaWQ6MmY0OWNmNDktNWI1ZS1mOTQ3LWI4N2ItMmQzOWI0MTI0MmViIj4gPHhtcE1NOkhpc3Rvcnk+IDxyZGY6U2VxPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0iY3JlYXRlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDoyZjQ5Y2Y0OS01YjVlLWY5NDctYjg3Yi0yZDM5YjQxMjQyZWIiIHN0RXZ0OndoZW49IjIwMTktMDEtMDNUMTY6NTc6NTNaIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOCAoV2luZG93cykiLz4gPC9yZGY6U2VxPiA8L3htcE1NOkhpc3Rvcnk+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+Af/+/fz7+vn49/b19PPy8fDv7u3s6+rp6Ofm5eTj4uHg397d3Nva2djX1tXU09LR0M/OzczLysnIx8bFxMPCwcC/vr28u7q5uLe2tbSzsrGwr66trKuqqainpqWko6KhoJ+enZybmpmYl5aVlJOSkZCPjo2Mi4qJiIeGhYSDgoGAf359fHt6eXh3dnV0c3JxcG9ubWxramloZ2ZlZGNiYWBfXl1cW1pZWFdWVVRTUlFQT05NTEtKSUhHRkVEQ0JBQD8+PTw7Ojk4NzY1NDMyMTAvLi0sKyopKCcmJSQjIiEgHx4dHBsaGRgXFhUUExIREA8ODQwLCgkIBwYFBAMCAQAA//+gB5ZbAAAJRElEQVRoBe1ZaWxUVRQ+97030+k6U6Z0hk5npksptKW0bFJAWRB3UEFFjQuJKIoLJCZqYhRjNC5RFCMuaKJREhMXjAuKggsuRI0bGkEpWymltNBS2ulMZ9qZmfbe9+699715MzPTmdLS6STnfPPee+fc755zz3nnPcNwHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMx......',
share: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAACXBIWXMAAAsTAAALEwEAmpwYAAAFHmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDIgNzkuMTYwOTI0LCAyMDE3LzA3LzEzLTAxOjA2OjM5ICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOCAoV2luZG93cykiIHhtcDpDcmVhdGVEYXRlPSIyMDE5LTAxLTAzVDE2OjU4OjI0KzA4OjAwIiB4bXA6TW9kaWZ5RGF0ZT0iMjAxOS0wMS0wM1QxNjo1ODo0OSswODowMCIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAxOS0wMS0wM1QxNjo1ODo0OSswODowMCIgZGM6Zm9ybWF0PSJpbWFnZS9wbmciIHBob3Rvc2hvcDpDb2xvck1vZGU9IjMiIHBob3Rvc2hvcDpJQ0NQcm9maWxlPSJzUkdCIElFQzYxOTY2LTIuMSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDoyZjU5Y2Y0OS01YjVlLWY5NDctYjg3Yi0yZDM5YjQxMjQyZWIiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MmY1OWNmNDktNWI1ZS1mOTQ3LWI4N2ItMmQzOWI0MTI0MmViIiB4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ9InhtcC5kaWQ6MmY1OWNmNDktNWI1ZS1mOTQ3LWI4N2ItMmQzOWI0MTI0MmViIj4gPHhtcE1NOkhpc3Rvcnk+IDxyZGY6U2VxPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0iY3JlYXRlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDoyZjU5Y2Y0OS01YjVlLWY5NDctYjg3Yi0yZDM5YjQxMjQyZWIiIHN0RXZ0OndoZW49IjIwMTktMDEtMDNUMTY6NTg6MjRaIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOCAoV2luZG93cykiLz4gPC9yZGY6U2VxPiA8L3htcE1NOkhpc3Rvcnk+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+Af/+/fz7+vn49/b19PPy8fDv7u3s6+rp6Ofm5eTj4uHg397d3Nva2djX1tXU09LR0M/OzczLysnIx8bFxMPCwcC/vr28u7q5uLe2tbSzsrGwr66trKuqqainpqWko6KhoJ+enZybmpmYl5aVlJOSkZCPjo2Mi4qJiIeGhYSDgoGAf359fHt6eXh3dnV0c3JxcG9ubWxramloZ2ZlZGNiYWBfXl1cW1pZWFdWVVRTUlFQT05NTEtKSUhHRkVEQ0JBQD8+PTw7Ojk4NzY1NDMyMTAvLi0sKyopKCcmJSQjIiEgHx4dHBsaGRgXFhUUExIREA8ODQwLCgkIBwYFBAMCAQAA//+gB5ZbAAAJRElEQVRoBe1ZaWxUVRQ+97030+k6U6Z0hk5npksptKW0bFJAWRB3UEFFjQuJKIoLJCZqYhRjNC5RFCMuaKJREhMXjAuKggsuRI0bGkEpWymltNBS2ulMZ9qZmfbe9+699715MzPTmdLS6STnfPPee+fc755zz3nnPcNwHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMx......'
};
// 进度条组件
interface ProgressProps {
value: number;
maxValue?: number;
height?: number;
color?: string;
backgroundColor?: string;
borderRadius?: number;
showPercentage?: boolean;
animated?: boolean;
}
const ProgressBar: React.FC<ProgressProps> = ({
value,
maxValue = 100,
height = 8,
color = '#3b82f6',
backgroundColor = '#334155',
borderRadius = 4,
showPercentage = true,
animated = true
}) => {
const [progress, setProgress] = useState(0);
const progressAnim = useRef(new Animated.Value(0)).current;
useEffect(() => {
const percentage = (value / maxValue) * 100;
if (animated) {
Animated.timing(progressAnim, {
toValue: percentage,
duration: 500,
useNativeDriver: false
}).start();
} else {
setProgress(percentage);
}
}, [value, maxValue, animated]);
const actualProgress = animated ? progressAnim : progress;
return (
<View style={[styles.progressBarContainer, { height, backgroundColor, borderRadius }]}>
<Animated.View
style={[
styles.progressBarFill,
{
backgroundColor: color,
borderRadius,
width: animated ? actualProgress.interpolate({
inputRange: [0, 100],
outputRange: ['0%', '100%']
}) : `${Math.min(100, Math.max(0, (value / maxValue) * 100))}%`
}
]}
/>
{showPercentage && (
<Text style={styles.progressPercentage}>
{Math.round((value / maxValue) * 100)}%
</Text>
)}
</View>
);
};
// 气泡弹出框组件
interface PopoverOption {
id: string;
label: string;
icon?: string;
action: () => void;
}
interface PopoverProps {
visible: boolean;
options: PopoverOption[];
onClose: () => void;
anchorPosition: { x: number; y: number };
}
const Popover: React.FC<PopoverProps> = ({ visible, options, onClose, anchorPosition }) => {
const scaleAnim = useRef(new Animated.Value(0)).current;
const fadeAnim = useRef(new Animated.Value(0)).current;
useEffect(() => {
if (visible) {
Animated.parallel([
Animated.spring(scaleAnim, {
toValue: 1,
tension: 300,
friction: 20,
useNativeDriver: true
}),
Animated.timing(fadeAnim, {
toValue: 1,
duration: 200,
useNativeDriver: true
})
]).start();
} else {
Animated.parallel([
Animated.timing(scaleAnim, {
toValue: 0,
duration: 150,
useNativeDriver: true
}),
Animated.timing(fadeAnim, {
toValue: 0,
duration: 150,
useNativeDriver: true
})
]).start();
}
}, [visible]);
if (!visible) return null;
return (
<Modal transparent={true} animationType="none" visible={visible} onRequestClose={onClose}>
<TouchableOpacity
style={styles.popoverBackdrop}
activeOpacity={1}
onPress={onClose}
>
<Animated.View
style={[
styles.popoverContainer,
{
top: anchorPosition.y,
left: anchorPosition.x,
transform: [{ scale: scaleAnim }],
opacity: fadeAnim
}
]}
>
<View style={styles.popoverTriangle} />
<View style={styles.popoverContent}>
{options.map((option, index) => (
<TouchableOpacity
key={option.id}
style={[styles.popoverItem, index === options.length - 1 && styles.lastItem]}
onPress={() => {
option.action();
onClose();
}}
>
{option.icon && (
<Image source={{ uri: option.icon }} style={styles.popoverItemIcon} />
)}
<Text style={styles.popoverItemText}>{option.label}</Text>
</TouchableOpacity>
))}
</View>
</Animated.View>
</TouchableOpacity>
</Modal>
);
};
// 懒加载图片组件
const LazyImage = ({ source, style }: { source: { uri: string }; style: any }) => {
const [loading, setLoading] = useState(true);
const [error, setError] = useState(false);
return (
<View style={style}>
{loading && !error && (
<View style={[StyleSheet.absoluteFill, { backgroundColor: '#334155', justifyContent: 'center', alignItems: 'center' }]}>
<Image source={{ uri: PROGRESS_ICONS.loading }} style={{ width: 30, height: 30, tintColor: '#94a3b8' }} />
</View>
)}
{error && (
<View style={[StyleSheet.absoluteFill, { backgroundColor: '#dc2626', justifyContent: 'center', alignItems: 'center' }]}>
<Image source={{ uri: PROGRESS_ICONS.error }} style={{ width: 30, height: 30, tintColor: '#ffffff' }} />
</View>
)}
<Image
source={source}
style={StyleSheet.absoluteFill}
onLoad={() => setLoading(false)}
onError={() => {
setLoading(false);
setError(true);
}}
resizeMode="cover"
/>
</View>
);
};
// 通知项类型
interface NoticeItem {
id: string;
type: 'announcement' | 'warning' | 'info' | 'discount';
title: string;
content: string;
time: string;
read: boolean;
progress: number;
imageUrl?: string;
}
// 瀑布流通知项组件
const WaterfallNoticeItem = ({ notice, onLongPress }: { notice: NoticeItem; onLongPress: (notice: NoticeItem) => void }) => {
const [anchorPosition, setAnchorPosition] = useState({ x: 0, y: 0 });
const [showPopover, setShowPopover] = useState(false);
const handleLongPress = (event: any) => {
const { pageX, pageY } = event.nativeEvent;
setAnchorPosition({ x: pageX - 150, y: pageY - 200 }); // Adjust position to center the popover
setShowPopover(true);
};
const popoverOptions: PopoverOption[] = [
{
id: 'view-details',
label: '查看详情',
icon: PROGRESS_ICONS.settings,
action: () => {
alert(`正在查看 "${notice.title}" 的详情`);
}
},
{
id: 'share',
label: '分享',
icon: PROGRESS_ICONS.share,
action: () => {
alert(`正在分享 "${notice.title}"`);
}
},
{
id: 'more',
label: '更多操作',
icon: PROGRESS_ICONS.menu,
action: () => {
alert(`更多操作: "${notice.title}"`);
}
}
];
return (
<TouchableOpacity
style={styles.waterfallNoticeItem}
onLongPress={handleLongPress}
>
<View style={styles.waterfallNoticeHeader}>
<View style={[styles.waterfallNoticeTypeBadge, { backgroundColor: `${getTypeColor(notice.type)}20` }]}>
<Image
source={{ uri: getTypeIcon(notice.type) }}
style={[styles.waterfallNoticeTypeIcon, { tintColor: getTypeColor(notice.type) }]}
/>
</View>
<View style={styles.waterfallNoticeInfo}>
<Text style={styles.waterfallNoticeTitle}>{notice.title}</Text>
<Text style={styles.waterfallNoticeTime}>{notice.time}</Text>
</View>
{!notice.read && <View style={styles.unreadDot} />}
</View>
<Text style={styles.waterfallNoticeContent}>{notice.content}</Text>
<View style={styles.progressContainer}>
<Text style={styles.progressLabel}>进度: {Math.round(notice.progress)}%</Text>
<ProgressBar
value={notice.progress}
maxValue={100}
height={6}
color={getTypeColor(notice.type)}
showPercentage={false}
/>
</View>
{notice.imageUrl && (
<LazyImage
source={{ uri: notice.imageUrl }}
style={styles.waterfallNoticeImage}
/>
)}
<View style={styles.waterfallNoticeActions}>
<TouchableOpacity style={styles.waterfallNoticeActionButton}>
<Image source={{ uri: PROGRESS_ICONS.settings }} style={styles.waterfallNoticeActionIcon} />
<Text style={styles.waterfallNoticeActionText}>设置</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.waterfallNoticeActionButton}>
<Image source={{ uri: PROGRESS_ICONS.share }} style={styles.waterfallNoticeActionIcon} />
<Text style={styles.waterfallNoticeActionText}>分享</Text>
</TouchableOpacity>
</View>
<Popover
visible={showPopover}
options={popoverOptions}
onClose={() => setShowPopover(false)}
anchorPosition={anchorPosition}
/>
</TouchableOpacity>
);
};
// 类型辅助函数
const getTypeColor = (type: string) => {
switch(type) {
case 'announcement': return '#3b82f6';
case 'warning': return '#ef4444';
case 'info': return '#10b981';
case 'discount': return '#f59e0b';
default: return '#64748b';
}
};
const getTypeIcon = (type: string) => {
switch(type) {
case 'announcement': return PROGRESS_ICONS.settings;
case 'warning': return PROGRESS_ICONS.error;
case 'info': return PROGRESS_ICONS.loading;
case 'discount': return PROGRESS_ICONS.share;
default: return PROGRESS_ICONS.loading;
}
};
// 主应用组件
const App = () => {
const [waterfallNotices, setWaterfallNotices] = useState<NoticeItem[]>([
{
id: '1',
type: 'announcement',
title: '文件下载进度',
content: '正在下载大型软件包,请耐心等待...',
time: '刚刚',
read: false,
progress: 65,
imageUrl: 'https://picsum.photos/seed/1/300/200'
},
{
id: '2',
type: 'warning',
title: '系统更新',
content: '系统更新正在进行中,预计还需要15分钟完成',
time: '2分钟前',
read: false,
progress: 30,
imageUrl: 'https://picsum.photos/seed/2/300/400'
},
{
id: '3',
type: 'info',
title: '备份任务',
content: '正在备份您的照片和文档到云端',
time: '5分钟前',
read: true,
progress: 85,
imageUrl: 'https://picsum.photos/seed/3/300/300'
},
{
id: '4',
type: 'discount',
title: '视频渲染',
content: '正在处理您的视频文件,渲染完成后将自动保存',
time: '8分钟前',
read: false,
progress: 42,
imageUrl: 'https://picsum.photos/seed/4/300/250'
},
{
id: '5',
type: 'info',
title: '数据同步',
content: '正在同步您的联系人和日历数据',
time: '10分钟前',
read: true,
progress: 95,
imageUrl: 'https://picsum.photos/seed/5/300/350'
},
{
id: '6',
type: 'announcement',
title: '文件上传',
content: '正在上传您的文档到服务器',
time: '15分钟前',
read: true,
progress: 20,
imageUrl: 'https://picsum.photos/seed/6/300/200'
}
]);
// 模拟进度更新
useEffect(() => {
const interval = setInterval(() => {
setWaterfallNotices(prev =>
prev.map(notice => {
// 只对未完成的任务更新进度
if (notice.progress < 100) {
const increment = Math.random() * 5; // 随机增加0-5%的进度
const newProgress = Math.min(100, notice.progress + increment);
return {
...notice,
progress: newProgress
};
}
return notice;
})
);
}, 2000);
return () => clearInterval(interval);
}, []);
return (
<SafeAreaView style={styles.container}>
<View style={styles.header}>
<Text style={styles.headerTitle}>进度条组件</Text>
<Text style={styles.headerSubtitle}>展示操作的当前进度</Text>
</View>
<ScrollView contentContainerStyle={styles.contentContainer}>
<View style={styles.progressBarSection}>
<Text style={styles.sectionTitle}>进度条示例</Text>
<View style={styles.progressExample}>
<Text style={styles.progressExampleLabel}>文件下载</Text>
<ProgressBar value={75} maxValue={100} color="#3b82f6" />
</View>
<View style={styles.progressExample}>
<Text style={styles.progressExampleLabel}>系统更新</Text>
<ProgressBar value={45} maxValue={100} color="#ef4444" />
</View>
<View style={styles.progressExample}>
<Text style={styles.progressExampleLabel}>数据备份</Text>
<ProgressBar value={90} maxValue={100} color="#10b981" />
</View>
<View style={styles.progressExample}>
<Text style={styles.progressExampleLabel}>视频渲染</Text>
<ProgressBar value={30} maxValue={100} color="#f59e0b" />
</View>
</View>
<View style={styles.waterfallSection}>
<Text style={styles.sectionTitle}>瀑布流通知</Text>
<Text style={styles.sectionSubtitle}>长按任意通知项查看操作菜单</Text>
<FlatList
data={waterfallNotices}
renderItem={({ item }) => <WaterfallNoticeItem notice={item} onLongPress={() => {}} />}
keyExtractor={item => item.id}
numColumns={2}
contentContainerStyle={styles.waterfallListContainer}
showsVerticalScrollIndicator={false}
/>
</View>
<View style={styles.featuresSection}>
<Text style={styles.featuresTitle}>功能特性</Text>
<View style={styles.featureList}>
<View style={styles.featureItem}>
<Text style={styles.featureBullet}>•</Text>
<Text style={styles.featureText}>动态进度展示</Text>
</View>
<View style={styles.featureItem}>
<Text style={styles.featureBullet}>•</Text>
<Text style={styles.featureText}>长按触发气泡菜单</Text>
</View>
<View style={styles.featureItem}>
<Text style={styles.featureBullet}>•</Text>
<Text style={styles.featureText}>懒加载图片优化性能</Text>
</View>
<View style={styles.featureItem}>
<Text style={styles.featureBullet}>•</Text>
<Text style={styles.featureText}>瀑布流布局展示</Text>
</View>
<View style={styles.featureItem}>
<Text style={styles.featureBullet}>•</Text>
<Text style={styles.featureText}>丰富的Base64图标库</Text>
</View>
<View style={styles.featureItem}>
<Text style={styles.featureBullet}>•</Text>
<Text style={styles.featureText}>响应式布局支持</Text>
</View>
</View>
</View>
<View style={styles.usageSection}>
<Text style={styles.usageTitle}>使用说明</Text>
<Text style={styles.usageText}>
进度条组件适用于展示长时间操作的进度,
如文件下载、上传、数据处理等场景。
</Text>
</View>
</ScrollView>
<View style={styles.footer}>
<Text style={styles.footerText}>© 2023 进度条组件. All rights reserved.</Text>
</View>
</SafeAreaView>
);
};
const { width, height } = Dimensions.get('window');
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#0f172a',
},
header: {
backgroundColor: '#1e293b',
paddingTop: 20,
paddingBottom: 25,
paddingHorizontal: 20,
borderBottomWidth: 1,
borderBottomColor: '#334155',
},
headerTitle: {
fontSize: 26,
fontWeight: '700',
color: '#f1f5f9',
textAlign: 'center',
marginBottom: 5,
},
headerSubtitle: {
fontSize: 15,
color: '#94a3b8',
textAlign: 'center',
},
contentContainer: {
padding: 16,
},
progressBarSection: {
marginBottom: 20,
},
sectionTitle: {
fontSize: 22,
fontWeight: '700',
color: '#f1f5f9',
marginBottom: 15,
paddingLeft: 10,
borderLeftWidth: 4,
borderLeftColor: '#3b82f6',
},
progressExample: {
marginBottom: 20,
},
progressExampleLabel: {
fontSize: 14,
color: '#cbd5e1',
marginBottom: 8,
},
waterfallSection: {
marginBottom: 20,
},
sectionSubtitle: {
fontSize: 14,
color: '#94a3b8',
marginBottom: 15,
paddingLeft: 14,
},
waterfallListContainer: {
padding: 5,
},
waterfallNoticeItem: {
backgroundColor: '#1e293b',
margin: 5,
borderRadius: 12,
overflow: 'hidden',
borderWidth: 1,
borderColor: '#334155',
position: 'relative',
},
waterfallNoticeHeader: {
flexDirection: 'row',
alignItems: 'center',
padding: 12,
},
waterfallNoticeTypeBadge: {
width: 30,
height: 30,
borderRadius: 15,
justifyContent: 'center',
alignItems: 'center',
marginRight: 10,
},
waterfallNoticeTypeIcon: {
width: 16,
height: 16,
},
waterfallNoticeInfo: {
flex: 1,
},
waterfallNoticeTitle: {
fontSize: 14,
fontWeight: '600',
color: '#f1f5f9',
},
waterfallNoticeTime: {
fontSize: 10,
color: '#94a3b8',
},
waterfallNoticeContent: {
fontSize: 12,
color: '#cbd5e1',
lineHeight: 18,
paddingHorizontal: 12,
marginBottom: 10,
},
progressContainer: {
paddingHorizontal: 12,
marginBottom: 10,
},
progressLabel: {
fontSize: 12,
color: '#94a3b8',
marginBottom: 4,
},
waterfallNoticeImage: {
height: 120,
marginHorizontal: 12,
marginBottom: 10,
borderRadius: 8,
},
waterfallNoticeActions: {
flexDirection: 'row',
justifyContent: 'space-around',
paddingVertical: 10,
borderTopWidth: 1,
borderTopColor: '#334155',
},
waterfallNoticeActionButton: {
flexDirection: 'row',
alignItems: 'center',
},
waterfallNoticeActionIcon: {
width: 14,
height: 14,
tintColor: '#94a3b8',
marginRight: 4,
},
waterfallNoticeActionText: {
fontSize: 12,
color: '#94a3b8',
},
unreadDot: {
width: 8,
height: 8,
borderRadius: 4,
backgroundColor: '#ef4444',
},
progressBarContainer: {
backgroundColor: '#334155',
overflow: 'hidden',
position: 'relative',
},
progressBarFill: {
height: '100%',
backgroundColor: '#3b82f6',
},
progressPercentage: {
position: 'absolute',
right: 10,
top: '50%',
transform: [{ translateY: -10 }],
color: '#ffffff',
fontSize: 10,
fontWeight: 'bold',
},
popoverBackdrop: {
flex: 1,
backgroundColor: 'rgba(0,0,0,0.5)',
},
popoverContainer: {
position: 'absolute',
zIndex: 1000,
},
popoverTriangle: {
position: 'absolute',
top: -10,
left: 20,
width: 0,
height: 0,
backgroundColor: 'transparent',
borderStyle: 'solid',
borderLeftWidth: 10,
borderRightWidth: 10,
borderBottomWidth: 10,
borderLeftColor: 'transparent',
borderRightColor: 'transparent',
borderBottomColor: '#1e293b',
transform: [{ rotate: '180deg' }],
},
popoverContent: {
backgroundColor: '#1e293b',
borderRadius: 8,
paddingVertical: 4,
minWidth: 150,
borderWidth: 1,
borderColor: '#334155',
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.2,
shadowRadius: 4,
elevation: 5,
},
popoverItem: {
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: 16,
paddingVertical: 12,
borderBottomWidth: 1,
borderBottomColor: '#334155',
},
lastItem: {
borderBottomWidth: 0,
},
popoverItemIcon: {
width: 16,
height: 16,
tintColor: '#94a3b8',
marginRight: 10,
},
popoverItemText: {
fontSize: 14,
color: '#f1f5f9',
},
featuresSection: {
backgroundColor: '#1e293b',
borderRadius: 16,
padding: 20,
marginBottom: 20,
borderWidth: 1,
borderColor: '#334155',
},
featuresTitle: {
fontSize: 20,
fontWeight: '700',
color: '#f1f5f9',
marginBottom: 15,
textAlign: 'center',
},
featureList: {
paddingLeft: 10,
},
featureItem: {
flexDirection: 'row',
alignItems: 'center',
marginBottom: 12,
},
featureBullet: {
fontSize: 18,
color: '#3b82f6',
marginRight: 10,
},
featureText: {
fontSize: 16,
color: '#cbd5e1',
flex: 1,
},
usageSection: {
backgroundColor: '#1e293b',
borderRadius: 16,
padding: 20,
borderWidth: 1,
borderColor: '#334155',
},
usageTitle: {
fontSize: 20,
fontWeight: '700',
color: '#f1f5f9',
marginBottom: 15,
textAlign: 'center',
},
usageText: {
fontSize: 16,
color: '#cbd5e1',
lineHeight: 24,
textAlign: 'center',
},
footer: {
paddingVertical: 15,
alignItems: 'center',
borderTopWidth: 1,
borderTopColor: '#334155',
backgroundColor: '#1e293b',
},
footerText: {
fontSize: 14,
color: '#94a3b8',
fontWeight: '500',
},
});
export default App;

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

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

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

本文探讨了React Native在鸿蒙跨端开发中的关键技术实现。以通知中心组件为例,重点分析了气泡弹出框(Popover)和懒加载图片两大核心组件的设计思路。
气泡弹出框通过Animated API实现流畅的缩放/淡入动画,利用Modal透明遮罩解决跨端适配问题,并采用动态位置校准确保鸿蒙设备上的准确定位。懒加载图片组件则实现了完整的加载状态管理,通过Base64图标和错误处理机制优化鸿蒙环境下的网络性能。
文章展示了如何利用React Native的跨平台特性,通过合理的组件划分、动画优化和状态管理,构建兼容鸿蒙系统的高质量交互组件。这些技术方案既保证了多端一致性,又针对鸿蒙系统进行了专项优化。
欢迎大家加入开源鸿蒙跨平台开发者社区,一起共建开源鸿蒙跨平台生态。
更多推荐

所有评论(0)