ReactNative项目OpenHarmony三方库集成实战:react-native-splash-screen

欢迎加入开源鸿蒙跨平台社区https://openharmonycrossplatform.csdn.net

📋 前言

启动屏(Splash Screen)是移动应用的第一印象,一个精心设计的启动屏不仅能展示品牌形象,还能在应用初始化期间提供良好的用户体验。在应用启动时,用户往往需要等待数据加载、权限请求、用户认证等初始化操作完成,此时启动屏可以优雅地遮盖这些后台操作,避免用户看到空白或闪烁的界面。react-native-splash-screen 是一个功能完善的启动屏控制库,支持显示和隐藏启动屏幕,是应用开发中不可或缺的基础组件。

🎯 库简介

基本信息

  • 库名称 : react-native-splash-screen
  • 版本信息 :
    • 3.3.0-0.0.2 + @react-native-oh-tpl/react-native-splash-screen: 支持 RN 0.72 版本(已废弃)
    • 3.3.2 + @react-native-ohos/react-native-splash-screen: 支持 RN 0.72 版本
    • 3.4.0 + @react-native-ohos/react-native-splash-screen: 支持 RN 0.77 版本
  • 官方仓库: https://github.com/crazycodeboy/react-native-splash-screen
  • 鸿蒙仓库: https://gitcode.com/openharmony-sig/rntpc_react-native-splash-screen
  • 主要功能 :
    • 🖼️ 显示启动屏幕
    • 🔄 隐藏启动屏幕
    • 🎨 自定义启动屏背景和图标
    • ⏱️ 精确控制启动屏显示时机
    • 📱 支持 Android、iOS、HarmonyOS 多平台

为什么需要启动屏库?

特性 原生开发 react-native-splash-screen
启动屏控制 ⚠️ 需配置原生文件 ✅ 统一API控制
动态隐藏 ⚠️ 需原生代码 ✅ JS端控制
品牌展示 ⚠️ 需单独配置 ✅ 统一配置
初始化等待 ❌ 需自行实现 ✅ 完美支持
跨平台一致性 ⚠️ iOS/Android差异 ✅ 统一行为
HarmonyOS 支持 ❌ 无 ✅ 完善适配

核心功能

功能 说明 HarmonyOS 支持
show() 显示启动屏幕 ✅(原生方法)
hide() 隐藏启动屏幕
自定义启动页 配置启动图片和背景色
启动页组件 SplashScreenView

兼容性验证

在以下环境验证通过:

  • RNOH : 0.72.90; SDK : HarmonyOS 6.0.0 Release SDK; IDE : DevEco Studio 6.0.2; ROM: 6.0.0

📦 安装步骤

1. 安装依赖

请到三方库的 Releases 发布地址查看配套的版本信息:

三方库版本 发布信息 支持 RN 版本
3.3.0-0.0.2 @react-native-oh-tpl/react-native-splash-screen 0.72(已废弃)
3.3.2 @react-native-ohos/react-native-splash-screen 0.72
3.4.0 @react-native-ohos/react-native-splash-screen 0.77
bash 复制代码
# RN 0.72 版本
npm install @react-native-ohos/react-native-splash-screen@3.3.2-rc.1

# RN 0.77 版本
npm install @react-native-ohos/react-native-splash-screen@3.4.0

# 或者使用 yarn
yarn add @react-native-ohos/react-native-splash-screen

2. 验证安装

安装完成后,检查 package.json 文件:

json 复制代码
{
  "dependencies": {
    "@react-native-ohos/react-native-splash-screen": "^3.3.2-rc.1"
  }
}

🔧 HarmonyOS 平台配置 ⭐

1. 在工程根目录的 oh-package.json5 添加 overrides 字段(要看自己package.json中的版本)

打开 harmony/oh-package.json5,添加以下配置:

json 复制代码
{
  // ... 其他配置
  "overrides": {
    "@rnoh/react-native-openharmony": "0.72.90"
  }
}

2. 引入原生端代码

打开 harmony/entry/oh-package.json5,添加以下依赖:

json5 复制代码
"dependencies": {
  "@react-native-ohos/react-native-splash-screen": "file:../../node_modules/@react-native-ohos/react-native-splash-screen/harmony/splash_screen.har"
}

点击右上角的 sync 按钮,或者在终端执行:

bash 复制代码
cd entry
ohpm install

3. 配置 CMakeLists

打开 entry/src/main/cpp/CMakeLists.txt,添加:

c 复制代码
project(rnapp)
cmake_minimum_required(VERSION 3.4.1)
set(RNOH_APP_DIR "${CMAKE_CURRENT_SOURCE_DIR}")
+ set(OH_MODULES "${CMAKE_CURRENT_SOURCE_DIR}/../../../oh_modules")
set(RNOH_CPP_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../react-native-harmony/harmony/cpp")

add_subdirectory("${RNOH_CPP_DIR}" ./rn)

# RNOH_BEGIN: manual_package_linking_1
add_subdirectory("../../../../sample_package/src/main/cpp" ./sample-package)
+ add_subdirectory("${OH_MODULES}/@react-native-ohos/react-native-splash-screen/src/main/cpp" ./splash_screen)
# RNOH_END: manual_package_linking_1

add_library(rnoh_app SHARED
    ${GENERATED_CPP_FILES}
    "./PackageProvider.cpp"
    "${RNOH_CPP_DIR}/RNOHAppNapiBridge.cpp"
)

target_link_libraries(rnoh_app PUBLIC rnoh)

# RNOH_BEGIN: manual_package_linking_2
target_link_libraries(rnoh_app PUBLIC rnoh_sample_package)
+ target_link_libraries(rnoh_app PUBLIC rnoh_splash_screen)
# RNOH_END: manual_package_linking_2

4. 引入 SplashScreenPackage

打开 entry/src/main/cpp/PackageProvider.cpp,添加:

cpp 复制代码
#include "RNOH/PackageProvider.h"
#include "generated/RNOHGeneratedPackage.h"
#include "SamplePackage.h"
+ #include "SplashScreenPackage.h"

using namespace rnoh;

std::vector<std::shared_ptr<Package>> PackageProvider::getPackages(Package::Context ctx) {
    return {
        std::make_shared<RNOHGeneratedPackage>(ctx),
        std::make_shared<SamplePackage>(ctx),
        + std::make_shared<SplashScreenPackage>(ctx),
    };
}

5. 在 ArkTS 侧引入 SplashScreenPackage

打开 entry/src/main/ets/RNPackagesFactory.ts,添加:

typescript 复制代码
+ import { SplashScreenPackage } from '@react-native-ohos/react-native-splash-screen/ts';

export function createRNPackages(ctx: RNPackageContext): RNPackage[] {
  return [
    new SamplePackage(ctx),
    + new SplashScreenPackage(ctx)
  ];
}

6. 创建启动页面

entry/src/main/ets/pages 目录下新建启动页 SplashScreenPage.ets

ts 复制代码
import { SplashScreenView } from '@react-native-ohos/react-native-splash-screen/src/main/ets/SplashScreenView'

@Entry
@Component
export struct SplashScreenPage {
  build() {
    Row() {
      SplashScreenView();
    }
  }
}

7. 注册启动页面

entry/src/resources/base/profile/main_pages.json 内添加 pages/SplashScreenPage

json 复制代码
{
  "src": [
    "pages/SplashScreenPage",
    "pages/Index"
  ]
}

8. 配置启动屏显示

entry/src/main/ets/entryability/EntryAbility.ets 内添加启动屏配置:

ts 复制代码
import window from '@ohos.window';
import { SplashScreen } from '@react-native-ohos/react-native-splash-screen/src/main/ets/SplashScreen'

export default class EntryAbility extends RNAbility {
  onWindowStageCreate(windowStage: window.WindowStage): void {
    let startWindowIcon = $r('app.media.splashIcon');
    let startWindowBackground = "#FFFFFF";
    let startPageUrl = 'pages/SplashScreenPage';
  
    SplashScreen.show(
      this.context, 
      windowStage, 
      startWindowIcon, 
      startWindowBackground, 
      startPageUrl
    ).then(() => {
      super.onWindowStageCreate(windowStage);
    })
  }
}

9. 准备启动图片

将启动屏图片放置在 entry/src/main/resources/base/media 文件夹中,命名为 splashIcon.png(或其他名称,需与代码中引用一致)。

然后编译、运行即可。

📖 API 详解

hide() - 隐藏启动屏幕

在应用初始化完成后,调用此方法隐藏启动屏幕,显示应用主界面。这是最常用的 API,通常在 React Native 端调用。

类型() => void

返回值:无

使用场景

  • 应用数据加载完成后
  • 用户登录验证完成后
  • 首页渲染完成后
  • 所有初始化任务完成后
tsx 复制代码
import SplashScreen from 'react-native-splash-screen';

class App extends React.Component {
  componentDidMount() {
    this.initializeApp();
  }

  initializeApp = async () => {
    try {
      await this.loadUserData();
      await this.loadAppConfig();
      await this.requestPermissions();
    
      SplashScreen.hide();
    } catch (error) {
      console.error('初始化失败:', error);
      SplashScreen.hide();
    }
  };

  loadUserData = async () => {
    // 加载用户数据
  };

  loadAppConfig = async () => {
    // 加载应用配置
  };

  requestPermissions = async () => {
    // 请求权限
  };

  render() {
    return <MainApp />;
  }
}

使用 Hook 的方式

tsx 复制代码
import { useEffect, useState } from 'react';
import SplashScreen from 'react-native-splash-screen';

const App = () => {
  const [isReady, setIsReady] = useState(false);

  useEffect(() => {
    const init = async () => {
      try {
        await Promise.all([
          loadFonts(),
          loadAssets(),
          initializeStore(),
        ]);
        setIsReady(true);
      } catch (error) {
        console.error('初始化失败:', error);
      } finally {
        SplashScreen.hide();
      }
    };

    init();
  }, []);

  if (!isReady) {
    return null;
  }

  return <MainNavigator />;
};

show() - 显示启动屏幕(原生方法)

在 HarmonyOS 中,show() 方法需要在原生端调用,用于在应用启动时显示启动屏。这个方法只能在 EntryAbility.ets 中调用。

类型(context, windowStage, icon, background, pageUrl) => Promise<void>

参数说明

参数 类型 必填 说明
context UIAbilityContext 应用上下文
windowStage window.WindowStage 窗口舞台
icon Resource 启动屏图标资源
background string 启动屏背景色
pageUrl string 启动页面路径

返回值Promise<void>

使用场景

  • 应用启动时显示品牌页面
  • 在 RN 页面加载期间展示启动屏
  • 自定义启动屏样式

完整配置示例

ts 复制代码
import window from '@ohos.window';
import { SplashScreen } from '@react-native-ohos/react-native-splash-screen/src/main/ets/SplashScreen'
import { RNAbility } from 'rnoh/ts';

export default class EntryAbility extends RNAbility {
  onWindowStageCreate(windowStage: window.WindowStage): void {
    let startWindowIcon = $r('app.media.splashIcon');
    let startWindowBackground = "#FFFFFF";
    let startPageUrl = 'pages/SplashScreenPage';
  
    SplashScreen.show(
      this.context,
      windowStage,
      startWindowIcon,
      startWindowBackground,
      startPageUrl
    ).then(() => {
      super.onWindowStageCreate(windowStage);
    }).catch((error: Error) => {
      console.error('启动屏显示失败:', error);
      super.onWindowStageCreate(windowStage);
    })
  }
}

参数详解

ets 复制代码
// startWindowIcon - 启动屏图标
// 支持的图片格式:png, jpg, webp
// 建议尺寸:根据设备屏幕尺寸适配
let startWindowIcon = $r('app.media.splashIcon');

// startWindowBackground - 启动屏背景色
// 支持格式:十六进制颜色值
let startWindowBackground = "#FFFFFF";  // 白色
let startWindowBackground = "#007AFF";  // 蓝色
let startWindowBackground = "#1A1A1A";  // 深灰色

// startPageUrl - 启动页面路径
// 对应 main_pages.json 中注册的页面
let startPageUrl = 'pages/SplashScreenPage';

SplashScreenView - 启动屏视图组件

SplashScreenView 是一个原生组件,用于渲染启动屏的视觉效果。需要在 SplashScreenPage.ets 中使用。

类型:ArkUI 组件

使用场景

  • 自定义启动屏布局
  • 添加动画效果
  • 显示加载进度

基础用法

ets 复制代码
import { SplashScreenView } from '@react-native-ohos/react-native-splash-screen/src/main/ets/SplashScreenView'

@Entry
@Component
export struct SplashScreenPage {
  build() {
    Row() {
      SplashScreenView();
    }
  }
}

自定义启动屏示例

ets 复制代码
import { SplashScreenView } from '@react-native-ohos/react-native-splash-screen/src/main/ets/SplashScreenView'

@Entry
@Component
export struct SplashScreenPage {
  @State private progress: number = 0;

  aboutToAppear() {
    this.startProgressAnimation();
  }

  startProgressAnimation() {
    setInterval(() => {
      this.progress = (this.progress + 5) % 100;
    }, 100);
  }

  build() {
    Column() {
      Image($r('app.media.logo'))
        .width(120)
        .height(120)
        .margin({ bottom: 20 })

      Text('欢迎使用')
        .fontSize(24)
        .fontColor('#333333')
        .margin({ bottom: 30 })

      Progress({ value: this.progress, total: 100, type: ProgressType.Linear })
        .width('60%')
        .color('#007AFF')

      Text(`${this.progress}%`)
        .fontSize(14)
        .fontColor('#999999')
        .margin({ top: 10 })
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#FFFFFF')
    .justifyContent(FlexAlign.Center)
  }
}

📋 完整示例

tsx 复制代码
import React, { useEffect, useState, useCallback } from "react";
import {
  View,
  Text,
  StyleSheet,
  ActivityIndicator,
  StatusBar,
  SafeAreaView,
} from "react-native";
import SplashScreen from "react-native-splash-screen";

type InitStep = {
  name: string;
  action: () => Promise<void>;
};

const App: React.FC = () => {
  const [isLoading, setIsLoading] = useState(true);
  const [currentStep, setCurrentStep] = useState<string>("");
  const [initProgress, setInitProgress] = useState(0);
  const [hasError, setHasError] = useState(false);

  const initSteps: InitStep[] = [
    { name: "加载字体资源", action: loadFonts },
    { name: "初始化存储", action: initializeStorage },
    { name: "检查用户状态", action: checkUserStatus },
    { name: "加载应用配置", action: loadAppConfig },
    { name: "预加载资源", action: preloadAssets },
  ];

  useEffect(() => {
    initializeApp();
  }, []);

  const initializeApp = async () => {
    try {
      for (let i = 0; i < initSteps.length; i++) {
        const step = initSteps[i];
        setCurrentStep(step.name);
        await step.action();
        setInitProgress(((i + 1) / initSteps.length) * 100);
      }
    
      setIsLoading(false);
    } catch (error) {
      console.error("初始化失败:", error);
      setHasError(true);
    } finally {
      SplashScreen.hide();
    }
  };

  async function loadFonts() {
    await new Promise((resolve) => setTimeout(resolve, 300));
  }

  async function initializeStorage() {
    await new Promise((resolve) => setTimeout(resolve, 200));
  }

  async function checkUserStatus() {
    await new Promise((resolve) => setTimeout(resolve, 400));
  }

  async function loadAppConfig() {
    await new Promise((resolve) => setTimeout(resolve, 300));
  }

  async function preloadAssets() {
    await new Promise((resolve) => setTimeout(resolve, 200));
  }

  const handleRetry = useCallback(() => {
    setHasError(false);
    setInitProgress(0);
    setCurrentStep("");
    initializeApp();
  }, []);

  if (isLoading) {
    return (
      <View style={styles.loadingContainer}>
        <StatusBar barStyle="dark-content" backgroundColor="#FFFFFF" />
        <ActivityIndicator size="large" color="#007AFF" />
        <Text style={styles.loadingText}>正在初始化应用...</Text>
        <Text style={styles.stepText}>{currentStep}</Text>
        <View style={styles.progressContainer}>
          <View style={[styles.progressBar, { width: `${initProgress}%` }]} />
        </View>
        <Text style={styles.progressText}>{Math.round(initProgress)}%</Text>
      </View>
    );
  }

  if (hasError) {
    return (
      <View style={styles.errorContainer}>
        <StatusBar barStyle="dark-content" backgroundColor="#FFFFFF" />
        <Text style={styles.errorTitle}>初始化失败</Text>
        <Text style={styles.errorText}>请检查网络连接后重试</Text>
        <Text style={styles.retryButton} onPress={handleRetry}>
          重试
        </Text>
      </View>
    );
  }

  return (
    <SafeAreaView style={styles.container}>
      <StatusBar barStyle="dark-content" backgroundColor="#FFFFFF" />
      <View style={styles.header}>
        <Text style={styles.title}>欢迎使用</Text>
        <Text style={styles.subtitle}>应用已准备就绪</Text>
      </View>
      <View style={styles.content}>
        <View style={styles.successIcon}>
          <Text style={styles.successIconText}>✓</Text>
        </View>
        <Text style={styles.welcomeText}>初始化完成</Text>
        <Text style={styles.descriptionText}>
          启动屏已成功隐藏,现在您可以开始使用应用了
        </Text>
      </View>
      <View style={styles.footer}>
        <Text style={styles.footerText}>react-native-splash-screen 示例</Text>
        <Text style={styles.versionText}>版本 1.0.0</Text>
      </View>
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: "#FFFFFF",
  },
  loadingContainer: {
    flex: 1,
    justifyContent: "center",
    alignItems: "center",
    backgroundColor: "#FFFFFF",
    padding: 20,
  },
  loadingText: {
    marginTop: 20,
    fontSize: 18,
    fontWeight: "600",
    color: "#333333",
  },
  stepText: {
    marginTop: 8,
    fontSize: 14,
    color: "#666666",
  },
  progressContainer: {
    width: "80%",
    height: 4,
    backgroundColor: "#E5E5EA",
    borderRadius: 2,
    marginTop: 24,
    overflow: "hidden",
  },
  progressBar: {
    height: "100%",
    backgroundColor: "#007AFF",
    borderRadius: 2,
  },
  progressText: {
    marginTop: 8,
    fontSize: 14,
    color: "#007AFF",
    fontWeight: "500",
  },
  errorContainer: {
    flex: 1,
    justifyContent: "center",
    alignItems: "center",
    backgroundColor: "#FFFFFF",
    padding: 20,
  },
  errorTitle: {
    fontSize: 20,
    fontWeight: "600",
    color: "#FF3B30",
    marginBottom: 8,
  },
  errorText: {
    fontSize: 14,
    color: "#666666",
    marginBottom: 24,
  },
  retryButton: {
    fontSize: 16,
    fontWeight: "500",
    color: "#007AFF",
    paddingVertical: 12,
    paddingHorizontal: 32,
    backgroundColor: "#F2F2F7",
    borderRadius: 8,
    overflow: "hidden",
  },
  header: {
    padding: 24,
    backgroundColor: "#007AFF",
    alignItems: "center",
  },
  title: {
    fontSize: 28,
    fontWeight: "700",
    color: "#FFFFFF",
  },
  subtitle: {
    fontSize: 14,
    color: "rgba(255, 255, 255, 0.8)",
    marginTop: 4,
  },
  content: {
    flex: 1,
    justifyContent: "center",
    alignItems: "center",
    padding: 20,
  },
  successIcon: {
    width: 80,
    height: 80,
    borderRadius: 40,
    backgroundColor: "#34C759",
    justifyContent: "center",
    alignItems: "center",
    marginBottom: 24,
  },
  successIconText: {
    fontSize: 40,
    color: "#FFFFFF",
  },
  welcomeText: {
    fontSize: 24,
    fontWeight: "600",
    color: "#333333",
    marginBottom: 12,
  },
  descriptionText: {
    fontSize: 16,
    color: "#666666",
    textAlign: "center",
    lineHeight: 24,
  },
  footer: {
    padding: 16,
    alignItems: "center",
    borderTopWidth: 1,
    borderTopColor: "#E5E5EA",
  },
  footerText: {
    fontSize: 14,
    color: "#999999",
  },
  versionText: {
    fontSize: 12,
    color: "#CCCCCC",
    marginTop: 4,
  },
});

export default App;

⚠️ 注意事项

启动屏图片配置

启动屏图片需要放置在正确的资源目录下:

复制代码
entry/src/main/resources/base/media/
├── splashIcon.png    # 启动屏图标
├── logo.png          # 应用 Logo
└── ...其他资源

图片规格建议

平台 建议尺寸 格式
手机 750 x 1334 px PNG
平板 2048 x 2732 px PNG

隐藏时机选择

选择合适的时机隐藏启动屏非常重要:

tsx 复制代码
// ✅ 推荐:在数据加载完成后隐藏
useEffect(() => {
  const init = async () => {
    await loadData();
    SplashScreen.hide();
  };
  init();
}, []);

// ✅ 推荐:使用状态控制
const [isReady, setIsReady] = useState(false);

useEffect(() => {
  loadData().then(() => {
    setIsReady(true);
    SplashScreen.hide();
  });
}, []);

// ❌ 不推荐:立即隐藏,可能导致白屏
useEffect(() => {
  SplashScreen.hide();
}, []);

iOS 与 HarmonyOS 差异

平台 show() 调用方式 hide() 调用方式
iOS 自动调用 JS 端调用
Android 自动调用 JS 端调用
HarmonyOS EntryAbility.ets 中手动调用 JS 端调用

在 iOS 中,show() 方法会自动在应用启动时调用。但在 HarmonyOS 中,需要在 EntryAbility.ets 中手动调用 SplashScreen.show() 方法。

版本差异

版本 说明 AutoLink
<= 3.3.0-0.0.2 不支持 AutoLink,已废弃
~3.3.2 支持 AutoLink,RN 0.72
~3.4.0 不支持 AutoLink,RN 0.77

常见问题

Q: 启动屏显示后无法隐藏?

A: 确保在 RN 端正确调用了 SplashScreen.hide() 方法,并且原生端配置正确。检查是否有异步操作阻塞了 hide 调用。

Q: 启动屏图片不显示?

A: 检查图片路径是否正确,确保图片放置在 entry/src/main/resources/base/media/ 目录下,并且文件名与代码中引用一致。

Q: 启动屏背景色不生效?

A: 确保背景色格式正确,使用十六进制颜色值如 #FFFFFF,不要使用 rgb()rgba() 格式。

Q: 启动屏显示时间过长?

A: 检查初始化逻辑是否有耗时操作,考虑使用懒加载或分步初始化,确保在合适的时机调用 SplashScreen.hide()

Q: 白屏闪烁问题?

A: 确保启动屏背景色与应用主界面背景色一致,避免颜色差异导致的视觉闪烁。

相关推荐
早點睡3902 小时前
ReactNative项目OpenHarmony三方库集成实战:react-native-shimmer-placeholder
javascript·react native·react.js
浮游本尊2 小时前
React 18.x 学习计划 - 第十五天:GraphQL 与实时应用实战
学习·react.js·graphql
qq_406176142 小时前
React 状态管理完全指南:从入门到选型
前端·javascript·react.js
终端鹿12 小时前
Vue3 模板引用 (ref):操作 DOM 与子组件实例 从入门到精通
前端·javascript·vue.js
蜡台13 小时前
Vue 打包优化
前端·javascript·vue.js·vite·vue-cli
卷帘依旧14 小时前
JavaScript中this绑定问题详解
前端·javascript
西洼工作室15 小时前
React轮播图优化:通过延迟 + 动画的组合,彻底消除视觉上的闪烁感
前端·react.js·前端框架
yaaakaaang15 小时前
(八)前端,如此简单!---五组结构
前端·javascript
EstherNi16 小时前
vue3仿照elementui样式的写法,并进行校验,并且有默认值的设置
javascript·elementui