Android S で追加された SplashScreen API 触ってみた

Design Division 所属 Androidエンジニアの スージ です。 良いデザインでクライアントのビジネスを前進させるために開発している、Jetpack Compose と KMM好きな Android エンジニアです。

Google I/O では OS以外にも、いろいろなAPIが追加・更新されますよね。 今年の Android 12 発表後に Features and APIs Overview の内容を確認して気になったのは、新しく追加された SplashScreen API です。今回 SplashScreen API をすこし触ってみました!

SplashScreen API とは?

Android 12 から、OS が実行する新しい Splash 画面アニメーションが追加されました。

例)Gmailアプリの Android 12 起動:

上記のように、Splash 画面のコンテンツのカスタマイズのために、SDK 31 (Android S) から SplashScreen API が追加されました。

Android S 以降で、このAPIを使ってないアプリはどんな遷移になる?

今までのAndroid 開発で Splash画面のため特別なAPIはありませんでした。なので、自分でSplash 画面を実装してそれを Launcher Activity として使うようなパターンがあり、そのアプリをもし Android 12 で開くとどんなアニメーションになるのか、調べてみました。

見た通り、Android S から SplashScreen API 対応しなくても、OSはアプリの Launcher アイコンを使って自分でSplash画面を出しています。その後、アプリ内実装した Splash画面(正しくは、Launcher Activity)が表示されます。

このため、Android S からの Splash画面実装の方向性は二つです:

  • SplashScreen API 使わずに、OS の Splash画面に任せる
  • SplashScreen API 使ってSplash画面 をカスタマイズする

SplashScreen API について

  • SplashScreenAPI v1.0.0-alpha01 は SDK 23 (Marshmallow) まで backport 対応が入ってます
  • アプリ起動して Splash画面が表示されるまでのアニメーションはOSがコントロールするから変えられない。でも、Splash画面の削除アニメーションをカスタマイズできる
  • cold start の場合(キルしたやクラッシュしたアプリ起動する) Splash画面表示する
  • warm start の場合(戻るボタン押してからアプリ再起動する・ホームボタン押してから長い間開いてなかったアプリ起動する)もSplash画面表示する
  • hot start の場合(ホームボタン押して少し間でアプリ再起動する)表示しない

SplashScreen API 画面の構造

Screenshot 2021-07-16 at 8.45.24 AM.png (40.4 kB)

  1. windowSplashScreenBackground : スプラッシュ画面の背景色。1色
  2. windowSplashScreenAnimatedIcon : .svg アイコン画像
  3. windowSplashScreenIconBackgroundColor : アイコンの背景色。これは、背景とアイコンにコントラストがない場合に使えるかも
  4. windowSplashScreenBrandingImage : ガイドライン的に推奨してない

    Optionally, you can use windowSplashScreenBrandingImage to set an image to be shown at the bottom of the splash screen. The design guidelines recommend against using a branding image.

アニメーションをつけたアイコンも使える

SplashScreen は AnimatedVectorDrawable もサポートしてるので(Android S 以降)アニメーションをつけたアイコンも使えます。

注意 SplashScreen が表示される時間を延長することはできますが、デフォルトの時間は短いので、アニメーションを使いたいならアニメーションの実行期間も短くしたほうがいいです(1秒以内)。ただし、アニメーション実行中に SplashScreen が削除されてしまう可能性はあります

内部的な実装

SplashScreen API の内部的な実装は、新しいR.layout.splash_screen_viewを inflate して Launcher Activity のレイアウトに追加します。

private open class ViewImpl(val activity: Activity) {
    private val _splashScreenView: ViewGroup by lazy {
        FrameLayout.inflate(
            activity,
            R.layout.splash_screen_view,
            null
            ) as ViewGroup
    }

    init {
        val content = activity.findViewById<ViewGroup>(android.R.id.content)
        content.addView(_splashScreenView)
    }
    ..
    
}

実装

  • gradle
android {
    compileSdk 31
    ..
}

dependencies {
    
    implementation "androidx.core:core-splashscreen:1.0.0-alpha01"
    ..
}
  • Launcher Activity のテーマは Theme.SplashScreen を parentとして定義する
<!-- AndroidManifest.xml -->
    <activity
        android:name=".MainActivity"
        android:theme="@style/AppTheme.SplashScreen">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>

