从 Jetpack SplashScreen 深度探讨 App 启动画面的前世今生~

2,175 阅读22分钟

可能有不少小伙伴已经留意到 Android 12 上推出了全新的启动画面 API SplashScreen。同时为了兼容低版本的使用,Jetpack 框架里推出了同名库。

本次针对这个库的使用和原理做个全面的介绍,将按照如下几个方面去展开:

  • 首先简单探讨一下为什么需要启动画面
  • 接着一起回顾一下之前打造启动画面的常规做法
  • 然后重点阐述一下 SplashScreen 库的目的,以及如何使用
  • 最后介绍一下 SplashScreen 库大致的实现原理

1. 为什么需要启动画面?

1.1 启动画面的作用

12-widget
  • 当我们打开各种桌面软件或移动端 App 的时候,首先会看到的经常是一段过渡画面。用以展示产品所属的公司、品牌的图标、公司的 Slogan 等,借以宣传公司的调性和传达一些特色

  • 同时在节日庆典、特殊活动的时候展示广告页面或活动海报

  • 在启动画面展示的同时,背后的内容得以加载

同时在一定程度上缓解用户的等待,启动画面良好设计的话,还能使得这个枯燥的过程显得有趣和充满期待!

1.2 App 启动的典型流程

来看一下 App 启动的典型流程。

点击 Launcher 上的 Logo 之后,首先用户将看到一个启动画面,如果是初次启动的话接着会展示 Guide 画面引导用户了解如何使用;如果是节日或广告需要的话展示的是广告宣传画面,最后才是展示内容的主画面。

我们本次探讨的核心就是第一个启动画面。

Jetpack SplashScreen 库正是用来打造用户看到的第一个启动画面,在讲述之前,先来回顾一下之前的常规做法。

2. 常规做法

我们知道从点击 Launcher 上的 icon 到 App 内容描画之前,有很多准备工作。在这段时间是看不到目标 App 内容的。

为了快速响应用户的点击或缓解用户的等待,在 App 描画之前系统将启动专用的 SplashScreenWindow 盖在 App 之上。该Window 的呈现源自于 App 主题方面的配置。

配置的不同进而影响到启动 Window 的表现,我们来看看各种配置的做法。

2.1 默认的启动画面背景

假使 App 的主题针对 Window 背景什么都不设置,你会发现启动的过程中总有个默认的白画面一闪而过。

 <style name="SplashThemeBase.DefaultBg"/>
12-widget

2.2 不设置启动画面背景

当然可以将 windowBackground 设置为空,可是你会发现启动的过程中又变成黑色一片闪过。

<style name="SplashThemeBase.NoBg">
    <item name="android:windowBackground">@null</item>
</style>
12-widget

无论是白画面还是黑画面一闪而过,都无法接受,所以还得继续优化。

2.3 直接关闭启动画面

无论白画面还是黑画面一闪而过的体验都不好,这时候可能会想到关闭默认的启动画面。通过 windowDisablePreview 属性可以彻底关闭启动画面。

<style name="SplashThemeBase.TransparentBg">
    <item name="android:windowDisablePreview">true</item>
</style>

这样一来,确实看不到启动画面的存在了,但整个过程貌似“变慢”了。实际上性能并没有**“劣化”**,只是启动中过渡的 Window 不存在了。假使 ApplicationActivity 等组件里存在耗时逻辑的话,这种劣化会更加明显。

相较于前面的黑白画面一闪而过,这种变慢的体验也好不到哪去。

12-widget

2.4 设置启动画面背景

绕来绕去,最后我们发现还是得提供一个恰当的启动画面。具体在于将 UI 提供的背景色、Icon、Brand 等资源组合成一个 LayerListDrawable,然后将其设置到 windowBackground 中即可。

<!--theme-->
<style name="SplashThemeBase.WithBg">
    <item name="android:windowBackground">@drawable/ic_splash_bg</item>
