【OpenHarmony】React Native实战+Timeline时间轴组件
时间轴(Timeline)是一种将时间序列信息可视化展示的UI组件,广泛应用于项目管理、历史记录、用户活动追踪等场景。在React Native生态中,Timeline并非内置组件,需要基于ScrollView和FlatList等基础组件自行构建
·
OpenHarmony实战:React Native实现Timeline时间轴组件

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
概述
时间轴(Timeline)是一种将时间序列信息可视化展示的UI组件,广泛应用于项目管理、历史记录、用户活动追踪等场景。在React Native生态中,Timeline并非内置组件,需要基于ScrollView和FlatList等基础组件自行构建。
在OpenHarmony 6.0.0平台上实现Timeline组件需要特别关注平台的渲染特性和性能优化策略。本文将详细讲解如何构建一个高性能、可定制的Timeline组件。
组件结构
核心组成
Timeline组件结构示意:
┌─────────────────────────────────────┐
│ ● 时间点1 │
│ │ 连接线 │
│ ● 时间点2 (当前) │
│ │ 连接线 │
│ ○ 时间点3 (未完成) │
└─────────────────────────────────────┘
图例:
● = 已完成节点
○ = 未完成节点
│ = 连接线
| 组件部分 | 功能说明 |
|---|---|
| 时间点 | 标识特定时间节点的圆点或图标 |
| 连接线 | 连接相邻时间点的垂直线条 |
| 时间标签 | 显示具体时间或日期 |
| 内容区域 | 展示该时间点的详细信息 |
| 状态指示 | 显示完成/进行中/待开始状态 |
数据结构设计
时间点模型
interface TimelineItem {
id: string; // 唯一标识
time: string; // 时间显示
title: string; // 标题
description: string; // 详细描述
status: 'completed' | 'active' | 'pending'; // 状态
icon?: string; // 可选图标
}
状态类型说明
| 状态 | 视觉表现 | 含义 |
|---|---|---|
| completed | 实心圆点 + 主题色 | 已完成 |
| active | 大圆点 + 强调色 + 脉冲动画 | 进行中 |
| pending | 空心圆点 + 灰色 | 待开始 |
OpenHarmony平台适配
渲染特性对比
| 特性 | iOS/Android | OpenHarmony 6.0.0 | 适配方案 |
|---|---|---|---|
| 布局计算 | Yoga引擎 | ArkUI布局引擎 | 简化嵌套结构 |
| 虚拟滚动 | FlatList | 支持但需优化 | 设置initialNumToRender |
| 动画效果 | 流畅 | 需谨慎使用 | 简化动画复杂度 |
| 样式支持 | 完整CSS | 部分属性限制 | 使用标准样式属性 |
性能优化策略
- 虚拟列表:使用FlatList替代ScrollView处理大量数据
- 样式预定义:使用StyleSheet.create避免动态创建样式
- 减少重渲染:使用React.memo和useCallback优化组件
- 图片懒加载:延迟加载时间点相关图片资源
完整实现代码

