rn_for_openharmony常用组件_Timeline时间线
摘要 该代码实现了一个React Native时间线组件Timeline,用于展示按时间顺序排列的事件节点。组件支持三种布局模式(默认左对齐、右对齐、交替显示),每个节点包含标题、描述、时间和自定义图标。核心设计包括: 数据结构:TimelineItem接口定义节点属性(标题必选,描述/时间/图标/颜色可选) 组件属性:支持节点数组、三种布局模式、线条颜色自定义和样式扩展 实现方式:通过map遍历
项目开源地址:https://atomgit.com/nutpi/rn_for_openharmony_element
快递到哪了?订单什么状态?项目进展到哪一步了?
这些问题有个共同点:都是按时间顺序发生的一系列事件。用列表展示当然可以,但时间线能让用户更直观地看到"进度"和"流程"。
一条竖线,几个节点,每个节点旁边是时间和描述。简单,但很有效。
今天来看看这个 Timeline 组件是怎么写的。
代码全貌
文件在 src/components/ui/Timeline.tsx:
import React from 'react';
import { View, Text, StyleSheet, ViewStyle } from 'react-native';
import { UITheme, ColorType } from './theme';
interface TimelineItem {
title: string;
description?: string;
time?: string;
icon?: string;
color?: ColorType;
}
interface TimelineProps {
items: TimelineItem[];
variant?: 'default' | 'alternate' | 'right';
lineColor?: string;
style?: ViewStyle;
}
export const Timeline: React.FC<TimelineProps> = ({
items,
variant = 'default',
lineColor = UITheme.colors.gray[200],
style,
}) => {
return (
<View style={style}>
{items.map((item, index) => {
const isLast = index === items.length - 1;
const dotColor = item.color ? UITheme.colors[item.color] : UITheme.colors.primary;
const isRight = variant === 'right' || (variant === 'alternate' && index % 2 === 1);
return (
<View
key={index}
style={[styles.item, isRight && styles.itemRight]}
>
{variant === 'alternate' && (
<View style={[styles.side, isRight ? styles.sideLeft : styles.sideRight]}>
{!isRight && item.time && <Text style={styles.time}>{item.time}</Text>}
{isRight && (
<>
<Text style={styles.title}>{item.title}</Text>
{item.description && <Text style={styles.description}>{item.description}</Text>}
</>
)}
</View>
)}
<View style={styles.dotContainer}>
<View style={[styles.dot, { backgroundColor: dotColor }]}>
{item.icon && <Text style={styles.dotIcon}>{item.icon}</Text>}
</View>
{!isLast && <View style={[styles.line, { backgroundColor: lineColor }]} />}
</View>
<View style={[styles.content, variant === 'right' && styles.contentRight]}>
{variant !== 'alternate' && item.time && <Text style={styles.time}>{item.time}</Text>}
{(variant !== 'alternate' || !isRight) && (
<>
<Text style={styles.title}>{item.title}</Text>
{item.description && <Text style={styles.description}>{item.description}</Text>}
</>
)}
{variant === 'alternate' && isRight && item.time && <Text style={styles.time}>{item.time}</Text>}
</View>
</View>
);
})}
</View>
);
};
const styles = StyleSheet.create({
item: { flexDirection: 'row', minHeight: 60 },
itemRight: { flexDirection: 'row-reverse' },
side: { flex: 1, paddingHorizontal: UITheme.spacing.md },
sideLeft: { alignItems: 'flex-end' },
sideRight: { alignItems: 'flex-start' },
dotContainer: { alignItems: 'center', width: 24 },
dot: {
width: 12,
height: 12,
borderRadius: 6,
alignItems: 'center',
justifyContent: 'center',
},
dotIcon: { fontSize: 8, color: UITheme.colors.white },
line: { flex: 1, width: 2, marginVertical: 4 },
content: { flex: 1, paddingLeft: UITheme.spacing.md, paddingBottom: UITheme.spacing.lg },
contentRight: { paddingLeft: 0, paddingRight: UITheme.spacing.md, alignItems: 'flex-end' },
time: { fontSize: UITheme.fontSize.xs, color: UITheme.colors.gray[400], marginBottom: 2 },
title: { fontSize: UITheme.fontSize.md, fontWeight: '500', color: UITheme.colors.gray[800] },
description: { fontSize: UITheme.fontSize.sm, color: UITheme.colors.gray[500], marginTop: 2 },
});
80 行左右,实现了三种布局模式。代码看着有点绕,别急,我们一块一块来。
数据结构设计
interface TimelineItem {
title: string;
description?: string;
time?: string;
icon?: string;
color?: ColorType;
}
每个时间节点需要哪些信息?
title 是必须的,告诉用户这个节点是什么事件。比如"订单创建"、“已发货”、“已签收”。
description 是补充说明,可选。有时候标题不够,需要更多细节。比如"已发货"可以补充"从上海仓库发出"。
time 是时间戳,可选。有些时间线不需要显示具体时间,只需要展示流程顺序。
icon 是节点图标,可选。默认是个小圆点,加上图标可以让每个节点更有辨识度。
color 是节点颜色,可选。不同颜色可以表示不同状态,绿色表示完成,黄色表示进行中,灰色表示待处理。
组件属性
interface TimelineProps {
items: TimelineItem[];
variant?: 'default' | 'alternate' | 'right';
lineColor?: string;
style?: ViewStyle;
}
items 是时间节点数组,这是核心数据。
variant 控制布局模式,有三种选择:
default内容在左边,这是最常见的right内容在右边alternate内容左右交替,适合展示对比或者让页面更有节奏感
lineColor 是连接线的颜色,默认浅灰色。一般不需要改,除非你的设计稿有特殊要求。
组件入口
export const Timeline: React.FC<TimelineProps> = ({
items,
variant = 'default',
lineColor = UITheme.colors.gray[200],
style,
}) => {
return (
<View style={style}>
{items.map((item, index) => {
组件接收 items 数组,然后用 map 遍历渲染每个节点。
这里没什么特别的,就是标准的列表渲染模式。
关键变量计算
const isLast = index === items.length - 1;
const dotColor = item.color ? UITheme.colors[item.color] : UITheme.colors.primary;
const isRight = variant === 'right' || (variant === 'alternate' && index % 2 === 1);
每个节点渲染前,先算三个值。
isLast 判断是不是最后一个节点。最后一个节点下面不需要连接线,因为没有下一个节点了。
dotColor 是节点圆点的颜色。如果 item 指定了 color,就用指定的;没指定就用主题色。
isRight 判断内容是否显示在右边。两种情况会在右边:一是 variant 本身就是 right;二是 variant 是 alternate 且当前是奇数索引(0、2、4 在左边,1、3、5 在右边)。
index % 2 === 1 这个取模运算就是用来实现交替效果的。
节点容器
return (
<View
key={index}
style={[styles.item, isRight && styles.itemRight]}
>
每个节点是一个水平的 flex 容器。
styles.item 设置了 flexDirection: 'row',子元素从左到右排列。
当 isRight 为 true 时,加上 styles.itemRight,它设置了 flexDirection: 'row-reverse',子元素从右到左排列。这样就实现了内容在右边的效果,而不需要改变 DOM 结构。
交替模式的左侧区域
{variant === 'alternate' && (
<View style={[styles.side, isRight ? styles.sideLeft : styles.sideRight]}>
{!isRight && item.time && <Text style={styles.time}>{item.time}</Text>}
{isRight && (
<>
<Text style={styles.title}>{item.title}</Text>
{item.description && <Text style={styles.description}>{item.description}</Text>}
</>
)}
</View>
)}
这段只在 alternate 模式下渲染。
交替模式的结构是:左侧区域 + 中间圆点线 + 右侧区域。内容在左右两边交替出现。
当 isRight 为 false 时(内容在左边),左侧区域显示时间。
当 isRight 为 true 时(内容在右边),左侧区域显示标题和描述。
有点绕对吧?其实就是:内容在哪边,时间就在另一边。这样时间和内容分开,视觉上更清晰。
styles.sideLeft 设置了 alignItems: 'flex-end',让内容靠右对齐,贴近中间的圆点。styles.sideRight 设置了 alignItems: 'flex-start',让内容靠左对齐。
中间的圆点和连接线
<View style={styles.dotContainer}>
<View style={[styles.dot, { backgroundColor: dotColor }]}>
{item.icon && <Text style={styles.dotIcon}>{item.icon}</Text>}
</View>
{!isLast && <View style={[styles.line, { backgroundColor: lineColor }]} />}
</View>
这是时间线的"线"的部分。
dotContainer 是一个垂直的容器,宽度固定 24px,里面放圆点和线。
圆点是个 12x12 的小圆,borderRadius: 6 让它变成正圆。如果有 icon,就在圆点里显示图标。
连接线用 {!isLast && ...} 条件渲染,最后一个节点不显示。线的宽度是 2px,高度用 flex: 1 自动撑满剩余空间。
marginVertical: 4 让线和圆点之间有点间距,不会紧贴在一起。
内容区域
<View style={[styles.content, variant === 'right' && styles.contentRight]}>
{variant !== 'alternate' && item.time && <Text style={styles.time}>{item.time}</Text>}
{(variant !== 'alternate' || !isRight) && (
<>
<Text style={styles.title}>{item.title}</Text>
{item.description && <Text style={styles.description}>{item.description}</Text>}
</>
)}
{variant === 'alternate' && isRight && item.time && <Text style={styles.time}>{item.time}</Text>}
</View>
内容区域的渲染逻辑有点复杂,因为要处理三种模式。
default 和 right 模式比较简单:时间、标题、描述依次显示。
alternate 模式要分情况:
- 内容在左边时(!isRight),显示标题和描述
- 内容在右边时(isRight),只显示时间(因为标题和描述在左侧区域显示了)
styles.contentRight 设置了 alignItems: 'flex-end',让文字右对齐。
样式定义
const styles = StyleSheet.create({
item: { flexDirection: 'row', minHeight: 60 },
itemRight: { flexDirection: 'row-reverse' },
minHeight: 60 保证每个节点有最小高度,即使内容很少也不会太挤。
side: { flex: 1, paddingHorizontal: UITheme.spacing.md },
sideLeft: { alignItems: 'flex-end' },
sideRight: { alignItems: 'flex-start' },
side 是交替模式下的侧边区域,flex: 1 让它和内容区域平分空间。
dotContainer: { alignItems: 'center', width: 24 },
dot: {
width: 12,
height: 12,
borderRadius: 6,
alignItems: 'center',
justifyContent: 'center',
},
dotIcon: { fontSize: 8, color: UITheme.colors.white },
line: { flex: 1, width: 2, marginVertical: 4 },
圆点容器固定宽度,圆点居中。线用 flex: 1 自动填充高度。
content: { flex: 1, paddingLeft: UITheme.spacing.md, paddingBottom: UITheme.spacing.lg },
contentRight: { paddingLeft: 0, paddingRight: UITheme.spacing.md, alignItems: 'flex-end' },
内容区域 flex: 1 占据剩余空间。paddingBottom 让节点之间有间距。
time: { fontSize: UITheme.fontSize.xs, color: UITheme.colors.gray[400], marginBottom: 2 },
title: { fontSize: UITheme.fontSize.md, fontWeight: '500', color: UITheme.colors.gray[800] },
description: { fontSize: UITheme.fontSize.sm, color: UITheme.colors.gray[500], marginTop: 2 },
});
文字样式形成层次:时间最小最浅,标题中等加粗,描述小号浅色。
Demo 里怎么用
看看 src/screens/demos/TimelineDemo.tsx 里的例子。
准备数据
const items = [
{ title: '创建订单', description: '订单已创建成功', time: '09:00', color: 'success' as const },
{ title: '支付完成', description: '使用支付宝支付 ¥299.00', time: '09:05', color: 'success' as const },
{ title: '商家发货', description: '包裹已从上海发出', time: '10:30', color: 'primary' as const },
{ title: '等待收货', description: '预计明天送达', time: '待定', color: 'warning' as const },
];
一个典型的订单物流时间线。前两步已完成用 success 绿色,当前步骤用 primary 蓝色,待处理用 warning 黄色。
as const 是 TypeScript 的类型断言,让字符串字面量保持精确类型。
基础用法
<Timeline items={items} />
最简单的用法,传入数据就行。默认左对齐,圆点在左边,内容在右边。
右对齐
<Timeline items={items} variant="right" />
内容跑到左边去了,圆点在右边。适合从右往左阅读的场景,或者设计上需要变化的时候。
交替显示
<Timeline items={items} variant="alternate" />
内容左右交替,时间线在中间。这种布局更有设计感,适合展示比较长的时间线,避免视觉疲劳。
带图标
<Timeline
items={[
{ title: '下单成功', time: '09:00', icon: '📦', color: 'success' },
{ title: '已发货', time: '10:00', icon: '🚚', color: 'primary' },
{ title: '运输中', time: '14:00', icon: '✈️', color: 'info' },
{ title: '已签收', time: '18:00', icon: '✅', color: 'success' },
]}
/>
每个节点加上图标,一眼就能看出是什么状态。图标比纯色圆点更有表现力。
实际场景
订单追踪
这是时间线最常见的用途。用户下单后,想知道包裹到哪了,时间线把每个环节串起来,一目了然。
const OrderTracking = ({ orderId }) => {
const { data: trackingInfo } = useOrderTracking(orderId);
const items = trackingInfo.map(info => ({
title: info.status,
description: info.location,
time: formatTime(info.timestamp),
color: info.isCurrent ? 'primary' : 'success',
}));
return <Timeline items={items} />;
};
操作日志
后台系统里,经常需要展示某条数据的操作历史。谁在什么时候做了什么操作。
const OperationLog = ({ logs }) => {
const items = logs.map(log => ({
title: `${log.operator} ${log.action}`,
description: log.detail,
time: log.time,
icon: getActionIcon(log.action),
}));
return <Timeline items={items} />;
};
项目里程碑
项目管理工具里,展示项目的关键节点和进度。
const ProjectMilestones = ({ milestones }) => {
const items = milestones.map(m => ({
title: m.name,
description: m.completed ? '已完成' : `预计 ${m.dueDate}`,
color: m.completed ? 'success' : m.isOverdue ? 'danger' : 'warning',
}));
return <Timeline items={items} variant="alternate" />;
};
用户动态
社交应用里,展示用户的活动记录。
const UserActivity = ({ activities }) => {
const items = activities.map(a => ({
title: a.action,
description: a.target,
time: formatRelativeTime(a.timestamp),
icon: getActivityIcon(a.type),
}));
return <Timeline items={items} />;
};
几个细节
写时间线组件,有几个容易忽略的点:
最后一个节点不要线
时间线的"线"是连接两个节点的,最后一个节点下面没有东西可连,所以不需要线。代码里用 !isLast 判断。
节点颜色要有意义
别随便用颜色,要让颜色传达状态。绿色表示完成,蓝色表示当前,黄色表示等待,红色表示异常。用户看一眼颜色就知道进度。
交替模式的对齐
交替模式下,左边的内容要右对齐,右边的内容要左对齐,这样才能贴近中间的时间线。代码里用 alignItems 控制。
最小高度
每个节点要有最小高度,不然内容少的节点会很矮,连接线会很短,看起来不协调。
写在最后
时间线组件的核心就是把一系列事件按顺序串起来。技术上不难,难的是处理好各种布局模式下的细节。
这个组件支持三种布局,代码里有不少条件判断,第一次看可能有点晕。但理解了每种模式的结构,再看代码就清楚了。
下次遇到需要展示流程、进度、历史记录的场景,试试时间线。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐


所有评论(0)