</style>

<!--drawable-->
<layer-list ... >
    <item android:drawable="@drawable/ic_logo"></item>
    <item  android:drawable="@drawable/ic_brand"></item>
</layer-list>
12-widget

这种做法既可以提供体验良好的过渡画面,同时可以快速响应用户的点击。可以说是最为简单也最为便捷的一种实现方式。

2.5 设置启动画面内容

Android 8 加入一个配置启动画面的属性 windowSplashscreenContent,用于配置启动画面的内容。它的优先级高于 windowBackground。两者一起设置的话呢,会覆盖在 windowBackground 上。大体的效果呢和前面的差不多,不再赘述。

<style name="SplashThemeBase.WithBg.SplashScreenContent">
    <item name="android:windowSplashscreenContent">@drawable/ic_splash_content</item>
</style>

注意:这个属性自 Android 8 加入,从 12 开始废弃

2.6 定义启动专用 Activity

有的时候觉得单单设置一张背景图太过简单,无法实现一些复杂的启动效果。又或者希望能和广告或节日的画面深度配合起来。这时候难免需要定义一个专门的启动 Activity

具体来讲需要在入口 Activity 前预设专门的 Splash Activity来显示启动画面,Splash Activity 在一定时间后自动跳转到入口 Activity

启动 Activity 提供类似启动画面的布局。

<androidx.constraintlayout.widget.ConstraintLayout ... >
    <ImageView android:src="@drawable/ic_icon" ...  />
    <ImageView android:src="@drawable/ic_brand" ...  />
</androidx.constraintlayout.widget.ConstraintLayout>

在画面展示后配合定时跳转等逻辑去打开广告或海报等画面。

class SplashActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        setContentView(binding.root)
        goToMainScreenDelayed()
    }
}

因为有了自己实现的启动 Activity,所以系统提供的启动画面可以关闭。当然也可以将系统的启动画面的 windowBackground 设置为整体背景色相接近的色调。

<style name="SplashThemeBase.NoSplashWindow" >
    <item name="android:windowDisablePreview">true</item>
</style>
12-widget

上面是通过 Activity 方式打造的启动画面的效果。

DEMO 的效果和前面的主题方式的效果差不多,但是这种做法将会拥有更大的定制空间。比如可以接入跨平台的 Lottie 动画库,打造更为丰富、炫酷、平台统一的启动效果!

3. Jetpack SplashScreen 的目的和使用

可以看到之前提供的主题配置方式只能展示一张背景图,这种静态效果看久了着实单调。而且这张图还需要我们自行组合 App 的 Icon、Brand 的 Logo,这就需要考虑很多 Size、位置、屏幕适配等 UI 细节,不太方便。如果需要丰富、灵活一些的效果,又需要自定义专用的 Activity 来解决,这样一来又显得繁琐。

正因为此,Jetpack 上推出了 SplashScreen 库,来高效、灵活地重塑启动画面。而 SplashScreen 库基于 Android 12 的新特性 SplashScreen API,所以我们先来了解一下这个特性。

3.1 Android 12 SplashScreen

3.1.1 特性介绍

Android 12 上 App 启动的时候将先展示一个启动画面的进场部分,该部分的呈现由 SplashScreen API 提供的更为丰富的属性来定制。之后在App 描画第一帧的时候将该画面的视图回调给我们来定制退场部分的效果。

进场和退场两部分共同组成了全新的 SplashScreen API。

需要说明的是,不同于之前的版本,12 上什么即便都不配置,启动画面会是白色背景下展示 Adaptive Icon 的一个效果。这相较于之前的纯白背景要好了不少。

3.1.2 画面构成