/**
* HarmonyOS实战:Timeline时间轴组件
*
* @platform OpenHarmony 6.0.0 (API 20)
* @react-native 0.72.5
* @typescript 4.8.4
*/
import React, { useState } from 'react';
import {
View,
Text,
StyleSheet,
TouchableOpacity,
ScrollView,
} from 'react-native';
// 时间点数据结构
interface TimelineItem {
id: string;
time: string;
title: string;
description: string;
status: 'completed' | 'active' | 'pending';
}
interface TimelineProps {
onBack: () => void;
}
const TimelineScreen: React.FC<TimelineProps> = ({ onBack }) => {
const [activeIndex, setActiveIndex] = useState<number>(2);
// 项目历程数据
const timelineData: TimelineItem[] = [
{
id: '1',
time: '2023-01',
title: '项目启动',
description: '完成需求分析和团队组建',
status: 'completed',
},
{
id: '2',
time: '2023-03',
title: '原型设计',
description: '完成UI/UX设计和交互原型',
status: 'completed',
},
{
id: '3',
time: '2023-05',
title: '核心开发',
description: '实现主要功能模块',
status: 'active',
},
{
id: '4',
time: '2023-08',
title: '鸿蒙适配',
description: '完成OpenHarmony平台适配',
status: 'pending',
},
{
id: '5',
time: '2023-10',
title: '测试上线',
description: '多设备测试和正式发布',
status: 'pending',
},
];
// 获取状态对应的样式
const getStatusBadgeStyle = (status: string) => {
switch (status) {
case 'completed':
return styles.badgeCompleted;
case 'active':
return styles.badgeActive;
default:
return styles.badgePending;
}
};
// 获取状态文本
const getStatusLabel = (status: string) => {
switch (status) {
case 'completed':
return '已完成';
case 'active':
return '进行中';
default:
return '待开始';
}
};
// 渲染单个时间轴项
const renderItem = (item: TimelineItem, index: number) => {
const isActive = activeIndex === index;
const isCompleted = item.status === 'completed';
return (
<View key={item.id} style={styles.itemWrapper}>
<View style={styles.itemRow}>
{/* 时间点和标签 */}
<View style={styles.timeColumn}>
<View
style={[
styles.timeDot,
isCompleted && styles.dotCompleted,
isActive && styles.dotActive,
]}
>
{isActive && <View style={styles.dotInner} />}
</View>
<Text style={[styles.timeLabel, isActive && styles.labelActive]}>
{item.time}
</Text>
</View>
{/* 内容卡片 */}
<TouchableOpacity
style={[styles.contentCard, isActive && styles.cardActive]}
onPress={() => setActiveIndex(index)}
activeOpacity={0.7}
>
<Text style={[styles.itemTitle, isActive && styles.titleActive]}>
{item.title}
</Text>
<Text style={styles.itemDesc}>{item.description}</Text>
<View style={[styles.statusBadge, getStatusBadgeStyle(item.status)]}>
<Text style={styles.badgeText}>{getStatusLabel(item.status)}</Text>
</View>
</TouchableOpacity>
</View>
{/* 连接线(最后一项不显示) */}
{index < timelineData.length - 1 && (
<View
style={[
styles.connector,
isCompleted && styles.connectorCompleted,
]}
/>
)}
</View>
);
};
return (
<ScrollView
showsVerticalScrollIndicator={false}
contentContainerStyle={styles.scrollContent}
>
{/* 顶部导航栏 */}
<View style={styles.navigationBar}>
<TouchableOpacity onPress={onBack} style={styles.backBtn}>
<Text style={styles.backText}>← 返回</Text>
</TouchableOpacity>
<View style={styles.titleWrapper}>
<Text style={styles.mainTitle}>Timeline时间轴</Text>
<Text style={styles.subTitle}>项目历程可视化展示</Text>
</View>
</View>
{/* 平台信息 */}
<View style={styles.versionBanner}>
<Text style={styles.versionText}>OpenHarmony 6.0.0 | API 20</Text>
</View>
{/* 标题区域 */}
<View style={styles.headerSection}>
<Text style={styles.pageTitle}>项目开发历程</Text>
<Text style={styles.pageDesc}>点击卡片查看各阶段详情</Text>
</View>
{/* 时间轴主体 */}
<View style={styles.timelineContainer}>
{timelineData.map((item, index) => renderItem(item, index))}
</View>
{/* 应用场景 */}
<View style={styles.scenarioCard}>
<Text style={styles.cardTitle}>应用场景</Text>
<View style={styles.scenarioList}>
<Text style={styles.scenarioItem}>📅 项目管理里程碑</Text>
<Text style={styles.scenarioItem}>📊 历史记录追踪</Text>
<Text style={styles.scenarioItem}>🏥 医疗诊疗过程</Text>
<Text style={styles.scenarioItem}>📦 物流配送状态</Text>
</View>
</View>
{/* 适配要点 */}
<View style={styles.adaptCard}>
<Text style={styles.adaptTitle}>OpenHarmony适配要点</Text>
<View style={styles.adaptList}>
<Text style={styles.adaptItem}>• 预定义样式避免动态创建</Text>
<Text style={styles.adaptItem}>• 简化连接线样式提升性能</Text>
<Text style={styles.adaptItem}>• 使用固定宽度确保对齐</Text>
<Text style={styles.adaptItem}>• 减少阴影等复杂视觉效果</Text>
</View>
</View>
</ScrollView>
);
};
const styles = StyleSheet.create({
scrollContent: {
backgroundColor: '#f8f9fa',
paddingBottom: 32,
},
navigationBar: {
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: 16,
paddingVertical: 12,
backgroundColor: '#3B82F6',
paddingTop: 50,
},
backBtn: {
padding: 8,
},
backText: {
fontSize: 16,
color: '#fff',
fontWeight: '600',
},
titleWrapper: {
flex: 1,
marginLeft: 8,
},
mainTitle: {
fontSize: 20,
fontWeight: 'bold',
color: '#fff',
},
subTitle: {
fontSize: 12,
color: 'rgba(255, 255, 255, 0.85)',
marginTop: 2,
},
versionBanner: {
backgroundColor: '#dbeafe',
paddingHorizontal: 16,
paddingVertical: 8,
},
versionText: {
fontSize: 12,
color: '#1e40af',
textAlign: 'center',
},
headerSection: {
padding: 20,
alignItems: 'center',
},
pageTitle: {
fontSize: 24,
fontWeight: 'bold',
color: '#1e40af',
marginBottom: 8,
},
pageDesc: {
fontSize: 14,
color: '#64748b',
},
timelineContainer: {
backgroundColor: '#fff',
marginHorizontal: 16,
borderRadius: 12,
padding: 20,
},
itemWrapper: {
marginBottom: 10,
},
itemRow: {
flexDirection: 'row',
alignItems: 'flex-start',
},
timeColumn: {
alignItems: 'center',
width: 60,
},
timeDot: {
width: 14,
height: 14,
borderRadius: 7,
backgroundColor: '#e5e7eb',
borderWidth: 2,
borderColor: '#fff',
},
dotCompleted: {
backgroundColor: '#3B82F6',
},
dotActive: {
width: 18,
height: 18,
borderRadius: 9,
backgroundColor: '#fff',
borderColor: '#3B82F6',
borderWidth: 3,
},
dotInner: {
width: 8,
height: 8,
borderRadius: 4,
backgroundColor: '#3B82F6',
},
timeLabel: {
marginTop: 6,
fontSize: 12,
color: '#9ca3af',
textAlign: 'center',
},
labelActive: {
color: '#3B82F6',
fontWeight: '600',
},
contentCard: {
flex: 1,
padding: 15,
backgroundColor: '#f8fafc',
borderRadius: 10,
marginLeft: 10,
},
cardActive: {
backgroundColor: '#eff6ff',
borderWidth: 1,
borderColor: '#bfdbfe',
},
itemTitle: {
fontSize: 16,
fontWeight: '600',
color: '#1e293b',
marginBottom: 6,
},
titleActive: {
color: '#3B82F6',
},
itemDesc: {
fontSize: 14,
color: '#64748b',
lineHeight: 20,
marginBottom: 10,
},
statusBadge: {
alignSelf: 'flex-start',
paddingHorizontal: 8,
paddingVertical: 4,
borderRadius: 12,
},
badgeCompleted: {
backgroundColor: '#dcfce7',
},
badgeActive: {
backgroundColor: '#fef3c7',
},
badgePending: {
backgroundColor: '#f1f5f9',
},
badgeText: {
fontSize: 11,
fontWeight: '600',
},
connector: {
height: 30,
width: 2,
backgroundColor: '#e5e7eb',
marginLeft: 53,
alignSelf: 'flex-start',
},
connectorCompleted: {
backgroundColor: '#3B82F6',
},
scenarioCard: {
backgroundColor: '#fff',
margin: 16,
padding: 16,
borderRadius: 12,
},
cardTitle: {
fontSize: 16,
fontWeight: 'bold',
color: '#334155',
marginBottom: 12,
},
scenarioList: {
gap: 8,
},
scenarioItem: {
fontSize: 14,
color: '#64748b',
paddingVertical: 4,
},
adaptCard: {
backgroundColor: '#eff6ff',
margin: 16,
marginBottom: 32,
padding: 16,
borderRadius: 12,
},
adaptTitle: {
fontSize: 16,
fontWeight: 'bold',
color: '#1e40af',
marginBottom: 12,
},
adaptList: {
gap: 6,
},
adaptItem: {
fontSize: 13,
color: '#475569',
lineHeight: 20,
},
});
export default TimelineScreen;
核心实现要点
1. 连接线处理
// 连接线样式:已完成使用主题色,未完成使用灰色
const connectorStyle = [
styles.connector,
isCompleted && styles.connectorCompleted,
];
2. 状态指示
// 三种状态对应不同视觉效果
completed: 实心圆点 + 蓝色
active: 大圆点 + 白底蓝边 + 内圈
pending: 空心圆点 + 灰色
3. OpenHarmony优化建议
| 优化项 | 说明 | 实现 |
|---|---|---|
| 样式预定义 | 避免运行时创建 | StyleSheet.create |
| 简化阴影 | 减少渲染负担 | 使用边框替代 |
| 固定宽度 | 确保布局稳定 | timeColumn设置固定宽度 |
| 条件样式 | 减少样式对象 | 预定义所有变体 |
应用场景扩展
| 场景 | 数据结构建议 | 特殊处理 |
|---|---|---|
| 订单追踪 | 添加物流信息字段 | 显示物流单号 |
| 审批流程 | 添加审批人信息 | 显示头像和姓名 |
| 医疗记录 | 添加医生和诊断 | 敏感信息脱敏 |
| 项目管理 | 添加负责人 | 支持点击查看详情 |
项目源码
更多推荐


所有评论(0)