ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Expo expo-splash-screen 模块实战:JS API、iOS/Android 原生配置与深色模式适配全解

Expo expo-splash-screen 模块实战:JS API、iOS/Android 原生配置与深色模式适配全解 Expo expo-splash-screen 模块实战JS API、iOS/Android 原生配置与深色模式适配全解【免费下载链接】expoAn open-source framework for making universal native apps with React. Expo runs on Android, iOS, and the web.项目地址: https://gitcode.com/GitHub_Trending/ex/expoexpo-splash-screen是 Expo 生态中负责「启动画面splash screen / launch screen」的官方模块它覆盖从 JS 层的显隐控制preventAutoHideAsync/hideAsync到 iOS Storyboard、Android 资源文件的完整原生配置链路。本文以模块的 README 为主体内容并结合仓库中 iOS 端实现、Android 端实现 与 JS 桥接层 的源码进行纵深解读。读完本文你将能够用 JS API 精确控制原生启动画面的自动隐藏时机、按模块规范手动配置双平台原生启动画面、并适配深色模式与 StatusBar 样式。1. 模块定位与核心特性启动画面是用户打开应用时看到的第一屏出现在应用加载完成之前。Expo 官方文档中常称之为 launch screen模块 README 的原文定义是expo-splash-screenallows you to customize your apps splash screen, which is the initial screen users see when the app is launched, before it has loaded.模块提供的核心特性包括三类内置的图片缩放模式resize modes、按外观区分的启动画面per-appearance即深色模式支持、以及启动期间的 StatusBar 定制。1.1 内置图片缩放模式expo-splash-screen内置了启动画面图片的展示处理逻辑其语义与 React NativeImage组件的resizeMode风格保持一致共三种模式CONTAIN等比缩放图片保持纵横比使图片的宽和高都不超过设备屏幕对应维度即「完整显示、可能留白」。这也是默认的 resizeMode。COVER等比缩放图片保持纵横比使图片的宽和高都不小于设备屏幕对应维度即「铺满屏幕、可能裁边」。NATIVE仅 Android直接利用 Android 在应用启动阶段展示静态位图的能力。由于 Android与 iOS 不同在启动阶段不支持对图片做拉伸处理该模式下应用会以图片原始尺寸、居中方式展示。选择该模式需要额外完成原生配置参见 NATIVE 模式调整 中res/drawable/splashscreen.xml与res/drawable/splashscreen_background.png两节。1.2 按外观区分的启动画面深色模式模块支持 per-appearance亦称 dark-mode启动画面响应 iOS 13 的系统外观切换以及 Android 10 的深色模式切换。实现方式在两大平台完全不同详见后文的 iOS/Android 配置章节。1.3 StatusBar 定制模块允许在启动画面展示期间定制 StatusBar其取值语义遵循 React Native StatusBar API 的定义可查阅 React Native 官方文档StatusBar条目。2. JavaScript APIAPI 入口如下import * as SplashScreen from expo-splash-screen;2.1 自动隐藏机制为什么需要preventAutoHideAsync通过该模块控制的原生启动画面会在 React Native 视图层级挂载后自动隐藏——也就是当你的应用首次render出视图组件时原生启动画面随即隐藏。源码印证了「内容出现即隐藏」这一机制Android 端SplashScreenManager.kt 中注册了一个ReactMarker监听器当收到CONTENT_APPEARED标记且preventAutoHideCalled为false时调用hide()iOS 端SplashScreenManager.swift 中监听RCTContentDidAppearNotification通知在onAppReady回调里执行同样判断。因此默认行为通常「够用」只有当应用需要先准备/下载资源或完成 API 调用、再渲染真实视图时才需要阻止自动隐藏。2.2SplashScreen.preventAutoHideAsync()使原生启动画面保持可见直到调用SplashScreen.hideAsync()。约束条件是必须在任何 React Native 视图层级渲染之前调用——既可以放在主组件的全局作用域也可以在初始渲染null的组件中调用见 第 3 节示例。返回值语义以 README 契约为准Promiseresolve 为true阻止自动隐藏成功resolve 为false原生启动画面此前已被阻止过自动隐藏例如已调用过本方法Promisereject大概率意味着原生启动画面此时已无法被阻止自动隐藏调用时它已经隐藏了。从当前源码结构看双平台的preventAutoHideAsync实现都会将userControlledAutoHideEnabled置为true并直接返回true见 Android 实现 与 iOS 实现源码注释明确说明该标记是供expo-router等上层库判断「启动画面是否由用户接管」的协议信号——调用过preventAutoHideAsync后internalMaybeHideAsync内部自动隐藏入口就不会再主动隐藏。2.3SplashScreen.hideAsync()隐藏原生启动画面仅当此前调用过preventAutoHideAsync()时才真正起作用。Promise在启动画面隐藏后 resolve。2.4SplashScreen.setOptions(options)从 SplashScreen.types.ts 的类型定义看可配置隐藏动画的默认行为export type SplashScreenOptions { /** 淡出动画时长毫秒。default 400 */ duration?: number; /** 是否以淡出动画方式隐藏启动画面。platform ios default false */ fade?: boolean; };两个参数的底层实现差异值得一读iOSSplashScreenManager.swift 中fade为true时走UIView.transition(..., options: .transitionCrossDissolve)交叉溶解过渡随后移除 loadingView否则直接isHidden true并移除视图AndroidSplashScreenManager.kt 中通过setOnExitAnimationListener对SplashScreenView执行alpha(0f)淡出动画使用AccelerateInterpolator时长即duration并对 API 31 以下的系统做了splashScreenViewProvider.remove()的分支处理。需要注意平台边界SplashScreen.native.ts 中setOptions在 Expo Go 内会打印警告并直接返回——该能力需要在 development build 中使用。2.5 平台实现分发模块采用 Expo 模块体系的标准分发JS 层入口 src/index.ts 同时导出 SplashScreen.tsWeb/无原生环境下的 no-op 占位实现函数体为空与 SplashScreen.native.ts通过requireOptionalNativeModule(ExpoSplashScreen)获取原生模块桥接。原生端模块名在双平台均注册为ExpoSplashScreen见 SplashScreenModule.kt 与 SplashScreenModule.swift。3. 使用示例3.1 在全局作用域调用preventAutoHideAsyncApp.tsximport React from react; import { StyleSheet, Text, View } from react-native; import * as SplashScreen from expo-splash-screen; // Prevent native splash screen from autohiding before App component declaration SplashScreen.preventAutoHideAsync() .then((result) console.log(SplashScreen.preventAutoHideAsync() succeeded: ${result})) .catch(console.warn); // its good to explicitly catch and inspect any error export default class App extends React.Component { componentDidMount() { // Hides native splash screen after 2s setTimeout(async () { await SplashScreen.hideAsync(); }, 2000); } render() { return ( View style{styles.container} Text style{styles.text}SplashScreen Demo! /Text /View ); } } const styles StyleSheet.create({ container: { flex: 1, alignItems: center, justifyContent: center, backgroundColor: #aabbcc, }, text: { color: white, fontWeight: bold, }, });要点preventAutoHideAsync在模块导入后、组件声明前于全局作用域执行并用.catch(console.warn)显式捕获错误——因为自动隐藏可能已发生Promise 会被 reject。3.2 在初始渲染null的组件中调用App.tsximport React from react; import { StyleSheet, Text, View } from react-native; import * as SplashScreen from expo-splash-screen; export default class App extends React.Component { state { appIsReady: false, }; async componentDidMount() { // Prevent native splash screen from autohiding try { await SplashScreen.preventAutoHideAsync(); } catch (e) { console.warn(e); } this.prepareResources(); } /** * Method that serves to load resources and make API calls */ prepareResources async () { await performAPICalls(...); await downloadAssets(...); this.setState({ appIsReady: true }, async () { await SplashScreen.hideAsync(); }); } render() { if (!this.state.appIsReady) { return null; } return ( View style{styles.container} Text style{styles.text}SplashScreen Demo! /Text /View ) } } const styles StyleSheet.create({ container: { flex: 1, alignItems: center, justifyContent: center, backgroundColor: #aabbcc, }, text: { color: white, fontWeight: bold, }, });这种模式的适用场景应用需要在首屏渲染前完成资源加载与 API 调用。组件先渲染null占位资源就绪后再切到真实视图并调用hideAsync()收尾。4. 安装托管managedExpo 项目npx expo install expo-splash-screenREADME 建议同时参阅 Expo 官方文档的 SplashScreen 章节。bare React Native 项目需先确保已安装并配置expo包参考 Expo 官方「Installing Expo Modules」指南再执行同样的npx expo install expo-splash-screen。iOS安装后运行npx pod-install。5. iOS 原生配置手动要获得原生启动画面iOS 生态中称为LaunchScreen行为需要提供SplashScreen.storyboard或SplashScreen.xib文件并配置 Xcode 工程。官方推荐流程为六步向Images.xcassets添加图片创建SplashScreen.storyboard为 Storyboard 中的ImageView选择Content Mode将SplashScreen.storyboard标记为 LaunchScreen可选启用深色模式可选定制 StatusBar。5.1 向Images.xcassets添加图片在 Xcode 工程打开.xcassets通常名为Images.xcassets或Assets.xcassets在内容面板新建New image set命名为SplashScreen提供准备好的启动画面图片需要三个不同的 1x/2x/3x 缩放版本。5.2 创建SplashScreen.storyboard这是启动画面的实际定义文件系统会用它来渲染启动画面。创建SplashScreen.storyboard文件添加View Controller打开Library右上角按钮→ 找到View Controller元素 → 拖入.storyboard添加Image View先移除View Controller中其他View元素 → 从Library找到Image View→ 拖拽为View Controller的子级设置Storyboard ID为SplashScreenViewController选中View Controller在右侧Identity Inspector中修改勾选Is Initial View Controller在Attributes Inspector的 View Controller 分区中勾选配置Image View图片源在Attributes Inspector的Image参数中选择SplashScreen配置Image View的Background需要#RRGGBB值时选择Custom在弹出的Colors Popup第二个标签页中从下拉框选择RGB Sliders。5.3 为ImageView选择Content Mode这一步决定图片如何展示在屏幕上打开SplashScreen.storyboard从View Controller中选中Image View在右侧Attributes Inspector找到Content Mode选择其一Aspect Fit—— 对应CONTAIN缩放模式Aspect Fill—— 对应COVER缩放模式也可以选择其他选项以实现不同的定位与缩放效果。5.4 将SplashScreen.storyboard标记为 LaunchScreen新创建的SplashScreen.storyboard必须在 Xcode 工程中标记为Launch Screen File才能从应用启动的最初阶段就呈现在Project Navigator中选中工程在TARGETS面板选中工程名切换到General标签找到App Icons and Launch Images分区的Launch Screen File选项选择或输入SplashScreen作为该选项的值。5.5 可选启用深色模式iOS 端有两种互补做法做法 A提供不同的背景色named colors在.xcassets中可新建也可复用已有如图片的 asset catalog创建New Color Set命名为SplashScreenBackground将Attributes Inspector中的Appearance改为Any, Dark分别为每种模式选择颜色在SplashScreen.storyboard中将其选为Image View的BackgroundBackground参数选择你创建的SplashScreenBackgroundnamed color。若还要让背景色铺满全屏需要把SplashScreen.storyboard改为「一个顶层View 两个Image View子视图」的结构底层为纯色背景图上层为真正的启动画面图第一个Image View背景色Image设为SplashScreenBackgroundContent Mode设为Scale To Fill通过Add new constraints底部菜单确保未勾选Constrain to margin每个方向的下拉框选择父View、值设0点击Add 4 Constraints使其撑满父视图第二个Image View真正启动画面图选择正确的Image与期望的Content Mode同样以四边约束撑满父视图。做法 B提供不同的深色模式启动画面图片打开SplashScreen图片集前面创建的 asset在Attributes Inspector的Appearances分区选择Any, Dark为深色模式框中放入专门准备的深色图片。系统切换到深色模式时即会改用这张图片。5.6 可选定制 StatusBarStatusBar hiding在TARGETS面板选中工程名切换到Info标签添加或修改Status bar initially hidden属性StatusBar style同样在Info标签添加或修改Status bar style属性。5.7 iOS 端实现印证从源码看JS 隐藏调用只是「摘掉」系统之上叠加的启动视图。SplashScreenManager.swift 的showSplashScreen()会从Info.plist读取UILaunchStoryboardName缺省为SplashScreen来实例化 Storyboard 作为loadingView若资源缺失则静默返回——注释说明这是为了在 brownfield混合集成应用中避免崩溃。hide()L35-L58在 App Extension 环境中直接跳过主线程上执行淡出或直接移除视图这解释了为何fade选项标注为platform ios。6. Android 原生配置手动要获得全原生的启动画面行为expo-splash-screen需要挂接到原生视图层级并消费若干放在/android/app/src/res目录下的资源。官方手动配置流程为八步配置res/drawable/splashscreen_image.png配置res/values/colors.xml配置res/drawable/splashscreen.xml配置res/values/styles.xml配置AndroidManifest.xml可选定制resizeMode可选启用深色模式可选定制 StatusBar。6.1res/drawable/splashscreen_image.png提供启动画面图片并放入res/drawable目录。该图片会在 Android 挂载应用原生视图层级时立刻加载。NATIVE模式调整若已在res/values/strings.xml中将string nameexpo_splash_screen_resize_mode覆盖为native则需为不同 DPI 设备准备多份资源。可在res目录下建立若干drawable-*子目录X为不同 DPI 等级系统按设备 DPI 选择对应版本res/drawable-mdpi— 1x — 中密度~160dpi基线密度res/drawable-hdpi— 1.5x — 高密度~240dpires/drawable-xhdpi— 2x — 超高密度~320dpires/drawable-xxhdpi— 3x — 超高超高密度~480dpires/drawable-xxxhdpi— 4x — 特超高密度~640dpi。每个目录都应有同名的splashscreen_image.png但分辨率按上述倍率缩放。6.2res/values/colors.xml该文件存放应用原生层复用的颜色。更新或新建以下内容resources color namesplashscreen_background#AABBCC/color !-- #AARRGGBB or #RRGGBB format -- !-- Other colors defined for your application -- /resources6.3res/drawable/splashscreen.xml该文件描述启动画面视图应如何被 Android 系统绘制。创建文件并写入 layer-list xmlns:androidhttp://schemas.android.com/apk/res/android item android:drawablecolor/splashscreen_background/ /layer-listNATIVE模式调整若已在strings.xml覆盖为native则应追加一个居中位图项layer-list xmlns:androidhttp://schemas.android.com/apk/res/android item android:drawablecolor/splashscreen_background/ item bitmap android:gravitycenter android:srcdrawable/splashscreen_image/ /item /layer-list6.4res/values/styles.xml定位主 Activity 的主题位于/android/app/src/res/values/styles.xml缺失则新建!-- Main activity theme. -- style nameAppTheme parentTheme.AppCompat.Light.NoActionBar item nameandroid:windowBackgrounddrawable/splashscreen/item !-- 指示系统以 splashscreen.xml 作为整个应用的背景 -- !-- Other style properties -- /style6.5AndroidManifest.xml让主AndroidManifest.xml中activity的android:theme指向包含启动画面配置的 stylemanifest xmlns:androidhttp://schemas.android.com/apk/res/android packagecom.example.myapp ... application ... !-- 确保 android:theme 指向包含原生启动画面引用的 style见 styles.xml -- activity android:name.MainActivity android:themestyle/AppTheme ... ... /activity /application /manifest6.6 可选定制resizeMode默认 resizeMode 为CONTAIN。如需更改在res/values/strings.xml中覆盖--- a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml ?xml version1.0 encodingUTF-8 standaloneyes? resources string nameapp_namesdk42/string string nameexpo_splash_screen_resize_modecontain|cover|native/string /resources6.7 可选启用深色模式不同的背景色—res/values-night/colors.xml在res/values-night目录下创建与colors.xml同构的文件系统在深色模式下会读取其中的值resources color namesplashscreen_background#AABBCC/color !-- #AARRGGBB or #RRGGBB format -- /resources不同的启动画面图片—res/drawable-night/splashscreen_image.png在res/drawable-night目录下放置与浅色版同名的图片即可。此步骤可选——例如你只有一张浅色 logo希望两种模式下仅背景色不同。6.8 可选定制 StatusBarStatusBar hiding更新res/values/styles.xml使状态栏完全隐藏取消隐藏则删除该条目或改为false!-- Main/SplashScreen activity theme. -- style nameAppTheme parentTheme.AppCompat.Light.NoActionBar item nameandroid:windowBackgrounddrawable/splashscreen/item item nameandroid:windowFullscreentrue/item !-- Other style properties -- /style若存在多个目录下的styles.xml含有完全相同的style条目例如res/values-night、res/values-night-v23务必同步修改。android:windowFullscreen的语义可参阅 Android 官方R.attr文档。StatusBar style仅对 Android 6.0 设备生效。要在指定系统颜色模式下强制light/dark状态栏样式需要准备或更新res/values-v23/styles.xml该属性自 API 23 起支持因此必须放在特定命名的目录中!-- Main/SplashScreen activity theme. -- style nameAppTheme parentTheme.AppCompat.Light.NoActionBar item nameandroid:windowBackgrounddrawable/splashscreen/item item nameandroid:windowLightStatusBartrue|false/item !-- Other style properties -- /style取值true为深色图标false为浅色图标。同样注意同步res/values-night-v23等目录下的同名style条目。多 API 级别资源覆盖的机制详见 Android 官方「providing resources」文档。6.9 Android 端实现印证从 SplashScreenManager.kt 看模块基于 AndroidXinstallSplashScreen()API 工作keepSplashScreenOnScreen期间通过OnPreDrawListener持续返回false阻止内容绘制——源码注释解释了这么做的原因setKeepOnScreenCondition()在 API 33 以下不可用因此自行实现hide()只是把开关置为false真正的移除发生在系统 splash 退出动画回调里setOnExitAnimationListener见 L38-L53并按Build.VERSION分支处理SplashScreenView.remove()针对 API 31–33 上 splash 退出监听器可能在 Activity 停止后触发导致的SurfaceControl.checkNotReleased()崩溃Google Issue Tracker 242118185专门注册了ActivityLifecycleCallbacks在onActivityStopped时clearOnExitAnimationListener()作为缓解措施。这些细节说明JS 层hide()的调用是「解除保持条件」最终呈现仍由 Android 系统 splash 框架完成这也决定了NATIVE模式必须走纯资源配置strings.xml 多密度 drawable路线。7. 配置插件Config Plugin除手动配置外模块自带配置插件可将上述资源文件自动化生成插件入口为 withSplashScreen.ts按平台拆分为withIosSplashScreen、withAndroidSplashScreen等插件见 plugin/src 目录分别处理 iOS 的 assets/Info.plist/Storyboard 与 Xcode 工程修改以及 Android 的 drawable/strings/styles/MainActivity 注入。各插件均配有测试用例如 withIosSplashScreen-test.ts、withAndroidSplashDrawables-test.ts 等可用来核对插件产出的资源结构。使用方式是通过app.json的plugins字段声明expo-splash-screen并传入图片/背景色配置模块根目录提供 app.plugin.js 作为插件入口。8. 已知问题8.1 iOS 缓存iOS 应用的启动画面有时会遇到缓存问题新图片出现前旧图片会闪现一下。官方建议重启设备、卸载并重新安装应用但缓存可能持续一两天请对前述步骤保持耐心。8.2NATIVE模式会将启动画面图片略微上推即 NATIVE 模式预览 中可见的偏移现象。模块维护方已知晓该问题截至 README 撰写时尚无解决方案。9. 从旧版本迁移9.1 从expo-splash-screen 0.12.0 迁移旧版代码保持向后兼容仍可按原方式工作。若要迁移到新的模块 API步骤如下将项目从react-native-unimodules迁移到expo-modules-core从MainActivity中移除旧的expo-splash-screen代码--- a/android/app/src/main/java/com/helloworld/MainActivity.java b/android/app/src/main/java/com/helloworld/MainActivity.java import com.facebook.react.ReactRootView; import com.swmansion.gesturehandler.react.RNGestureHandlerEnabledRootView; -import host.exp.exponent.experience.splashscreen.legacy.singletons.SplashScreen; -import host.exp.exponent.experience.splashscreen.legacy.SplashScreenImageResizeMode; - public class MainActivity extends ReactActivity { Override protected void onCreate(Bundle savedInstanceState) { // This is required for expo-splash-screen. setTheme(R.style.AppTheme); super.onCreate(null); - // SplashScreen.show(...) has to be called after super.onCreate(...) - SplashScreen.show(this, SplashScreenImageResizeMode.CONTAIN, ReactRootView.class, false); }在strings.xml中覆盖默认resizeMode--- a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml ?xml version1.0 encodingUTF-8 standaloneyes? resources string nameapp_namesdk42/string string nameexpo_splash_screen_resize_modecontain/string /resources10. 致谢Hall of Fame该模块构建在以下开源项目Expo 仓库内 README 的 Hall of Fame 章节的坚实工作之上react-native-splash-screencrazycodeboyreact-native-bootsplashzoontekreact-native-makebamlab11. 延伸阅读模块变更记录CHANGELOGJS 桥接层与类型定义SplashScreen.native.ts、SplashScreen.types.ts原生实现iOS SplashScreenManager、iOS 模块定义、Android SplashScreenManager、Android 模块定义【免费下载链接】expoAn open-source framework for making universal native apps with React. Expo runs on Android, iOS, and the web.项目地址: https://gitcode.com/GitHub_Trending/ex/expo创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表