SplashScreen 的画面由一个 SplashScreenWindow 承载,中间是一个 AdaptiveIcon,SplashScreen API 则提供了丰富的属性来指定该的各部分构成,简洁明了。比如:

  • windowSplashScreenAnimatedIcon:指定 App Icon 的图片,如果指定的是 Animated Vector Drawable 的话,在启动画面展示的同时会执行一个 Icon 动画效果
  • windowSplashScreenIconBackgroundColor:Icon 背景色
  • Adaptive Icon Mask:由 Launcher 的配置指定,主题无法更改,说明一下
  • windowSplashScreenBackground:Window 背景色,和之前的 windowBackground 属性类似
  • windowSplashScreenBrandingImage:放置品牌 Logo

当 SplashScreenWindow 退场的时候整个画面的视图会被封装到 SplashScreenView 中供我们实现退场效果。

下面这是 Gmail 适配了 SplashScreen API 后的启动效果。简单来讲就是一个进场的 M 字母的 Path 动画,退场的时候直接结束到目标内容的效果。

并没有适配 Icon 背景、Brand Logo、退场动画等特性,后面的章节将充分演示各项特性。

12-widget

3.1.3 启动规则

在冷启动和暖启动的时候展示 SplashScreen,热启动的时候从不展示该画面。

  • 冷启动:进程尚不存在的情况下点击 App Icon 的启动
  • 暖启动:进程存在但 App 不在前台,并且 Activity 已经销毁了的启动
  • 热启动:进程存在但 App 不在前台,并且 Activity 尚未销毁的启动

3.2 Jetpack SplashScreen

3.2.1 目的

Android 12 的 SplashScreen API 非常好用,但是低版本无法尝鲜,所以 Jetpack 里推出了同名库。

它是为了兼容 12 之前低版本的 UI 库,最早兼容到 Android 6(API 23),6.0 及以上的设备占用率近 9 成,完全够用了。它通过整合和统一 SplashScreen API,达到一套代码实现近乎一致的 App 启动体验。

简述:

  • 兼容低版本系统的启动画面 UI 库
  • 支持到 Android 6.0(近 9 成)
  • 整合 12 的 SplashScreen API
  • 一套代码打造近乎一致的启动效果

3.2.2 提供的主题和属性

SplashScreen 库的版本到了 1.0.0-alpha01 版,在 gradle 文件里简单导入即可。

dependencies {
    implementation "androidx.core:core-splashscreen:1.0.0-alpha01"
}

它预设了主题供我们使用,必须将入口 Activity 的主题改成扩展自这个主题。

<style name="Theme.SplashScreen" parent="Theme.SplashScreenBase">
    <item name="windowSplashScreenAnimatedIcon">@android:drawable/sym_def_app_icon</item>
    ...
</style>

同时预设了些属性供我们覆写和提供自己的启动画面资源。

<attr format="reference" name="postSplashScreenTheme"/>
<attr format="reference" name="windowSplashScreenAnimatedIcon"/>
<attr format="integer" name="windowSplashScreenAnimationDuration"/>
<attr format="color" name="windowSplashScreenBackground"/>

这里需要提醒两点:

  1. SplashScreen 库针对 12 之前的低版本暂不支持设置 Icon Bg,和 Brand Icon 的属性
  2. postSplashScreenTheme 属性用来指定 Activity 最终的主题,避免使用预设的主题影响目标 Actvitiy 的主题展示

3.2.3 提供的 API

SplashScreen 库还提供了逻辑方面的 API,供我们去实现启动画面时长的控制,启动画面退场效果的实现需求。

API说明
SplashScreenJetpack版获取定制启动画面入口的类
Activity#installSplashScreen()覆写Activity,用以获取定制入口的静态成员函数
setKeepVisibleCondition指定保持启动画面展示的条件
KeepOnScreenCondition实现展示条件的接口
setOnExitAnimationListener监听启动画面的退出时机
OnExitAnimationListener启动画面退出的回调接口
SplashScreenViewProvider定制退场效果的启动画面视图

3.3 SplashScreen 库的使用

3.3.1 打造进场效果

进场效果的部分只涉及到资源方面的配置。