<!-- styles.xml -->
    <style name="AppTheme.SplashScreen" parent="Theme.SplashScreen">
        <!-- 背景色 -->
        <!-- @color/xx resource だから1色だけ設定できる -->
        <item name="windowSplashScreenBackground">@color/white</item>
        
        <!-- 真ん中のアイコン -->
        <!-- vector drawable や animated vector drawable でもオッケー! -->
        <item name="windowSplashScreenAnimatedIcon">@drawable/icon</item>
        
        <!-- 元 Launcher Activity のテーマ, Splash 非表示になった後のテーマ -->
        <item name="postSplashScreenTheme">@style/AppTheme.Parent</item>
        
        <!-- これ定義しない AnimatedVectorDrawable のアニメーション動けない -->
        <item name="windowSplashScreenAnimationDuration">1000</item>
        
        <!-- アイコンの背景色、もし入れたいなら -->
        <item name="windowSplashScreenIconBackgroundColor">@color/colorAccent</item>
        
        <!-- 画面下に表示する画像、guideline によってこれを作用することは推奨しない -->
        <item name="windowSplashScreenBrandingImage">@drawable/files</item>
    </style>
  • Launcher Activity のところで、setContentView や window flag など設定する前に installSplashScreen() を呼び出す。
class MainActivity : AppCompatActivity() {
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        installSplashScreen()

        setContentView(R.layout.activity_main)
    }
}

結果はこうなりました

animated_splash.gif (2.9 MB)

  • アニメーションをつけたアイコンの場合、 windowSplashScreenAnimationDuration の定義も必要です。 duration を設定しないとアニメーションは実行されない。
  • デフォルトで、 Launcher Activity のレイアウトの最初の frame を draw する瞬間に SplashScreen は削除されます。
  • Splash を実装する理由の1つに、Splash 画面のバックグラウンドでデータをロードするというものがあります。そのために、Splash 画面の削除を延長するには documention によると ViewTreeObserver を使用 することができるし、この API で wrapper listener の SplashScreen.KeepOnScreenConditionを使うこともできます。
    /**
     * Condition evaluated to check if the splash screen should remain on screen
     *
     * The splash screen will stay visible until the condition isn't met anymore.
     * The condition is evaluated before each request to draw the application, so it needs to be
     * fast to avoid blocking the UI.
     */
    public fun interface KeepOnScreenCondition {

        /**
         * Callback evaluated before every requests to draw the Activity. If it returns `true`, the
         * splash screen will be kept visible to hide the Activity below.
         *
         * This callback is evaluated in the main thread.
         */
        @MainThread
        public fun shouldKeepOnScreen(): Boolean
    }
class MainActivity : AppCompatActivity() {

    private val viewModel: MainViewModel
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val splashScreen = installSplashScreen()

        setContentView(R.layout.activity_main)

        splashScreen.setKeepVisibleCondition {
            return viewModel.isDataReady
        }
    }
}
  • SplashScreen のView のアクセスは OnExitAnimationListener内でしかできません。この listener の中で SplashScreen の exit animation を設定します。
    splashScreen.setOnExitAnimationListener { splashScreenView ->
        
        val slideUp = ObjectAnimator.ofFloat(
            splashScreenView,
            View.TRANSLATION_Y,
            0f,
            -splashScreenView.height.toFloat()
        )
        slideUp.interpolator = AccelerateDecelerateInterpolator()
        slideUp.duration = 200L

        // Call SplashScreenView.remove at the end of your custom animation.
        slideUp.doOnEnd { splashScreenView.remove() }

        // Run your animation.
        slideUp.start()
    }

最後に

この記事では、Lottie JSON を AnimatedVectorDrawable に変換するために、bodymovin-to-avd ライブラリを使わせていただきました。 記事作成現在で SplashScreen API はまだ バージョン alpha01 なので、これから仕様が変わることもあると思います。でも、やっと Splash画面のAPIが導入されたので個人的に嬉しいです。これからの開発パターンはどうなるのかを楽しみにしてます。


Goodpatch には、デザインと技術の両方を追求できる環境があります。 少しでもご興味を持ってくださった方、ぜひ一度カジュアルにお話ししましょう!