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 部分属性限制 使用标准样式属性

性能优化策略

  1. 虚拟列表:使用FlatList替代ScrollView处理大量数据
  2. 样式预定义:使用StyleSheet.create避免动态创建样式
  3. 减少重渲染:使用React.memo和useCallback优化组件
  4. 图片懒加载:延迟加载时间点相关图片资源

完整实现代码

在这里插入图片描述

/**
 * 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设置固定宽度
条件样式 减少样式对象 预定义所有变体

应用场景扩展

场景 数据结构建议 特殊处理
订单追踪 添加物流信息字段 显示物流单号
审批流程 添加审批人信息 显示头像和姓名
医疗记录 添加医生和诊断 敏感信息脱敏
项目管理 添加负责人 支持点击查看详情

项目源码

开源鸿蒙社区:https://openharmonycrossplatform.csdn.net

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