首先扩展自 SplashScreen 库的预设主题作成一个 Base 主题,指定诸如启动画面背景、目标 Activity 背景等方面的共同属性。

<style name="SplashScreenTheme.Base" parent="Theme.SplashScreen">
    ...
    <item name="windowSplashScreenBackground">@color/splashBackground</item>
    <item name="postSplashScreenTheme">@style/TargetScreenTheme</item>
</style>

其他的一些属性需要针对低版本和 12 作区分。比如 12上可以指定其独有的动画 Icon,Icon 背景和 Brand logo,而低版本上指定一个静态 Icon 即可。但为了效果接近,可以指定 App 的 Adaptive Icon。

<!-- version:12- -->
<style name="SplashScreenTheme" parent="SplashScreenTheme.Base">
    <item name="windowSplashScreenAnimatedIcon">@mipmap/ic_icon_adaptive</item>
</style>

<!-- version:12+ -->
<style name="SplashScreenTheme" parent="SplashScreenTheme.Base">
    <item name="windowSplashScreenAnimatedIcon">@drawable/ic_icon_animated</item>
    <item name="android:windowSplashScreenIconBackgroundColor">@color/iconBackground</item>
    <item name="android:windowSplashScreenBrandingImage">@drawable/ic_brand</item>
</style>

我们来看一下分别运行在 Android 8 和 12 上的进场效果:

可以看到高低版本上是比较接近的进场画面,只不过 12 上多了特有的 Kotlin 的组合动画和一个 TechMerger 字样的 Brand Logo。

3.3.2 延长启动画面

随着 App 第一帧的开始描画,SplashScreenWindow 即将消失。如果背面的业务逻辑尚未准备完毕,那体验不是很好,鱼骨屏什么的就是用来优化这个问题。

当然对于启动画面来讲,现在可以通过 SplashScreen 库的 API 来灵活控制启动画面的时长,确保内容好了再退出。这个 API 就是 installSplashScreen()

通过这个静态函数可以拿到定制的入口,之后可以调用 setKeepVisibleCondition() 设置启动画面保持展示的条件,条件可以 ViewModel 的耗时加载相结合。这里提供的是一个 ViewModel 实例初始化 2s 之后再退出启动画面的一个模拟逻辑。

注意:由于 installSplashScreen 函数内部将调用 setTheme 反映实际的主题,所以需要在 setContentView 之前调用

class JetpackSplashActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        splashScreen = installSplashScreen()
        setContentView(binding.root)
        splashScreen.setKeepVisibleCondition {
            !viewModel.isDataReady() 
        }
    }
}

class MyViewModel(application: Application): AndroidViewModel(application) {
    companion object {
        const val WORK_DURATION = 2000L
    }
    private val initTime = SystemClock.uptimeMillis()
    fun isDataReady() = SystemClock.uptimeMillis() - initTime > WORK_DURATION
}

我们来看一下分别运行在 Android 8 和 12 上启动画面的延长效果:

可以看到高低版本上都成功实现了启动画面的延迟退出。

3.3.3 打造整体退场效果

当启动画面退出的时候如果能提供一个无缝过渡到目标内容的动画,体验会更好。我们可以利用 SplashScreen 库的 setOnExitAnimationListener 来针对进场画面的整体视图实现一个退场的动画。

比如这里我们定制一个 SplashScreen 整体的下移淡出效果。

注意:记得在动画结束的时候调用 SplashScreenViewProvider 的 remove() 及时将启动画面的视图移除,否则可能覆盖在实际画面上,遮挡内容。

class JetpackSplashActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        splashScreen.setOnExitAnimationListener { splashScreenViewProvider ->
            showSplashExitAnimator(splashScreenViewProvider.view) {
                splashScreenViewProvider.remove()
            }
        }

    private fun showSplashExitAnimator(splashScreenView: View, onExit: () -> Unit = {}) {
        ...
        AnimatorSet().run {
            ...
            playTogether(slideDown, alphaOut)
            doOnEnd { onExit() }
        }
    }
}

