Android 12的启动白屏简单适配方案

TL;DR

  1. 导入implementation 'androidx.core:core-splashscreen:1.0.1'

  2. 继承主题 Theme.ScreenSplash,继承的主题可以设置前景和背景以及时间(最大500),类似icon

  3. 使用主题

在国产大厂的APP上,每一个Feature,每一个模块,每一个按钮,甚至每一个不可交互的地方,它都有出生的意义:广告位。

闪屏页的战略地位不言而喻😄

在 Android 12(SDK 31)之前,应用的启动画面(Splash Screen)一直是个令人头疼的问题。开发者们不得不绞尽脑汁,通过自定义 Activity 主题、设置 windowBackground 或者创建一个独立的 Splash Activity 来模拟启动效果。

不仅需要作为天然的广告位,这个一闪而过页面承载太多东西了。你的所有需要注册的内容,所有内容的完整性检查,服务器判断,DNS解析。当然还有广告的加载以及用户的手有没有抖动...

虽然目前看来体验不尽人意,适配寥寥无几,不过当初Android12似乎真的想改变这些(当然不止这些,更多的可能是体验统一的问题),所以带来一个原生,更高效的解决方案。抛开这些开发决定不了的内容,下面我简单记录一下自己适配这个SplashScreen,当然这也是我第一次使用这个特性。

核心概念与 Theme.SplashScreen 属性详解

Android 12 的启动画面不再是应用自身绘制的 View,而是由系统根据主题配置生成并管理。你的启动 Activity 会首先应用一个继承自 Theme.SplashScreen 的主题,系统会根据该主题的属性来渲染启动画面,并在应用准备就绪后平滑过渡到应用真正的界面。下面是我在网上找到的VerseAPP的动画闪屏页(你也可以下载查看teelgram的动画效果):

以下是 Theme.SplashScreen 主题及其关键属性的详细解析:

1. 父主题:Theme.SplashScreen

  • 作用: 这是 Android 12+ 系统提供的启动画面基础主题。你的自定义启动主题必须继承这个主题,才能享受到系统级别的启动画面管理和动画效果。

2. 背景属性:windowSplashScreenBackground

