Android12 SplashScreenAPIを触ってみた


Android12のアップデートがそろそろとなっている中、今更ながらにAndroid12(targetSDKVersion31)から利用できるSplashScreenAPIを触ってみました。

SplashScreenとは?

アプリが実行されていない時(コールドスタート)にSplash画面が表示される。
デフォルトではアダプティブアイコンの設定が引き回されるので、何も設定しなくてもアイコンがちゃんと設定されていれば表示はされる。
背景色に関してはandroid:windowBackgroundの属性を設定していればそちらの色設定で表示される。
別Activityを作成してSplashを作成していなければ、特に問題にはならなさそうです。

どんなカスタマイズができるか?

targetSDKVersionを31にすると下記の要素が設定できるようになります。

xmlのテーマで設定できる項目

1.アイコンの変更
2.アイコンの背景変更
3.スクリーンの背景変更
4.ブランディングアイコンの追加
5.スクリーンの表示時間調整

v31/themes.xml
<!-- 1.アイコンの変更 アダプティブアイコンの使用と同等なのでサイズに注意 -->
<item name="android:windowSplashScreenAnimatedIcon">@drawable/obake</item>
<!-- 2.アイコンの範囲の色設定 -->
<item name="android:windowSplashScreenIconBackgroundColor">@color/black</item>
<!-- 3.スクリーンの背景色-->
<item name="android:windowSplashScreenBackground">#ffff00</item>
<!-- 4.ブランディングアイコン -->
<item name="android:windowSplashScreenBrandingImage">@drawable/branding_icon</item>
<!-- 5.表示時間 -->
<item name="android:windowSplashScreenAnimationDuration">1000</item>

ブランディングアイコンについては公式のURLで非推奨との記載があるので、使わない方がいいかもしれません。
表示時間に関しては最大1000msまで。それ以上を設定したとしても、意味なしです。

プログラム上で設定できる項目

1.splash画面の表示時間調整
2.splash画面のアニメーション設定

1.splash画面の表示時間調整

起動時のActivityで任意のタイミングまで処理を止める形での対応です。onPreDrawListenerを設定して、消すタイミングまでListenerを設定しておく感じみたいです。ちょっと、無理矢理な感じは否めないのですが。。。
今回サンプルとしてわかりやすく、Activity内でフラグを持たせてますが、実際に適応する場合はviewModelにフラグを持たせておいて、終わったタイミングでフラグを更新する方が良さそうです。実際のGoogle側のサンプルコードもそうなっています。

起動するActivityのファイル
class MainActivity : AppCompatActivity() {
    private var isReady = false

    val content: View = findViewById(android.R.id.content)
    content.viewTreeObserver.addOnPreDrawListener(
        object : ViewTreeObserver.OnPreDrawListener {
            override fun onPreDraw(): Boolean {
                // Check if the initial data is ready.
                Log.d("TEST", " waiting splash !! ")
                return if (isReady) {
                    Log.d("TEST", " show splash !! ")
                    content.viewTreeObserver.removeOnPreDrawListener(this)
                    true
                } else {
                    waitSplash()
                    false
                }
            }
        }
    )

    private fun waitSplash() {
        lifecycleScope.launch(Dispatchers.Default) {
            sleep(5000)
            isReady = true
        }
    }
}

2.splash画面のアニメーション設定

splashScreenのviewを取得出来るようになっているので、onCreateなどで取得してアニメーションを追加します。

起動するActivityのファイル
class MainActivity : AppCompatActivity() {
    private var isReady = false

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        if (Build.VERSION.SDK_INT >= 31) {
            splashScreen.setOnExitAnimationListener { splashScreenView ->
                val turn = turnAnim(splashScreenView)
                val slideUp = slideUpAnim(splashScreenView)
                val animation = AnimatorSet()

                animation.playSequentially(turn,slideUp)

                animation.doOnEnd { splashScreenView.remove() }

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

    private fun slideUpAnim(view: View): ObjectAnimator {
       val animationObj =  ObjectAnimator.ofFloat(
            view,
            View.TRANSLATION_Y,
            0f,
            -view.height.toFloat()
        )
        animationObj.interpolator = AnticipateInterpolator()
        animationObj.duration = 500L
        return animationObj
    }

    private fun turnAnim(view: View): ObjectAnimator {
        val animationObj =  ObjectAnimator.ofFloat(
            view,
            View.ROTATION_Y,
            360f
        )
        animationObj.interpolator = AccelerateDecelerateInterpolator()
        animationObj.duration = 1000L
        return animationObj
    }
}

まとめ

触ってみた感じではとりあえず、別ActivityでSplash画面を作っていない限りでは大きくプログラムを改修することなく適応できそうな感じではありました。
起動時に重い通信をするようなものであれば、プログラム側で待たせるようにする形にできれば良さそうな感じがします。
気になるのはブランディングアイコンはどういった経緯で入ったかですね。非推奨とも記載あるので紆余曲折あってそうなってそうな感じ。。。
なくなりそうな気もするので、入れない方が無難なのかなと感じました。