我们来看一下分别运行在 Android 8 和 12 上的退场效果:

可以看到 12 上的 Brand Logo 是一起执行的退场动画,总的来说高低版本上都实现了几乎一致的整体下移和淡出的效果。

3.3.4 打造 Icon 独有的退场效果

如果觉得整体的退场动画太过突兀或夸张,还可以针对 App Icon 作单独的退场效果。基本逻辑和定制整体的退场效果差不多。,区别在于执行动画的对象由 view 变成了 IconView

同样要注意在动画结束的时候调用 remove()

class JetpackSplashActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        splashScreen.setOnExitAnimationListener { splashScreenViewProvider ->
            showSplashIconExitAnimator(splashScreenViewProvider.iconView) {
                splashScreenViewProvider.remove()
            }
        }

    private fun showSplashIconExitAnimator(iconView: View, onExit: () -> Unit = {}) {
        ...
        AnimatorSet().run {
            ...
            playTogether(alphaOut, scaleOut, slideUp)
            doOnEnd { onExit() }
        }
    }
}

这里延时的是针对 Icon 的上移和淡出动画,来看一下效果:

可以看到 12 上的 Brand Logo 是不动的,整体上都是一个 Icon 上移和淡出的效果。

3.3.5 控制退场动画的时长

设备性能或状态会影响 App 开始描画的时间,为了让用户早点看到实际内容,可以灵活控制退场动画的时长。比如当描画得晚,可以考虑不展示退场动画或执行极短的固定时长;当描画得早,进场动画可能尚未结束,将剩余的时长交接给退场部分。

主要通过 SplashScreen 库返回的进场动画开始时刻(iconAnimationStartMillis)和总时长(iconAnimationDurationMillis)的 API,与退场回调的当前时刻进行计算即可。

需要注意的是,针对 12 之前的版本,SplashScreen 库的进场部分不支持 Icon 动画,所以上述的两个属性总是返回 0,需要特别处理一下。

private fun getRemainingDuration(provider: SplashScreenViewProvider): Long {
    val animationDuration = provider.iconAnimationDurationMillis
    val animationStart = provider.iconAnimationStartMillis

    return if (animationDuration == 0L || animationStart == 0L)
        defaultExitDuration
    else (animationDuration - SystemClock.uptimeMillis() + animationStart)
        .coerceAtLeast(0L)
}

3.4 Lottie 支持 SplashScreen 吗?

Lottie 是跨平台的动画库,非常好用。那么 SplashScreen 库支持吗?

并不支持,因为 SplashScreen 库只能配置 Drawable 文件,不可以替换 View。而 Lottie 的效果完全依赖于自定义的 AnimationView

但 SplashScreen 的设计者提供了一个魔改思路:

  1. 拷贝 Lottie Json 的第一帧,做成 SVG 并转换为 Animated Vetor Drawable,设置到 Splash Icon
  2. 目标布局正中放入执行 Icon 动画的 View
  3. 在 Splash 退出的时候将 LottieAnimationView 解析 Json 并执行动画
  4. 动画结束后记得将真正的视图展示

代码示例:

splashScreen.setOnExitAnimationListener { vp ->
    val lottieView = findViewById<LottieAnimationView>(R.id.animationView)
    ...
    lottieView.postDelayed({
        vp.view.alpha = 0f
        vp.iconView.alpha = 0f
        lottieView!!.playAnimation()
    }, delay)

    lottieView.addAnimatorListener(object : AnimatorListenerAdapter() {
        override fun onAnimationEnd(animation: Animator?) {
            imageView.visibility = View.VISIBLE
        }
    })
}

我们来看一下高低版本上魔改之后支持 Lottie 的 SplashScreen 效果,可以看到是几乎完全一致的非常流畅、丝滑的启动体验。