类型: Drawable 引用(@drawable/@color/

作用: 定义启动画面的背景。这是替换传统"白屏"的关键。

自定义方案:

  • 纯色: 最简单也是最高效的方式,直接引用一个颜色资源,如 <item name="windowSplashScreenBackground">@color/your_brand_color</item>

  • 渐变色: 创建一个 shape Drawable,并在其中定义 gradient 标签,实现平滑的颜色过渡。

  • 图片作为背景(慎用,推荐 LayerList): 虽然可以直接引用一张图片 (@drawable/your_image),但通常不推荐直接将一张大图作为背景,因为它可能导致屏幕适配问题、文件尺寸增大和内存开销。

  • LayerList 组合背景

3. 图标属性:windowSplashScreenAnimatedIcon

  • 类型: Drawable 引用(@drawable/@mipmap/

  • 作用: 定义在启动画面中心显示的图标或动画。

  • 可选动画类型:

  • Animated Vector Drawable (AVD)

    • 格式: XML (.xml),定义了矢量图的动画。

      • 优点: 官方推荐,文件小巧,可缩放不失真,性能优秀,可以实现复杂的矢量动画。

      • 创建: 复杂动画通常需要手动编写 XML 或借助工具。

    • Animation Drawable:

    • 格式: XML (.xml),引用一系列帧图片。

      • 优点: 简单易懂,适合简单的帧动画。

      • 缺点: 每一帧都是一张图片,文件体积大,内存占用高,缩放可能失真。适用于帧数非常少且简单的动画。

    • 自适应图标 (Adaptive Icon):

    • 格式: 通常是矢量图,由前景层 (@mipmap/ic_launcher_foreground) 和背景层 (@mipmap/ic_launcher_background) 组成。

      • 优点: 系统原生支持,自动适应不同形状的图标蒙版,无需额外动画即可平滑缩放。

      • 最常用方案: 如果没有自定义动画需求,直接使用你的自适应图标前景层即可,例如:<item name="windowSplashScreenAnimatedIcon">@mipmap/ic_launcher_foreground</item>

4. 动画持续时间:windowSplashScreenAnimationDuration

  • 类型: 整数(毫秒)

  • 作用: 定义 windowSplashScreenAnimatedIcon 中图标动画的持续时间。系统会在这个时间结束后开始淡出启动画面。

  • 注意: 你的动画设计应尽量在这个持续时间内完成,以避免动画被截断或过早结束。建议值为 200ms 到 1000ms 之间,保持快速且流畅。

5. 品牌 Logo:windowSplashScreenBrandDrawable (可选)

  • 类型: Drawable 引用

  • 作用: 在启动画面底部显示一个可选的品牌 Logo。

  • 位置: 这个 Logo 会固定显示在启动画面的底部,不会随图标一起动画。

  • 用处: 适合展示公司或产品的额外品牌标识。

6. 核心属性:postSplashScreenTheme

  • 类型: Style 引用(@style/

  • 作用: **这是最重要的属性!**它指定了启动画面结束后,Activity 应该切换到哪个主题来渲染你的应用界面。

  • 重要性: 如果没有正确设置这个属性,你的 Activity 在启动画面消失后,可能会显示错误的样式,甚至出现界面空白或闪烁。务必将其指向你应用正常运行时所使用的主要主题。


二、应用开发实践:从零开始配置

让我们通过一个完整的示例,一步步将 Android 12 的启动画面集成到你的应用中。

1: 确保你的项目兼容 Android 12 (SDK 31+)

首先,在你的 build.gradle (Module: app) 文件中,确保 compileSdktargetSdk 至少是 31:


    android {
        compileSdk 31 // 或更高版本
        defaultConfig {
            targetSdk 31 // 或更高版本
            // ...
        }
        // ...
    }

2. 添加 Splash Screen 库依赖


    dependencies {
        // ... 其他依赖
        implementation 'androidx.core:core-splashscreen:1.0.1' 
    }

3. 定义你的应用主题

res/values/themes.xml (和 res/values-night/themes.xml,用于深色模式) 中定义应用主主题。这将是 postSplashScreenTheme 指向的主题。


    <resources>
        <style name="Theme.MyAwesomeApp" parent="Theme.MaterialComponents.DayNight.NoActionBar">
            <item name="colorPrimary">#6200EE</item>
            <item name="colorPrimaryVariant">#3700B3</item>
            <item name="colorOnPrimary">#FFFFFF</item>
            <item name="colorSecondary">#03DAC6</item>
            <item name="colorSecondaryVariant">#018786</item>
            <item name="colorOnSecondary">#000000</item>
            <item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
            <item name="android:navigationBarColor">@color/black</item>
            <item name="android:windowBackground">@color/white</item>
            </style>
    </resources>

    <resources>
        <style name="Theme.MyAwesomeApp" parent="Theme.MaterialComponents.DayNight.NoActionBar">
            <item name="colorPrimary">#BB86FC</item>
            <item name="colorPrimaryVariant">#3700B3</item>
            <item name="colorOnPrimary">#000000</item>
            <item name="colorSecondary">#03DAC6</item>
            <item name="colorSecondaryVariant">#03DAC6</item>
            <item name="colorOnSecondary">#000000</item>
            <item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
            <item name="android:navigationBarColor">@color/black</item>
            <item name="android:windowBackground">@color/dark_gray</item>
        </style>
    </resources>

4. 准备启动画面背景(渐变色和Logo)

我们创建一个 LayerList 来实现一个包含渐变背景和底部品牌Logo的启动画面。(实际上不推荐 用渐变色,我更推荐telegram或者X的方案,纯色背景+AVD/静态LOGO)


    <?xml version="1.0" encoding="utf-8"?>
    <layer-list xmlns:android="[http://schemas.android.com/apk/res/android](http://schemas.android.com/apk/res/android)">
        <item>
            <shape android:shape="rectangle">
                <gradient
                    android:angle="270"
                    android:startColor="#6200EE"
                    android:endColor="#03DAC6"
                    android:type="linear" />
            </shape>
        </item>

        <item android:bottom="32dp"> <bitmap
                android:src="ic_my_company_logo"
                android:gravity="bottom'center_horizontal" />
        </item>
    </layer-list>

注意:

  • @drawable/ic_my_company_logo 应该是你的 Logo 图片(Vector Drawable 或 Bitmap)。

  • android:bottom 可以调整 Logo 的位置。

  • android:gravity="bottom'center_horizontal" 将 Logo 放置在底部中央。

5. 准备启动画面动画图标(AVD 示例)

假设你已经有一个名为 ic_animated_logo.xml 的 Animated Vector Drawable。


    <?xml version="1.0" encoding="utf-8"?>
    <animated-vector xmlns:android="[http://schemas.android.com/apk/res/android](http://schemas.android.com/apk/res/android)"
        android:drawable="@drawable/ic_static_logo"> <target android:name="group_name_in_static_logo"> <propertyValuesHolder
                android:propertyName="rotation"
                android:valueFrom="0"
                android:valueTo="360"
                android:valueType="floatType"
                android:duration="1000"
                android:interpolator="@android:interpolator/fast_out_slow_in" />
        </target>
    </animated-vector>

如果不想使用自定义动画,直接使用自适应图标前景部分:@mipmap/ic_launcher_foreground

6. 定义启动画面主题

现在,在 res/values/themes.xml (和 res/values-night/themes.xml) 中定义你的启动主题,并引用我们准备好的 Drawable。


    <resources>
        <style name="Theme.MyAwesomeApp.SplashScreen" parent="Theme.SplashScreen">
            <item name="windowSplashScreenBackground">@drawable/splash_layer_bg</item>

            <item name="windowSplashScreenAnimatedIcon">@drawable/ic_animated_logo</item>
            <item name="windowSplashScreenAnimationDuration">1000</item> <item name="postSplashScreenTheme">@style/Theme.MyAwesomeApp</item>
        </style>
    </resources>

    <resources>
        <style name="Theme.MyAwesomeApp.SplashScreen" parent="Theme.SplashScreen">
            <item name="windowSplashScreenBackground">@color/dark_splash_bg_color</item>
            <item name="windowSplashScreenAnimatedIcon">@drawable/ic_animated_logo_dark</item>
            <item name="windowSplashScreenAnimationDuration">1000</item>
            <item name="postSplashScreenTheme">@style/Theme.MyAwesomeApp</item>
        </style>
    </resources>

7. 在 AndroidManifest.xml 中应用主题

AndroidManifest.xml 中,将 Theme.MyAwesomeApp.SplashScreen 主题应用到启动 Activity(通常是 MainActivity,也有可能是SplashActivity之类的)。


    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="[http://schemas.android.com/apk/res/android](http://schemas.android.com/apk/res/android)"
        xmlns:tools="[http://schemas.android.com/tools](http://schemas.android.com/tools)">

        <application
            android:allowBackup="true"
            android:icon="@mipmap/ic_launcher"
            android:label="@string/app_name"
            android:roundIcon="@mipmap/ic_launcher_round"
            android:supportsRtl="true"
            android:theme="@style/Theme.MyAwesomeApp"> <activity
                android:name=".MainActivity"
                android:exported="true"
                android:theme="@style/Theme.MyAwesomeApp.SplashScreen"> <intent-filter>
                    <action android:name="android.intent.action.MAIN" />
                    <category android:name="android.intent.category.LAUNCHER" />
                </intent-filter>
            </activity>

            <activity android:name=".OtherActivity"
                android:theme="@style/Theme.MyAwesomeApp"/>

        </application>
    </manifest>

8. 在 MainActivity 中安装启动画面

最后,在启动 Activity 的 onCreate() 方法中调用 installSplashScreen()这是启用系统启动画面 API 的核心步骤。


    // MainActivity.kt
    import android.os.Bundle
    import androidx.appcompat.app.AppCompatActivity
    import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
    import kotlinx.coroutines.Dispatchers
    import kotlinx.coroutines.delay
    import kotlinx.coroutines.withContext
    import androidx.lifecycle.lifecycleScope
    import kotlinx.coroutines.launch

    class MainActivity : AppCompatActivity() {

        private var isContentReady = false // 用于控制启动画面何时消失

        override fun onCreate(savedInstanceState: Bundle?) {
            // 1. 调用 installSplashScreen() 必须在 super.onCreate() 之前
            val splashScreen = installSplashScreen()

            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)

            // 2. 延迟启动画面消失,直到内容准备就绪
            splashScreen.setKeepOnScreenCondition {
                !isContentReady // 当 isContentReady 为 false 时,启动画面会一直显示
            }

            // 3. 模拟数据加载或应用初始化
            loadAppContent()
        }

        private fun loadAppContent() {
            lifecycleScope.launch(Dispatchers.IO) {
                // 模拟耗时的初始化操作,例如网络请求、数据库加载等
                delay(3000) // 模拟 3 秒的加载时间

                // 数据加载完成
                isContentReady = true

                withContext(Dispatchers.Main) {
                    // 在这里可以执行数据加载完成后的 UI 更新或跳转操作
                    // 例如:navigateToHome()
                }
            }
        }
    }

Java 示例(AI转换的代码,应该咩问题):


    // MainActivity.java
    import android.os.Bundle;
    import androidx.appcompat.app.AppCompatActivity;
    import androidx.core.splashscreen.SplashScreen;
    import android.os.Handler;
    import android.os.Looper;
    import java.util.concurrent.Executors;
    import java.util.concurrent.ScheduledExecutorService;
    import java.util.concurrent.TimeUnit;

    public class MainActivity extends AppCompatActivity {

        private boolean isContentReady = false; // 用于控制启动画面何时消失

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            // 1. 调用 installSplashScreen() 必须在 super.onCreate() 之前
            SplashScreen splashScreen = SplashScreen.Companion.installSplashScreen(this);

            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);

            // 2. 延迟启动画面消失,直到内容准备就绪
            splashScreen.setKeepOnScreenCondition(() -> !isContentReady);

            // 3. 模拟数据加载或应用初始化
            loadAppContent();
        }

        private void loadAppContent() {
            ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
            executor.schedule(() -> {
                // 模拟耗时的初始化操作
                isContentReady = true; // 数据加载完成

                new Handler(Looper.getMainLooper()).post(() -> {
                    // 在主线程执行数据加载完成后的 UI 更新或跳转操作
                });
            }, 3, TimeUnit.SECONDS); // 模拟 3 秒的加载时间
        }
    }