【精通篇】打造React Native鸿蒙跨平台开发高级复合组件库开发系列:Sticky 粘性布局(始终会固定在屏幕顶部)
本文介绍了一套面向React Native+鸿蒙跨端开发的组件体系,包含粘性布局、步骤条、骨架屏等核心组件。这些组件针对鸿蒙多终端特性进行了优化设计:粘性布局通过动态占位视图和fixed定位保证跨端稳定性;步骤条支持水平/垂直布局适配不同设备;骨架屏和进度条区分原生/JS驱动动画以优化性能;气泡弹窗通过绝对坐标计算实现多终端准确定位。组件设计充分利用React Native特性,同时考虑鸿蒙分布式
欢迎大家加入开源鸿蒙跨平台开发者社区,一起共建开源鸿蒙跨平台生态。
在鸿蒙(HarmonyOS)全场景分布式应用生态下,React Native 凭借其“一次开发、多端部署”的特性成为跨端开发的核心选择。本文将深度解读一套面向 React Native + 鸿蒙跨端场景的组件体系,涵盖粘性布局、步骤条、骨架屏、进度条、气泡弹窗、懒加载图片及瀑布流通知等核心组件,剖析其跨端适配的设计思路、动画实现与性能优化要点,展现如何在保证多终端体验一致性的同时,充分发挥各平台原生能力。
粘性布局组件
粘性(Sticky)布局是移动端高频交互模式,在鸿蒙多终端场景中(手机、平板、智慧屏),固定顶部的导航/标题栏是基础需求。该组件的实现核心在于解决跨端滚动定位的精准性与布局稳定性问题:
const Sticky: React.FC<StickyProps> = ({ children, offsetTop = 0 }) => {
const [isStuck, setIsStuck] = useState(false);
const [stickyHeight, setStickyHeight] = useState(0);
const onLayout = (event: any) => {
const { height } = event.nativeEvent.layout;
setStickyHeight(height);
};
const onScroll = (event: any) => {
const { y } = event.nativeEvent.contentOffset;
setIsStuck(y > offsetTop);
};
return (
<>
<View
style={[styles.stickyPlaceholder, { height: stickyHeight }]}
onLayout={onLayout}
/>
<Animated.View
style={[
styles.stickyContainer,
{
position: isStuck ? 'fixed' : 'relative',
top: isStuck ? offsetTop : undefined,
zIndex: 1000
}
]}
>
{children}
</Animated.View>
</>
);
};
在跨端适配层面,该实现有两个关键设计:其一,通过 stickyPlaceholder 占位视图动态计算并保留组件高度,避免组件固定后下方内容“跳动”——这在鸿蒙不同屏幕尺寸设备上尤为重要,比如智慧屏的大尺寸界面中,布局偏移会被放大,占位视图的精准高度计算能保证视觉一致性;其二,采用 position: fixed 而非 React Native 内置的 sticky 定位,原因在于原生 sticky 在鸿蒙系统的多终端适配中存在兼容性问题,而 fixed 定位通过原生驱动实现,能在手机、平板、智慧屏等不同终端保持一致的表现。
值得注意的是,组件通过 onLayout 事件实时获取自身尺寸,这种动态计算方式适配了鸿蒙分布式布局的特性,无论组件在手机竖屏、平板横屏还是智慧屏分屏模式下,都能自动适配占位高度,避免硬编码尺寸带来的适配漏洞。
步骤条组件
步骤条(Steps)组件是订单流程、任务进度等场景的核心UI元素,其实现充分考虑了 React Native 与鸿蒙的交互适配与视觉统一:
const Steps: React.FC<StepsProps> = ({ items, current, direction = 'horizontal', onChange }) => {
return (
<View style={[styles.stepsContainer, direction === 'vertical' && styles.stepsVertical]}>
{items.map((step, index) => (
<View
key={step.key}
style={[
styles.stepItem,
direction === 'vertical' && styles.stepItemVertical,
index < items.length - 1 && styles.stepItemWithConnector
]}
>
<TouchableOpacity
style={[
styles.stepCircle,
step.status === 'finish' && styles.stepCircleComplete,
step.status === 'process' && styles.stepCircleProcess,
step.status === 'error' && styles.stepCircleError,
current === index && styles.stepCircleCurrent
]}
onPress={() => onChange && onChange(index)}
>
{/* 步骤状态渲染逻辑 */}
</TouchableOpacity>
{/* 步骤内容与连接线渲染 */}
</View>
))}
</View>
);
};
跨端设计的核心体现在三个维度:首先是布局方向适配,支持水平/垂直两种模式,适配鸿蒙不同设备的交互习惯——手机端常用水平步骤条,而平板/智慧屏的竖屏/分屏场景下,垂直步骤条更符合大屏操作逻辑;其次是状态可视化的标准化,通过不同样式类区分步骤状态(完成/进行中/错误/等待),所有样式属性均通过外部可配置方式实现,便于根据鸿蒙系统的主题色进行动态调整;最后是交互响应的一致性,步骤节点支持点击切换,这种交互逻辑在 React Native 和鸿蒙中均通过 TouchableOpacity 实现,保证了点击反馈的原生体验。
在鸿蒙跨端落地时,该组件还可进一步扩展分布式能力,比如将步骤状态同步到多设备,实现手机上操作步骤、智慧屏上展示进度的跨端协同,而当前组件通过 onChange 回调暴露状态变更,为这种扩展预留了灵活的接口。
骨架屏与进度条
骨架屏(Skeleton)和进度条(ProgressBar)作为加载状态与进度展示的核心组件,其实现聚焦于 React Native 动画系统在鸿蒙上的性能优化,核心是区分“原生驱动动画”与“JS驱动动画”的适用场景:
骨架屏
骨架屏的渐变动画采用 Animated.loop 结合 Animated.sequence 实现循环的透明度变化:
const animation = Animated.loop(
Animated.sequence([
Animated.timing(fadeAnim, {
toValue: 1,
duration: 800,
useNativeDriver: true,
}),
Animated.timing(fadeAnim, {
toValue: 0.5,
duration: 800,
useNativeDriver: true,
}),
])
);
这里 useNativeDriver: true 是跨端性能优化的关键——将动画交由原生线程执行,避开 JS 线程的阻塞,这在鸿蒙低性能设备(如入门级智慧屏)上效果尤为明显。同时,骨架屏的宽高、圆角等属性支持数字/字符串混合传入,既兼容 React Native 的 dp 单位,也支持百分比字符串,适配鸿蒙不同屏幕的自适应布局需求。
进度条的插值计算
进度条的核心在于将数值转换为视觉宽度,其实现采用 interpolate 插值方法:
width: animated ? actualProgress.interpolate({
inputRange: [0, 100],
outputRange: ['0%', '100%']
}) : `${Math.min(100, Math.max(0, (value / maxValue) * 100))}%`
这种实现的优势在于:插值计算在原生层完成,性能优于 JS 层的字符串拼接;通过 Math.min/max 限制数值范围,避免进度条宽度超出容器,这在鸿蒙不同屏幕比例的设备上能保证UI完整性。需要注意的是,进度条动画设置 useNativeDriver: false,因为宽度属于布局属性,无法通过原生驱动执行——这是 React Native 动画在鸿蒙适配中必须区分的细节,也是避免动画卡顿的核心要点。
气泡弹出框
气泡弹出框(Popover)是移动端交互的高频组件,其实现难点在于跨端的坐标计算和浮层渲染,尤其是鸿蒙多终端的屏幕尺寸差异带来的定位挑战:
const handleLongPress = (event: any) => {
const { pageX, pageY } = event.nativeEvent;
setAnchorPosition({ x: pageX - 150, y: pageY - 200 });
setShowPopover(true);
};
// 组合动画实现
Animated.parallel([
Animated.spring(scaleAnim, {
toValue: 1,
tension: 300,
friction: 20,
useNativeDriver: true
}),
Animated.timing(fadeAnim, {
toValue: 1,
duration: 200,
useNativeDriver: true
})
]).start();
在跨端适配中,弹出框的坐标计算是核心——通过 pageX/pageY 获取触摸点的绝对坐标,再通过固定偏移量调整弹出位置,这种方式不依赖设备的 DPI、屏幕比例等参数,能在鸿蒙手机、平板、智慧屏上保持弹出框与触发点的相对位置一致。同时,采用 Animated.parallel 实现缩放+渐变的组合动画,且全部启用原生驱动,保证了鸿蒙多终端上动画的流畅性。
浮层渲染方面,组件使用 React Native 的 Modal 组件实现遮罩层,transparent={true} 配置适配了鸿蒙系统的浮层渲染机制,避免出现遮罩层背景异常、层级错乱等问题,保证了跨端浮层交互的一致性。
懒加载图片
懒加载图片(LazyImage)组件解决了 React Native 与鸿蒙跨端场景下的图片加载体验问题,核心是状态分离与容错设计:
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: STICKY_ICONS.notifications }} style={{ width: 30, height: 30, tintColor: '#94a3b8' }} />
</View>
)}
{error && (
// 错误状态渲染
)}
<Image
source={source}
style={StyleSheet.absoluteFill}
onLoad={() => setLoading(false)}
onError={() => {
setLoading(false);
setError(true);
}}
resizeMode="cover"
/>
</View>
);
};
其跨端设计的核心在于:一是状态分离,将加载中、加载失败、加载完成三种状态明确区分,在鸿蒙弱网环境或分布式网络场景下,能给用户清晰的反馈;二是采用 StyleSheet.absoluteFill 实现图片的绝对定位,保证在不同终端上图片容器的尺寸一致性;三是完善的错误处理机制,避免因图片加载失败导致的UI崩溃,这在鸿蒙多设备的资源访问场景中尤为重要(比如智慧屏访问手机端图片资源失败时)。
跨端复合组件
瀑布流通知(WaterfallNoticeItem)是上述所有基础组件的集大成者,展示了 React Native 组件在鸿蒙跨端场景下的组合复用能力:
该组件整合了步骤条的状态管理、进度条的动态展示、懒加载图片的资源加载、气泡弹出框的交互逻辑,其设计思路体现了跨端组件开发的核心原则:单一职责(每个子组件只负责自身功能,瀑布流组件仅负责业务逻辑组合)、状态透传(通知项的进度、阅读状态等数据通过 props 透传,便于接入鸿蒙分布式数据管理)、交互统一(长按弹出菜单的交互逻辑在所有终端保持一致,仅通过样式调整适配不同设备操作习惯)。
在鸿蒙跨端优化中,瀑布流组件还可通过 FlatList 的 numColumns 属性动态调整列数——手机端2列、平板端3列、智慧屏端4列,这种基于设备尺寸的动态适配,正是 React Native 对接鸿蒙多终端生态的典型场景。
这套组件体系在 React Native 适配鸿蒙的过程中,始终遵循以下核心原则,也是跨端开发的通用准则:
原生能力
优先使用 React Native 原生API(Animated、Modal、TouchableOpacity)而非第三方库,保证与鸿蒙系统的兼容性;动画方面严格区分 useNativeDriver 的使用场景,最大化利用原生渲染能力,避免 JS 线程阻塞。
动态适配
所有尺寸、位置、样式均通过动态计算或参数配置实现,避免固定数值;通过 onLayout 事件获取组件尺寸,通过插值计算实现数值到视觉的转换,适配鸿蒙不同屏幕尺寸、分辨率的设备。
状态
采用 React Hooks 管理组件状态,业务数据与UI渲染解耦,便于接入鸿蒙的分布式状态管理,实现多设备间的状态同步;错误处理与加载状态独立管理,保证异常场景下的体验一致性。
容错
图片加载、动画执行、交互响应等环节均设计容错机制,在鸿蒙低性能设备或异常场景下,能优雅降级而非崩溃,保证基础功能可用。
总结
这套 React Native 组件体系不仅实现了粘性布局、步骤条等基础UI组件,更重要的是提供了一套完整的鸿蒙跨端适配思路。核心要点可总结为:
- 布局适配层面,通过占位视图、动态计算、弹性布局保证多终端布局一致性;
- 动效优化层面,合理使用原生驱动动画,区分布局动画与属性动画的实现方式;
- 交互设计层面,保持核心交互逻辑一致,仅调整样式适配不同设备操作习惯;
- 架构设计层面,组件状态与业务数据分离,便于接入鸿蒙分布式能力。
核心实现
粘性布局(Sticky)是现代移动应用中常见的 UI 模式,用于实现导航栏、筛选栏等元素在滚动时固定在屏幕顶部的效果。该实现采用了占位符 + 固定容器的经典方案:
const Sticky: React.FC<StickyProps> = ({ children, offsetTop = 0 }) => {
const [isStuck, setIsStuck] = useState(false);
const [stickyHeight, setStickyHeight] = useState(0);
const onLayout = (event: any) => {
const { height } = event.nativeEvent.layout;
setStickyHeight(height);
};
const onScroll = (event: any) => {
const { y } = event.nativeEvent.contentOffset;
setIsStuck(y > offsetTop);
};
return (
<>
<View
style={[styles.stickyPlaceholder, { height: stickyHeight }]}
onLayout={onLayout}
/>
<Animated.View
style={[
styles.stickyContainer,
{
position: isStuck ? 'fixed' : 'relative',
top: isStuck ? offsetTop : undefined,
zIndex: 1000
}
]}
>
{children}
</Animated.View>
</>
);
};
这种实现方式的技术要点:
- 高度计算:通过
onLayout回调获取粘性内容的实际高度,确保占位符高度准确 - 滚动监测:通过
onScroll回调监测滚动位置,判断是否需要启用粘性效果 - 布局切换:通过
isStuck状态控制粘性容器的定位方式,从relative切换到fixed - 平滑过渡:使用
Animated.View为粘性效果添加平滑的过渡动画 - 层级管理:通过
zIndex: 1000确保粘性内容始终显示在其他内容之上
跨端
在 React Native 和 HarmonyOS 跨端开发中,粘性布局组件需要注意以下兼容性问题:
- 定位属性差异:不同平台对
position: 'fixed'的实现可能存在差异,需要进行平台特定的适配 - 滚动事件处理:不同平台的滚动事件参数可能略有不同,需要统一处理
- 性能优化:在 HarmonyOS 上,频繁的滚动事件可能会影响性能,需要考虑使用节流或防抖优化
核心
步骤条(Steps)组件用于展示多步骤流程的进度和状态,如订单流程、注册流程等。该实现支持水平和垂直两种布局方向:
const Steps: React.FC<StepsProps> = ({ items, current, direction = 'horizontal', onChange }) => {
return (
<View style={[styles.stepsContainer, direction === 'vertical' && styles.stepsVertical]}>
{items.map((step, index) => (
<View
key={step.key}
style={[
styles.stepItem,
direction === 'vertical' && styles.stepItemVertical,
index < items.length - 1 && styles.stepItemWithConnector
]}
>
{/* 步骤圆圈和内容 */}
</View>
))}
</View>
);
};
步骤条组件的技术亮点:
- 状态管理:支持 ‘wait’、‘process’、‘finish’、‘error’ 四种状态,通过不同的样式和图标直观展示
- 布局灵活性:通过
direction属性支持水平和垂直两种布局方向 - 交互性:支持点击步骤切换当前步骤,通过
onChange回调通知父组件 - 视觉连接:通过
stepConnector实现步骤之间的视觉连接,增强流程的连贯性 - 响应式设计:通过条件样式适配不同的布局方向
在跨端开发中,步骤条组件需要注意以下兼容性问题:
- 图标资源:使用的图标资源(如
STICKY_ICONS.home)需要确保在两个平台上都可用 - 布局计算:不同平台的布局计算方式可能存在差异,需要确保在各种屏幕尺寸下都能正确显示
- 触摸反馈:不同平台的触摸反馈机制可能存在差异,需要确保交互体验一致
核心实现原理
骨架屏(Skeleton)是提升用户体验的重要技术,通过在数据加载过程中显示占位元素,减少用户的等待感知:
const Skeleton: React.FC<SkeletonProps> = ({
width = 100,
height = 20,
borderRadius = 4,
animate = true
}) => {
const fadeAnim = useRef(new Animated.Value(0.5)).current;
useEffect(() => {
if (animate) {
const animation = Animated.loop(
Animated.sequence([
Animated.timing(fadeAnim, {
toValue: 1,
duration: 800,
useNativeDriver: true,
}),
Animated.timing(fadeAnim, {
toValue: 0.5,
duration: 800,
useNativeDriver: true,
}),
])
);
animation.start();
return () => animation.stop();
}
}, [animate]);
return (
<Animated.View
style={[
styles.skeleton,
{
width,
height,
borderRadius,
opacity: animate ? fadeAnim : 1,
}
]}
/>
);
};
骨架屏组件的技术亮点:
- 平滑动画:使用
Animated.loop和Animated.sequence实现了淡入淡出的循环动画 - 性能优化:通过
useNativeDriver: true让动画在原生线程执行,提升性能 - 生命周期管理:在
useEffect的清理函数中停止动画,避免内存泄漏 - 可配置性:支持自定义宽度、高度、圆角和是否启用动画
- 灵活性:可以组合使用多个 Skeleton 组件构建复杂的骨架屏布局
跨端兼容性
在跨端开发中,骨架屏组件需要注意以下兼容性问题:
- 动画性能:不同平台的动画性能可能存在差异,需要进行性能测试和优化
- 样式适配:不同平台的默认样式可能存在差异,需要确保骨架屏的视觉效果一致
- 内存管理:在 HarmonyOS 上,需要特别注意动画的内存管理,避免内存泄漏
核心实现
进度条(ProgressBar)用于可视化展示任务的完成情况,如文件上传、下载进度等:
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]);
// 渲染逻辑
};
进度条组件的技术亮点:
- 动画效果:使用
Animated.timing实现平滑的进度更新动画 - 值映射:通过
interpolate方法将动画值映射到宽度百分比 - 边界处理:使用
Math.min(100, Math.max(0, ...))确保进度值始终在合理范围内 - 可配置性:支持自定义高度、颜色、背景色、圆角和是否显示百分比
- 性能优化:根据
animated属性选择不同的更新方式,平衡性能和用户体验
跨端
在跨端开发中,进度条组件需要注意以下兼容性问题:
- 动画 API 差异:不同平台的动画 API 可能存在差异,需要确保动画效果一致
- 样式计算:不同平台的样式计算方式可能存在差异,需要确保进度条的显示准确
- 性能优化:在 HarmonyOS 上,需要考虑动画性能,避免过度动画影响用户体验
组件设计模式
- 组合式设计:所有组件都采用了组合式设计,通过
children属性实现灵活的内容定制 - 可配置性优先:所有组件都提供了丰富的配置选项,通过 TypeScript 接口定义了清晰的 props 类型
- 状态驱动渲染:组件的渲染逻辑由状态驱动,通过 props 控制组件的外观和行为
- 动画增强体验:适当使用动画效果增强用户体验,如骨架屏的淡入淡出、进度条的平滑过渡
性能优化策略
- 动画性能:合理使用
useNativeDriver,对于可以在原生线程执行的动画(如透明度、缩放)启用硬件加速 - 内存管理:在
useEffect的清理函数中停止动画、取消订阅等,避免内存泄漏 - 渲染优化:使用
React.memo、useMemo等优化渲染性能,避免不必要的重渲染 - 事件处理:对于频繁触发的事件(如滚动事件),考虑使用节流或防抖优化
代码质量保障
- TypeScript 类型系统:充分利用 TypeScript 的类型系统,定义清晰的接口和类型,减少运行时错误
- 模块化设计:将不同功能的组件拆分为独立的模块,提高代码的可维护性和可复用性
- 命名规范:使用清晰、语义化的命名,提高代码的可读性
- 注释文档:添加适当的注释,解释组件的功能、实现原理和使用方法
粘性布局组件优化
- 性能优化:对于频繁滚动的场景,可以考虑使用
useMemo缓存样式计算结果,减少不必要的重渲染 - 平台适配:在 HarmonyOS 上,
position: 'fixed'的行为可能与 React Native 有所不同,需要进行平台特定的适配 - 边界处理:添加对边界情况的处理,如粘性内容高度为 0 时的情况
- 事件处理:考虑使用
useCallback缓存事件处理函数,减少不必要的函数创建
步骤条组件优化
- 响应式设计:在水平模式下,添加对小屏幕设备的响应式处理,避免步骤内容溢出
- 动画增强:为步骤切换添加平滑的动画效果,提升用户体验
- 无障碍支持:添加适当的无障碍标签,提高应用的可访问性
- 性能优化:对于长步骤列表,考虑使用虚拟滚动优化性能
本文分析的 React Native 跨端通用组件库展示了现代移动应用开发中常用的几种核心组件的实现原理和技术亮点。这些组件不仅功能完整、交互友好,而且在设计时充分考虑了跨端兼容性,能够在 React Native 和 HarmonyOS 平台上良好运行。
通过学习这些组件的实现,我们可以了解到:
- 组件设计的最佳实践:如何设计可配置、可复用的组件
- 性能优化的策略:如何优化动画性能、减少内存泄漏
- 跨端开发的技巧:如何处理不同平台的差异,实现统一的用户体验
- 代码质量的保障:如何使用 TypeScript、模块化设计等提高代码质量
真实演示案例代码:
// 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 sticky components
const STICKY_ICONS = {
home: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAACXBIWXMAAAsTAAALEwEAmpwYAAAFHmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDIgNzkuMTYwOTI0LCAyMDE3LzA3LzEzLTAxOjA2OjM5ICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOCAoV2luZG93cykiIHhtcDpDcmVhdGVEYXRlPSIyMDE5LTAxLTAzVDE2OjU1OjQ5KzA4OjAwIiB4bXA6TW9kaWZ5RGF0ZT0iMjAxOS0wMS0wM1QxNjo1NjoxNCswODowMCIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAxOS0wMS0wM1QxNjo1NjoxNCswODowMCIgZGM6Zm9ybWF0PSJpbWFnZS9wbmciIHBob3Rvc2hvcDpDb2xvck1vZGU9IjMiIHBob3Rvc2hvcDpJQ0NQcm9maWxlPSJzUkdCIElFQzYxOTY2LTIuMSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDoyZjA5Y2Y0OS01YjVlLWY5NDctYjg3Yi0yZDM5YjQxMjQyZWIiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MmYwOWNmNDktNWI1ZS1mOTQ3LWI4N2ItMmQzOWI0MTI0MmViIiB4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ9InhtcC5kaWQ6MmYwOWNmNDktNWI1ZS1mOTQ3LWI4N2ItMmQzOWI0MTI0MmViIj4gPHhtcE1NOkhpc3Rvcnk+IDxyZGY6U2VxPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0iY3JlYXRlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDoyZjA5Y2Y0OS01YjVlLWY5NDctYjg3Yi0yZDM5YjQxMjQyZWIiIHN0RXZ0OndoZW49IjIwMTktMDEtMDNUMTY6NTU6NDlaIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOCAoV2luZG93cykiLz4gPC9yZGY6U2VxPiA8L3htcE1NOkhpc3Rvcnk+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+Af/+/fz7+vn49/b19PPy8fDv7u3s6+rp6Ofm5eTj4uHg397d3Nva2djX1tXU09LR0M/OzczLysnIx8bFxMPCwcC/vr28u7q5uLe2tbSzsrGwr66trKuqqainpqWko6KhoJ+enZybmpmYl5aVlJOSkZCPjo2Mi4qJiIeGhYSDgoGAf359fHt6eXh3dnV0c3JxcG9ubWxramloZ2ZlZGNiYWBfXl1cW1pZWFdWVVRTUlFQT05NTEtKSUhHRkVEQ0JBQD8+PTw7Ojk4NzY1NDMyMTAvLi0sKyopKCcmJSQjIiEgHx4dHBsaGRgXFhUUExIREA8ODQwLCgkIBwYFBAMCAQAA//+gB5ZbAAAJRElEQVRoBe1ZaWxUVRQ+97030+k6U6Z0hk5npksptKW0bFJAWRB3UEFFjQuJKIoLJCZqYhRjNC5RFCMuaKJREhMXjAuKggsuRI0bGkEpWymltNBS2ulMZ9qZmfbe9+699715MzPTmdLS6STnfPPee+fc755zz3nnPcNwHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMx......',
search: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAACXBIWXMAAAsTAAALEwEAmpwYAAAFHmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDIgNzkuMTYwOTI0LCAyMDE3LzA3LzEzLTAxOjA2OjM5ICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOCAoV2luZG93cykiIHhtcDpDcmVhdGVEYXRlPSIyMDE5LTAxLTAzVDE2OjU2OjIwKzA4OjAwIiB4bXA6TW9kaWZ5RGF0ZT0iMjAxOS0wMS0wM1QxNjo1Njo0NSswODowMCIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAxOS0wMS0wM1QxNjo1Njo0NSswODowMCIgZGM6Zm9ybWF0PSJpbWFnZS9wbmciIHBob3Rvc2hvcDpDb2xvck1vZGU9IjMiIHBob3Rvc2hvcDpJQ0NQcm9maWxlPSJzUkdCIElFQzYxOTY2LTIuMSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDoyZjE5Y2Y0OS01YjVlLWY5NDctYjg3Yi0yZDM5YjQxMjQyZWIiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MmYxOWNmNDktNWI1ZS1mOTQ3LWI4N2ItMmQzOWI0MTI0MmViIiB4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ9InhtcC5kaWQ6MmYxOWNmNDktNWI1ZS1mOTQ3LWI4N2ItMmQzOWI0MTI0MmViIj4gPHhtcE1NOkhpc3Rvcnk+IDxyZGY6U2VxPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0iY3JlYXRlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDoyZjE5Y2Y0OS01YjVlLWY5NDctYjg3Yi0yZDM5YjQxMjQyZWIiIHN0RXZ0OndoZW49IjIwMTktMDEtMDNUMTY6NTY6MjBaIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOCAoV2luZG93cykiLz4gPC9yZGY6U2VxPiA8L3htcE1NOkhpc3Rvcnk+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+Af/+/fz7+vn49/b19PPy8fDv7u3s6+rp6Ofm5eTj4uHg397d3Nva2djX1tXU09LR0M/OzczLysnIx8bFxMPCwcC/vr28u7q5uLe2tbSzsrGwr66trKuqqainpqWko6KhoJ+enZybmpmYl5aVlJOSkZCPjo2Mi4qJiIeGhYSDgoGAf359fHt6eXh3dnV0c3JxcG9ubWxramloZ2ZlZGNiYWBfXl1cW1pZWFdWVVRTUlFQT05NTEtKSUhHRkVEQ0JBQD8+PTw7Ojk4NzY1NDMyMTAvLi0sKyopKCcmJSQjIiEgHx4dHBsaGRgXFhUUExIREA8ODQwLCgkIBwYFBAMCAQAA//+gB5ZbAAAJRElEQVRoBe1ZaWxUVRQ+97030+k6U6Z0hk5npksptKW0bFJAWRB3UEFFjQuJKIoLJCZqYhRjNC5RFCMuaKJREhMXjAuKggsuRI0bGkEpWymltNBS2ulMZ9qZmfbe9+699715MzPTmdLS6STnfPPee+fc755zz3nnPcNwHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMx......',
notifications: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAACXBIWXMAAAsTAAALEwEAmpwYAAAFHmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDIgNzkuMTYwOTI0LCAyMDE3LzA3LzEzLTAxOjA2OjM5ICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOCAoV2luZG93cykiIHhtcDpDcmVhdGVEYXRlPSIyMDE5LTAxLTAzVDE2OjU2OjUxKzA4OjAwIiB4bXA6TW9kaWZ5RGF0ZT0iMjAxOS0wMS0wM1QxNjo1NzoxNiswODowMCIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAxOS0wMS0wM1QxNjo1NzoxNiswODowMCIgZGM6Zm9ybWF0PSJpbWFnZS9wbmciIHBob3Rvc2hvcDpDb2xvck1vZGU9IjMiIHBob3Rvc2hvcDpJQ0NQcm9maWxlPSJzUkdCIElFQzYxOTY2LTIuMSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDoyZjI5Y2Y0OS01YjVlLWY5NDctYjg3Yi0yZDM5YjQxMjQyZWIiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MmYyOWNmNDktNWI1ZS1mOTQ3LWI4N2ItMmQzOWI0MTI0MmViIiB4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ9InhtcC5kaWQ6MmYyOWNmNDktNWI1ZS1mOTQ3LWI4N2ItMmQzOWI0MTI0MmViIj4gPHhtcE1NOkhpc3Rvcnk+IDxyZGY6U2VxPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0iY3JlYXRlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDoyZjI5Y2Y0OS01YjVlLWY5NDctYjg3Yi0yZDM5YjQxMjQyZWIiIHN0RXZ0OndoZW49IjIwMTktMDEtMDNUMTY6NTY6NTFaIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOCAoV2luZG93cykiLz4gPC9yZGY6U2VxPiA8L3htcE1NOkhpc3Rvcnk+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+Af/+/fz7+vn49/b19PPy8fDv7u3s6+rp6Ofm5eTj4uHg397d3Nva2djX1tXU09LR0M/OzczLysnIx8bFxMPCwcC/vr28u7q5uLe2tbSzsrGwr66trKuqqainpqWko6KhoJ+enZybmpmYl5aVlJOSkZCPjo2Mi4qJiIeGhYSDgoGAf359fHt6eXh3dnV0c3JxcG9ubWxramloZ2ZlZGNiYWBfXl1cW1pZWFdWVVRTUlFQT05NTEtKSUhHRkVEQ0JBQD8+PTw7Ojk4NzY1NDMyMTAvLi0sKyopKCcmJSQjIiEgHx4dHBsaGRgXFhUUExIREA8ODQwLCgkIBwYFBAMCAQAA//+gB5ZbAAAJRElEQVRoBe1ZaWxUVRQ+97030+k6U6Z0hk5npksptKW0bFJAWRB3UEFFjQuJKIoLJCZqYhRjNC5RFCMuaKJREhMXjAuKggsuRI0bGkEpWymltNBS2ulMZ9qZmfbe9+699715MzPTmdLS6STnfPPee+fc755zz3nnPcNwHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMx......',
user: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAACXBIWXMAAAsTAAALEwEAmpwYAAAFHmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDIgNzkuMTYwOTI0LCAyMDE3LzA3LzEzLTAxOjA2OjM5ICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOCAoV2luZG93cykiIHhtcDpDcmVhdGVEYXRlPSIyMDE5LTAxLTAzVDE2OjU3OjIyKzA4OjAwIiB4bXA6TW9kaWZ5RGF0ZT0iMjAxOS0wMS0wM1QxNjo1Nzo0OCswODowMCIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAxOS0wMS0wM1QxNjo1Nzo0OCswODowMCIgZGM6Zm9ybWF0PSJpbWFnZS9wbmciIHBob3Rvc2hvcDpDb2xvck1vZGU9IjMiIHBob3Rvc2hvcDpJQ0NQcm9maWxlPSJzUkdCIElFQzYxOTY2LTIuMSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDoyZjM5Y2Y0OS01YjVlLWY5NDctYjg3Yi0yZDM5YjQxMjQyZWIiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MmYzOWNmNDktNWI1ZS1mOTQ3LWI4N2ItMmQzOWI0MTI0MmViIiB4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ9InhtcC5kaWQ6MmYzOWNmNDktNWI1ZS1mOTQ3LWI4N2ItMmQzOWI0MTI0MmViIj4gPHhtcE1NOkhpc3Rvcnk+IDxyZGY6U2VxPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0iY3JlYXRlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDoyZjM5Y2Y0OS01YjVlLWY5NDctYjg3Yi0yZDM5YjQxMjQyZWIiIHN0RXZ0OndoZW49IjIwMTktMDEtMDNUMTY6NTc6MjJaIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOCAoV2luZG93cykiLz4gPC9yZGY6U2VxPiA8L3htcE1NOkhpc3Rvcnk+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+Af/+/fz7+vn49/b19PPy8fDv7u3s6+rp6Ofm5eTj4uHg397d3Nva2djX1tXU09LR0M/OzczLysnIx8bFxMPCwcC/vr28u7q5uLe2tbSzsrGwr66trKuqqainpqWko6KhoJ+enZybmpmYl5aVlJOSkZCPjo2Mi4qJiIeGhYSDgoGAf359fHt6eXh3dnV0c3JxcG9ubWxramloZ2ZlZGNiYWBfXl1cW1pZWFdWVVRTUlFQT05NTEtKSUhHRkVEQ0JBQD8+PTw7Ojk4NzY1NDMyMTAvLi0sKyopKCcmJSQjIiEgHx4dHBsaGRgXFhUUExIREA8ODQwLCgkIBwYFBAMCAQAA//+gB5ZbAAAJRElEQVRoBe1ZaWxUVRQ+97030+k6U6Z0hk5npksptKW0bFJAWRB3UEFFjQuJKIoLJCZqYhRjNC5RFCMuaKJREhMXjAuKggsuRI0bGkEpWymltNBS2ulMZ9qZmfbe9+699715MzPTmdLS6STnfPPee+fc755zz3nnPcNwHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMx......',
menu: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAACXBIWXMAAAsTAAALEwEAmpwYAAAFHmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDIgNzkuMTYwOTI0LCAyMDE3LzA3LzEzLTAxOjA2OjM5ICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOCAoV2luZG93cykiIHhtcDpDcmVhdGVEYXRlPSIyMDE5LTAxLTAzVDE2OjU3OjUzKzA4OjAwIiB4bXA6TW9kaWZ5RGF0ZT0iMjAxOS0wMS0wM1QxNjo1ODoxOCswODowMCIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAxOS0wMS0wM1QxNjo1ODoxOCswODowMCIgZGM6Zm9ybWF0PSJpbWFnZS9wbmciIHBob3Rvc2hvcDpDb2xvck1vZGU9IjMiIHBob3Rvc2hvcDpJQ0NQcm9maWxlPSJzUkdCIElFQzYxOTY2LTIuMSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDoyZjQ5Y2Y0OS01YjVlLWY5NDctYjg3Yi0yZDM5YjQxMjQyZWIiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MmY0OWNmNDktNWI1ZS1mOTQ3LWI4N2ItMmQzOWI0MTI0MmViIiB4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ9InhtcC5kaWQ6MmY0OWNmNDktNWI1ZS1mOTQ3LWI4N2ItMmQzOWI0MTI0MmViIj4gPHhtcE1NOkhpc3Rvcnk+IDxyZGY6U2VxPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0iY3JlYXRlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDoyZjQ5Y2Y0OS01YjVlLWY5NDctYjg3Yi0yZDM5YjQxMjQyZWIiIHN0RXZ0OndoZW49IjIwMTktMDEtMDNUMTY6NTc6NTNaIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOCAoV2luZG93cykiLz4gPC9yZGY6U2VxPiA8L3htcE1NOkhpc3Rvcnk+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+Af/+/fz7+vn49/b19PPy8fDv7u3s6+rp6Ofm5eTj4uHg397d3Nva2djX1tXU09LR0M/OzczLysnIx8bFxMPCwcC/vr28u7q5uLe2tbSzsrGwr66trKuqqainpqWko6KhoJ+enZybmpmYl5aVlJOSkZCPjo2Mi4qJiIeGhYSDgoGAf359fHt6eXh3dnV0c3JxcG9ubWxramloZ2ZlZGNiYWBfXl1cW1pZWFdWVVRTUlFQT05NTEtKSUhHRkVEQ0JBQD8+PTw7Ojk4NzY1NDMyMTAvLi0sKyopKCcmJSQjIiEgHx4dHBsaGRgXFhUUExIREA8ODQwLCgkIBwYFBAMCAQAA//+gB5ZbAAAJRElEQVRoBe1ZaWxUVRQ+97030+k6U6Z0hk5npksptKW0bFJAWRB3UEFFjQuJKIoLJCZqYhRjNC5RFCMuaKJREhMXjAuKggsuRI0bGkEpWymltNBS2ulMZ9qZmfbe9+699715MzPTmdLS6STnfPPee+fc755zz3nnPcNwHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMx......',
settings: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAACXBIWXMAAAsTAAALEwEAmpwYAAAFHmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDIgNzkuMTYwOTI0LCAyMDE3LzA3LzEzLTAxOjA2OjM5ICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOCAoV2luZG93cykiIHhtcDpDcmVhdGVEYXRlPSIyMDE5LTAxLTAzVDE2OjU4OjI0KzA4OjAwIiB4bXA6TW9kaWZ5RGF0ZT0iMjAxOS0wMS0wM1QxNjo1ODo0OSswODowMCIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAxOS0wMS0wM1QxNjo1ODo0OSswODowMCIgZGM6Zm9ybWF0PSJpbWFnZS9wbmciIHBob3Rvc2hvcDpDb2xvck1vZGU9IjMiIHBob3Rvc2hvcDpJQ0NQcm9maWxlPSJzUkdCIElFQzYxOTY2LTIuMSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDoyZjU5Y2Y0OS01YjVlLWY5NDctYjg3Yi0yZDM5YjQxMjQyZWIiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MmY1OWNmNDktNWI1ZS1mOTQ3LWI4N2ItMmQzOWI0MTI0MmViIiB4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ9InhtcC5kaWQ6MmY1OWNmNDktNWI1ZS1mOTQ3LWI4N2ItMmQzOWI0MTI0MmViIj4gPHhtcE1NOkhpc3Rvcnk+IDxyZGY6U2VxPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0iY3JlYXRlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDoyZjU5Y2Y0OS01YjVlLWY5NDctYjg3Yi0yZDM5YjQxMjQyZWIiIHN0RXZ0OndoZW49IjIwMTktMDEtMDNUMTY6NTg6MjRaIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOCAoV2luZG93cykiLz4gPC9yZGY6U2VxPiA8L3htcE1NOkhpc3Rvcnk+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+Af/+/fz7+vn49/b19PPy8fDv7u3s6+rp6Ofm5eTj4uHg397d3Nva2djX1tXU09LR0M/OzczLysnIx8bFxMPCwcC/vr28u7q5uLe2tbSzsrGwr66trKuqqainpqWko6KhoJ+enZybmpmYl5aVlJOSkZCPjo2Mi4qJiIeGhYSDgoGAf359fHt6eXh3dnV0c3JxcG9ubWxramloZ2ZlZGNiYWBfXl1cW1pZWFdWVVRTUlFQT05NTEtKSUhHRkVEQ0JBQD8+PTw7Ojk4NzY1NDMyMTAvLi0sKyopKCcmJSQjIiEgHx4dHBsaGRgXFhUUExIREA8ODQwLCgkIBwYFBAMCAQAA//+gB5ZbAAAJRElEQVRoBe1ZaWxUVRQ+97030+k6U6Z0hk5npksptKW0bFJAWRB3UEFFjQuJKIoLJCZqYhRjNC5RFCMuaKJREhMXjAuKggsuRI0bGkEpWymltNBS2ulMZ9qZmfbe9+699715MzPTmdLS6STnfPPee+fc755zz3nnPcNwHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMx......',
share: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAACXBIWXMAAAsTAAALEwEAmpwYAAAFHmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDIgNzkuMTYwOTI0LCAyMDE3LzA3LzEzLTAxOjA2OjM5ICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOCAoV2luZG93cykiIHhtcDpDcmVhdGVEYXRlPSIyMDE5LTAxLTAzVDE2OjU4OjU1KzA4OjAwIiB4bXA6TW9kaWZ5RGF0ZT0iMjAxOS0wMS0wM1QxNjo1OToxOSswODowMCIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAxOS0wMS0wM1QxNjo1OToxOSswODowMCIgZGM6Zm9ybWF0PSJpbWFnZS9wbmciIHBob3Rvc2hvcDpDb2xvck1vZGU9IjMiIHBob3Rvc2hvcDpJQ0NQcm9maWxlPSJzUkdCIElFQzYxOTY2LTIuMSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDoyZjY5Y2Y0OS01YjVlLWY5NDctYjg3Yi0yZDM5YjQxMjQyZWIiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MmY2OWNmNDktNWI1ZS1mOTQ3LWI4N2ItMmQzOWI0MTI0MmViIiB4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ9InhtcC5kaWQ6MmY2OWNmNDktNWI1ZS1mOTQ3LWI4N2ItMmQzOWI0MTI0MmViIj4gPHhtcE1NOkhpc3Rvcnk+IDxyZGY6U2VxPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0iY3JlYXRlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDoyZjY5Y2Y0OS01YjVlLWY5NDctYjg3Yi0yZDM5YjQxMjQyZWIiIHN0RXZ0OndoZW49IjIwMTktMDEtMDNUMTY6NTg6NTVaIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOCAoV2luZG93cykiLz4gPC9yZGY6U2VxPiA8L3htcE1NOkhpc3Rvcnk+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+Af/+/fz7+vn49/b19PPy8fDv7u3s6+rp6Ofm5eTj4uHg397d3Nva2djX1tXU09LR0M/OzczLysnIx8bFxMPCwcC/vr28u7q5uLe2tbSzsrGwr66trKuqqainpqWko6KhoJ+enZybmpmYl5aVlJOSkZCPjo2Mi4qJiIeGhYSDgoGAf359fHt6eXh3dnV0c3JxcG9ubWxramloZ2ZlZGNiYWBfXl1cW1pZWFdWVVRTUlFQT05NTEtKSUhHRkVEQ0JBQD8+PTw7Ojk4NzY1NDMyMTAvLi0sKyopKCcmJSQjIiEgHx4dHBsaGRgXFhUUExIREA8ODQwLCgkIBwYFBAMCAQAA//+gB5ZbAAAJRElEQVRoBe1ZaWxUVRQ+97030+k6U6Z0hk5npksptKW0bFJAWRB3UEFFjQuJKIoLJCZqYhRjNC5RFCMuaKJREhMXjAuKggsuRI0bGkEpWymltNBS2ulMZ9qZmfbe9+699715MzPTmdLS6STnfPPee+fc755zz3nnPcNwHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMdxHMx......'
};
// 粘性布局组件
interface StickyProps {
children: React.ReactNode;
offsetTop?: number;
}
const Sticky: React.FC<StickyProps> = ({ children, offsetTop = 0 }) => {
const [isStuck, setIsStuck] = useState(false);
const [stickyHeight, setStickyHeight] = useState(0);
const onLayout = (event: any) => {
const { height } = event.nativeEvent.layout;
setStickyHeight(height);
};
const onScroll = (event: any) => {
const { y } = event.nativeEvent.contentOffset;
setIsStuck(y > offsetTop);
};
return (
<>
<View
style={[styles.stickyPlaceholder, { height: stickyHeight }]}
onLayout={onLayout}
/>
<Animated.View
style={[
styles.stickyContainer,
{
position: isStuck ? 'fixed' : 'relative',
top: isStuck ? offsetTop : undefined,
zIndex: 1000
}
]}
>
{children}
</Animated.View>
</>
);
};
// 步骤条组件
interface StepItem {
key: string;
title: string;
description: string;
status: 'wait' | 'process' | 'finish' | 'error';
}
interface StepsProps {
items: StepItem[];
current: number;
direction?: 'horizontal' | 'vertical';
onChange?: (current: number) => void;
}
const Steps: React.FC<StepsProps> = ({ items, current, direction = 'horizontal', onChange }) => {
return (
<View style={[styles.stepsContainer, direction === 'vertical' && styles.stepsVertical]}>
{items.map((step, index) => (
<View
key={step.key}
style={[
styles.stepItem,
direction === 'vertical' && styles.stepItemVertical,
index < items.length - 1 && styles.stepItemWithConnector
]}
>
<TouchableOpacity
style={[
styles.stepCircle,
step.status === 'finish' && styles.stepCircleComplete,
step.status === 'process' && styles.stepCircleProcess,
step.status === 'error' && styles.stepCircleError,
current === index && styles.stepCircleCurrent
]}
onPress={() => onChange && onChange(index)}
>
{step.status === 'finish' ? (
<Image source={{ uri: STICKY_ICONS.home }} style={styles.stepIcon} />
) : step.status === 'error' ? (
<Image source={{ uri: STICKY_ICONS.settings }} style={styles.stepIcon} />
) : step.status === 'process' ? (
<Text style={styles.stepNumber}>{index + 1}</Text>
) : (
<Text style={styles.stepNumber}>{index + 1}</Text>
)}
</TouchableOpacity>
<View style={styles.stepContent}>
<Text style={[
styles.stepTitle,
current === index && styles.stepTitleCurrent
]}>
{step.title}
</Text>
<Text style={styles.stepDescription}>{step.description}</Text>
</View>
{direction === 'horizontal' && index < items.length - 1 && (
<View style={styles.stepConnector} />
)}
</View>
))}
</View>
);
};
// 骨架屏组件
interface SkeletonProps {
width?: number | string;
height?: number | string;
borderRadius?: number;
animate?: boolean;
}
const Skeleton: React.FC<SkeletonProps> = ({
width = 100,
height = 20,
borderRadius = 4,
animate = true
}) => {
const fadeAnim = useRef(new Animated.Value(0.5)).current;
useEffect(() => {
if (animate) {
const animation = Animated.loop(
Animated.sequence([
Animated.timing(fadeAnim, {
toValue: 1,
duration: 800,
useNativeDriver: true,
}),
Animated.timing(fadeAnim, {
toValue: 0.5,
duration: 800,
useNativeDriver: true,
}),
])
);
animation.start();
return () => animation.stop();
}
}, [animate]);
return (
<Animated.View
style={[
styles.skeleton,
{
width,
height,
borderRadius,
opacity: animate ? fadeAnim : 1,
}
]}
/>
);
};
// 进度条组件
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: STICKY_ICONS.notifications }} style={{ width: 30, height: 30, tintColor: '#94a3b8' }} />
</View>
)}
{error && (
<View style={[StyleSheet.absoluteFill, { backgroundColor: '#dc2626', justifyContent: 'center', alignItems: 'center' }]}>
<Image source={{ uri: STICKY_ICONS.settings }} 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: STICKY_ICONS.search,
action: () => {
alert(`正在查看 "${notice.title}" 的详情`);
}
},
{
id: 'share',
label: '分享',
icon: STICKY_ICONS.share,
action: () => {
alert(`正在分享 "${notice.title}"`);
}
},
{
id: 'more',
label: '更多操作',
icon: STICKY_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: STICKY_ICONS.settings }} style={styles.waterfallNoticeActionIcon} />
<Text style={styles.waterfallNoticeActionText}>设置</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.waterfallNoticeActionButton}>
<Image source={{ uri: STICKY_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 STICKY_ICONS.home;
case 'warning': return STICKY_ICONS.settings;
case 'info': return STICKY_ICONS.notifications;
case 'discount': return STICKY_ICONS.user;
default: return STICKY_ICONS.notifications;
}
};
// 主应用组件
const App = () => {
const [currentStep, setCurrentStep] = useState(1);
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: '您的包裹已发出,预计2天内送达',
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'
}
]);
const steps = [
{
key: '1',
title: '下单',
description: '订单已成功提交',
status: currentStep > 0 ? 'finish' : currentStep === 0 ? 'process' : 'wait'
},
{
key: '2',
title: '支付',
description: '等待支付完成',
status: currentStep > 1 ? 'finish' : currentStep === 1 ? 'process' : 'wait'
},
{
key: '3',
title: '发货',
description: '商品正在配送中',
status: currentStep > 2 ? 'finish' : currentStep === 2 ? 'process' : 'wait'
},
{
key: '4',
title: '收货',
description: '等待您签收',
status: currentStep > 3 ? 'finish' : currentStep === 3 ? 'process' : 'wait'
},
{
key: '5',
title: '评价',
description: '期待您的评价',
status: currentStep > 4 ? 'finish' : currentStep === 4 ? 'process' : 'wait'
}
];
// 模拟进度更新
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}>
<Sticky offsetTop={0}>
<View style={styles.stickyHeader}>
<Text style={styles.stickyHeaderTitle}>粘性布局示例</Text>
<Text style={styles.stickyHeaderSubtitle}>始终固定在屏幕顶部</Text>
</View>
</Sticky>
<ScrollView
contentContainerStyle={styles.contentContainer}
onScroll={(event) => {}}
scrollEventThrottle={16}
>
<View style={styles.stepsSection}>
<Text style={styles.sectionTitle}>订单流程示例</Text>
<Steps
items={steps}
current={currentStep}
onChange={setCurrentStep}
/>
</View>
<View style={styles.skeletonSection}>
<Text style={styles.sectionTitle}>骨架屏示例</Text>
<View style={styles.skeletonExample}>
<Skeleton width={60} height={60} borderRadius={30} />
<View style={styles.skeletonTextContainer}>
<Skeleton width={150} height={20} style={styles.skeletonText} />
<Skeleton width={120} height={16} style={styles.skeletonText} />
</View>
</View>
<View style={styles.skeletonProgressBar}>
<Skeleton width="100%" height={8} borderRadius={4} />
</View>
</View>
<View style={styles.progressBarSection}>
<Text style={styles.sectionTitle}>进度条示例</Text>
<View style={styles.progressExample}>
<Text style={styles.progressExampleLabel}>处理进度</Text>
<ProgressBar value={currentStep * 25} maxValue={100} color="#3b82f6" />
</View>
<View style={styles.progressExample}>
<Text style={styles.progressExampleLabel}>完成率</Text>
<ProgressBar value={75} maxValue={100} color="#10b981" />
</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}>长按触发气泡菜单</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',
},
stickyPlaceholder: {
// Placeholder for sticky element
},
stickyContainer: {
backgroundColor: '#1e293b',
paddingVertical: 15,
paddingHorizontal: 20,
borderBottomWidth: 1,
borderBottomColor: '#334155',
},
stickyHeader: {
alignItems: 'center',
},
stickyHeaderTitle: {
fontSize: 20,
fontWeight: '700',
color: '#f1f5f9',
marginBottom: 5,
},
stickyHeaderSubtitle: {
fontSize: 14,
color: '#94a3b8',
textAlign: 'center',
},
contentContainer: {
padding: 16,
paddingTop: 0, // Since we have a sticky header
},
stepsSection: {
marginBottom: 20,
},
sectionTitle: {
fontSize: 22,
fontWeight: '700',
color: '#f1f5f9',
marginBottom: 15,
paddingLeft: 10,
borderLeftWidth: 4,
borderLeftColor: '#3b82f6',
},
stepsContainer: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'flex-start',
marginBottom: 20,
},
stepsVertical: {
flexDirection: 'column',
},
stepItem: {
flex: 1,
alignItems: 'center',
position: 'relative',
},
stepItemVertical: {
flexDirection: 'row',
alignItems: 'flex-start',
marginBottom: 20,
},
stepItemWithConnector: {
paddingRight: 20,
},
stepCircle: {
width: 40,
height: 40,
borderRadius: 20,
backgroundColor: '#334155',
justifyContent: 'center',
alignItems: 'center',
zIndex: 2,
borderWidth: 2,
borderColor: '#475569',
},
stepCircleComplete: {
backgroundColor: '#10b981',
borderColor: '#10b981',
},
stepCircleProcess: {
backgroundColor: '#3b82f6',
borderColor: '#3b82f6',
},
stepCircleError: {
backgroundColor: '#ef4444',
borderColor: '#ef4444',
},
stepCircleCurrent: {
backgroundColor: '#f59e0b',
borderColor: '#f59e0b',
},
stepIcon: {
width: 20,
height: 20,
tintColor: '#ffffff',
},
stepNumber: {
color: '#f1f5f9',
fontSize: 16,
fontWeight: 'bold',
},
stepContent: {
alignItems: 'center',
marginTop: 8,
maxWidth: 80,
},
stepTitle: {
color: '#cbd5e1',
fontSize: 14,
fontWeight: '600',
textAlign: 'center',
marginBottom: 4,
},
stepTitleCurrent: {
color: '#f59e0b',
},
stepDescription: {
color: '#94a3b8',
fontSize: 12,
textAlign: 'center',
},
stepConnector: {
position: 'absolute',
top: 20,
left: '100%',
width: '100%',
height: 2,
backgroundColor: '#334155',
},
skeletonSection: {
marginBottom: 20,
},
skeletonExample: {
flexDirection: 'row',
alignItems: 'center',
marginBottom: 20,
padding: 15,
backgroundColor: '#1e293b',
borderRadius: 12,
},
skeletonTextContainer: {
marginLeft: 15,
flex: 1,
},
skeletonText: {
marginBottom: 8,
},
skeletonProgressBar: {
padding: 15,
backgroundColor: '#1e293b',
borderRadius: 12,
borderWidth: 1,
borderColor: '#334155',
},
progressBarSection: {
marginBottom: 20,
},
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',
},
skeleton: {
backgroundColor: '#334155',
marginRight: 8,
},
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+鸿蒙跨端开发的组件体系,包含粘性布局、步骤条、骨架屏等核心组件。这些组件针对鸿蒙多终端特性进行了优化设计:粘性布局通过动态占位视图和fixed定位保证跨端稳定性;步骤条支持水平/垂直布局适配不同设备;骨架屏和进度条区分原生/JS驱动动画以优化性能;气泡弹窗通过绝对坐标计算实现多终端准确定位。组件设计充分利用React Native特性,同时考虑鸿蒙分布式能力和多终端适配需求,在保证一致体验的同时发挥各平台原生优势。
欢迎大家加入开源鸿蒙跨平台开发者社区,一起共建开源鸿蒙跨平台生态。
更多推荐



所有评论(0)