但这种做法违背了 SplashScreen 库的设计初衷,不建议使用,这里只是提供一种思路。

更详细的说明可以参考官方的 DEMO 介绍:github.com/vcaen/splas…

4. SplashScreen 库的实现原理

接下来了解一下 SplashScreen 库如何兼容低版本,实现几乎一致的启动效果。

4.1 总体原理

写这个资料的时候 Android 12 的源码尚未公开,最近公开了之后看了一眼,发现 SplashScreen 的实现非常繁杂。

这里简单提一下关键地方,SplashScreenWindow 退出的时候,系统会通过 AIDL 将封装了启动画面的信息的序列化对象传递给 App 进程。App 将对象反序列化并创建退场视图即 SplashScreenView,然后添加到 DecorView 上去。之后 App 即可对这个视图作退场效果的定制。

更多全面的细节,感兴趣的朋友可自行研究。本次主要Jetpack SplashScreen 库的源码进行解读。

总体的原理分为进场和退场两个部分。

进场部分的画面针对 12 之前的版本是 windowBackground 思路,针对 12 是系统专属的 SplashScreen 系属性实现的。

退场部分,在 12 之前是自定义的 FrameLayout 添加到 Activity 的 ContentView 上,12 则是反序列化的 SplashScreenView 添加到了 DecorView 上。

4.2 进场画面的原理

进场画面的原理完全依赖于主题的配置,面向低版本的话和之前的常规做法是一样的思路,即提供一个读取我们配置的画面资源的 LayerListDrawable 放置到 windowBackground 中。

<style name="Theme.SplashScreen" parent="Theme.SplashScreenBase">
    ...
</style>

<style name="Theme.SplashScreenBase" parent="android:Theme.NoTitleBar">
    <item name="android:windowBackground">@drawable/compat_splash_screen</item>
    ...
</style>
<!--compat_splash_screen.xml-->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:gravity="fill">
        <color android:color="?attr/windowSplashScreenBackground" />
    </item>
    <item
        android:drawable="?attr/windowSplashScreenAnimatedIcon"
        ... />
</layer-list>

面向 12 的话,则是由系统回调专属的属性去构建启动画面的视图。

<style name="Theme.SplashScreen" parent="android:Theme.DeviceDefault.NoActionBar">
    <item name="android:windowSplashScreenAnimatedIcon">?windowSplashScreenAnimatedIcon</item>
    <item name="android:windowSplashScreenBackground">?windowSplashScreenBackground</item>
    ...
</style>

4.3 定制入口的初始化

获取 SplashScreen 实例的 API 是 installSplashScreen()。其运行在低版本上的话,需要额外读取和缓存 Icon 和 Background 的配置,然后读取并设置目标 Activity 的 Theme。12 上的话,Window 背景,Icon 和 Branding 等属性由系统控制,只需要配置目标 Activity 的 Theme 即可。

4.4 延长启动画面

通过 setKeepVisibleCondition() 可以延长启动画面的展示,无关运行的版本,原理都是向 ContentView 的 ViewTreeObserver 注册OnPreDrawListener回调来实现。

系统在描画前先回调 onPreDraw(),获取是否放行描画的条件,此处将回调 KeepVisibleCondition 的逻辑。如果不放行,在下次屏幕刷新的时候继续回调,直到满足条件开始描画,启动 Window 消失。描画放行的时候,低版本额外需要手动调用 dispatchOnExitAnimation 来执行退出回调,12 则由系统自行执行。

需要注意:这个时候退场用的自定义视图仍然还没添加上来,只是延迟了 SplashScreenWindow 的退出而已。

4.5 退场画面的回调

setOnExitAnimationListener() 可以监听退场时机。

运行在低版本上的时候,需要手动加载启动画面的布局到 ContentView中,并将之前设置的 Window Background 和 Icon 等属性显示。然后添加 Layout change 回调,在布局完毕的时候通过 adjustInsets 特殊处理将 Icon 位置调整一下。判断启动画面保持条件是否达到,达到的话调用 onSplashScreenExit

运行在 12 上的时候,布局不需要手动准备,通过 12 专用的系统接口,将视图缓存到 Provider 里即可,后续的 Exit 也由系统执行。

4.6 adjustInsets 的特殊处理

面向低版本的退场画面在布局成功后会调用的特殊处理。

进场部分的是 Window Drawable,Icon 是居中的。但退场部分是向 ContentView中手动添加的 Framelayout 布局,Icon 在布局里是居中的,但由于 StatusBarNavigationBar 高度不一样,Icon 在整个 Window 里是偏下的。

如果不加干预的话,进场过渡到退场的时候,Icon 会发生跳跃。

源码通过 windowInsets API 获取状态栏和导航栏的高度,取差值的一半交由 IconView 去移动到 window 中间。

private class Impl23(activity: Activity) : Impl(activity) {
    override fun adjustInsets( ... ) {
        // Offset the icon if the insets have changed
        val rootWindowInsets = view.rootWindowInsets
        val ty = rootWindowInsets.systemWindowInsetTop
                - rootWindowInsets.systemWindowInsetBottom
        splashScreenViewProvider.iconView.translationY = -ty.toFloat() / 2f
    }
}

结语

到这里我们探讨了启动画面的必要性、回顾了启动画面打造的常规做法、介绍了 Android 12 上 SplashScreen API、以及详细了解了 Jetpack SplashScreen 库的目的、使用细节和实现原理

可以看到 SplashScreen 库简单又清晰,可以帮助我们灵活、高效地重塑启动画面,主要体现在这么几个方面:

  • 配置图标动画、图标背景、品牌 Logo 等新元素,打造丰富的进场效果

  • 适当地调节启动画面的展示时间,以配合后台的加载

  • 针对整体或 Icon 视图,灵活打造无缝衔接的退场效果

  • 灵活控制退场动画的有无和时长,自然地过渡到目标内容

值得提醒的是:启动画面只是过渡,动画效果避免突兀,更不要过多占用用户时间!

参考资料&资源分享

主要分享一下使用到的参考资料以及分享不错的 DEMO,大家可以通过这些资料和 DEMO 切实感受和实践下 SplashScreen 库的玩法!

名称地址
Splash Screen API 的官方介绍developer.android.google.cn/about/versi…
Jetpack SplashScreen 库的地址developer.android.google.cn/jetpack/and…
本文源码公开后的 SplashScreen 项目github.com/ellisonchan…
ComposeBird 项目github.com/ellisonchan…

最后一个着重说一下,这是我之前采用 Jetpack Compose 写的 Flappy Bird 小游戏。我抽空给它适配了 SplashScreen 库的功能。可以看到游戏启动的时候小鸟渐渐飞进来,之后小鸟向上淡出到游戏界面的效果。

12-widget

FAQ

问题回答
1. 不知道有没有解决多语言适配的问题 多屏幕适配问题不显示文字,不存在语言问题;Icon 按照 Adaptive Icon 规格提供即可,尺寸及居中的问题,SplashWindow 功能会处理
2. 这样等于固定了样式,请问产品能答应吗,到头来还不如就设置一张图片的好,感觉没有用武之地,而且ios没有这玩意,无法同步,注定是个鸡肋这个 API 在设置静态背景之外可以设置动画图标,品牌图标,退场效果等,可以给需要丰富启动效果的 App 提供便利,iOS 的话我想肯定有同样的办法做到,只不过没 Android 方便。
3. 了解确实可以,但这东西在国内似乎没啥用?国内啥 APP不放点广告,有能耐的自己做广告宣传 没能耐的接穿山甲,接优量汇。其实不影响广告界面的展示,因为一般广告展示前还有个展示 Logo 的启动画面,可以把这个画面用 SplashScreen 实现。
4. 站在用户的角度也站在我急躁性格的我,在用一个 app的时侯我一秒了也不想看到Splash页面的存面.是的,SplashScreen 不能占用过长的时间。所以一般 Icon 动画控制在 1s 内,而退场动画的时长可以尽量缩短,或者弱化退场效果。
5. SplashScreen和目前设置启动背景有什么差异啊。是否就是比现在的多了可以设置启动画面的动画。除了背景还可以设置icon进场动画和品牌logo,除此之外可以便捷地控制启动画面的时长,还可以定制退场动画。总结起来讲,比以前定制的空间更大,但API并不复杂。
6. 如果描画的时候进场动画没有执行完毕就消失,体验会不会不好?描画很快第一帧很早的话,确实可能发生进场动画进行过程中突然消失的现象。
但进场动画的时长最长只能设置为 1s。第一帧开始描画且 1s 动画还没执行完毕的情况很少发生。
一旦发生的话,可以考虑将剩余时间交给退场动画来优化一下。这样子能够过渡一下再退出,而不是直接消失。
7. 如果延长条件一直保持会怎么样?启动动画会怎么样?第一帧的描画一直被挂起,启动画面 Window 一直残留,保持着在进场动画最后一帧,无法消失。
8. Activity 指定的 Theme 和 postSplashScreenTheme 的区别是?前者专门为展示启动画面准备的,后者则是 Activity 的最终主题,目的避免使用启动画面主题影响到实际的显示。
9. installSplashScreen 的准确作用?不调用的话会怎么样?installSplashScreen 只是初始化一下定制入口,以及获取设置资源和设置主题,看不到退场效果。
如果不调用的话,仍然可以看到进场画面,但无法定制退场效果和更新 Activity 主题。
10. 为什么 SplashScreen 库不设计成低版本也支持 Brand Icon 和 动画 Icon?Brand Icon 不像 App Icon,尺寸和形状不一,利用现有的 Background Drawable 担心有尺寸和显示的问题。Icon 动画需要拿到动画 Drawable 并手动执行,单单利用 WindowBackground 属性无法做到支持。
11. App 开始 draw 的时候启动 Window 便消失,那之后的属性动画为什么还可以执行?12 的时候虽然 Window 退出了,但内部的视图传递了过来放在我们的 DecorView 上,而 12 之前则是自定义的 FrameLayout 添加到了我们的 ContentView 上。
退场的时候我们执行动画的 View 本质上都是我们自己的 View,所以即便 SplashScreenWindow 消失了也没关系。
12. 为什么 Android 12 是将视图添加到 DecorView,而 Jetpack 是添加到 ContentView?覆盖到 DecorView 上去,会和系统栏的显示产生冲突,猜测是 Jetpack 无法解决一些诸如 StatusBar,NavigationBar 的显示问题。而 Android 12 是系统级别的,可以更高权限地解决这些细节。
13. SplashScreen 功能和 Activity 的 transition 动画的关系?transition 包括 windowIsTranslucent,是针对 Activity window 而言的,和 SplashScreen 的启动画面专用属性是不一样的。应用了 SplashScreen 特性的话,入口 Actvitiy 的动画就不需要了。
14. 退场动画支持共享元素动画吗?SplashScreenView 不支持设置共享元素在画面之间的 Transition 动画。
15. 图标动画的要求?将 SVG 转成 AnimatedVectorDrawable 的文件即可,内部通过属性动画的标签实现诸如 Path、位移、透明度等动画效果。

推荐阅读

之前针对 Android 12 的 SplashScreen API 和 Jetpack SplashScreen 库分别写过更为详细的解读文章,感兴趣的朋友可补充食用~

同时在 GDG 社区说 活动上也分享过本文的话题,可以到 B站 上观看视频回放:

「社区说」《使用 Jetpack SplashScreen 打造全新的应用启动效果》