スプラッシュ画面

Android 12 には SplashScreen API が追加されました。これにより、Android 12 以降を搭載したデバイスでの実行時に、すべてのアプリに対して新しいアプリ起動アニメーションが有効になります。これには、アプリ開始を示す動画、アプリアイコンを表示するスプラッシュ画面、アプリ本体への遷移が含まれます。

スプラッシュ画面の例
図 1: スプラッシュ画面の例

新しいエクスペリエンスでは、アプリを起動するたびに標準のデザイン要素が提供されますが、カスタマイズもできるため、アプリ独自のブランディングを維持できます。

SplashScreen API を直接使用するだけでなく、SplashScreen API をラップする SplashScreen 互換性ライブラリを使用することもできます。

スプラッシュ画面の仕組み

アプリのプロセスが実行されていないとき(コールド スタート)またはアクティビティが作成されていないとき(ウォーム スタート)にユーザーがアプリを起動すると、次のイベントが発生します(ホットスタート時にスプラッシュ画面が表示されることはありません)。

  1. 定義したテーマとアニメーションを使用してスプラッシュ画面が表示されます。

  2. アプリの準備が整うと、スプラッシュ画面が閉じてアプリが表示されます。

アニメーションの要素とメカニズム

アニメーションの要素は、Android マニフェストの XML リソース ファイルで定義します。ライトモードとダークモードのバージョンがあります。

ウィンドウ背景、アニメーション アプリアイコン、アイコン背景で構成されます。

スプラッシュ画面の要素
図 2: スプラッシュ画面のカスタマイズ可能な要素

これらの要素については、次の点を考慮してください。

  • アプリアイコン(1)はベクター型ドローアブルにする必要があり、静止画でもアニメーションでもかまいません。アニメーションの継続時間に制限はありませんが、1,000 ミリ秒以下にすることをおすすめします。デフォルトでは、ランチャー アイコンが使用されます。

  • アイコン背景(2)は省略可能です。アイコンとウィンドウ背景のコントラストを高める場合に便利です。アダプティブ アイコンを使用する場合、ウィンドウ背景とのコントラストが十分あれば、背景が表示されます。

  • アダプティブ アイコンと同様に、フォアグラウンドの 3 分の 1 がマスクされています(3)。

  • ウィンドウ背景(4)は、不透明な単色で構成されています。ウィンドウ背景が設定され、無地の色である場合、属性が設定されていなければ、デフォルトで使用されます。

スプラッシュ画面のアニメーションは、「開始」と「終了」のアニメーションで構成されています。

  • 開始アニメーションは、システムビューからスプラッシュ画面までです。これはシステムによって制御され、カスタマイズできません。

  • 終了アニメーションは、スプラッシュ画面を非表示にするアニメーションです。カスタマイズする場合は、SplashScreenView とそのアイコンにアクセスし、変換、不透明度、色を設定して任意のアニメーションを実行できます。その場合、アニメーションが終了したらスプラッシュ画面を手動で削除する必要があります。

アプリのスプラッシュ画面をカスタマイズする

SplashScreen はデフォルトで、テーマの windowBackground(単色の場合)と、ランチャー アイコンを使用します。スプラッシュ画面のカスタマイズは、アプリテーマに属性を追加して行います。

アプリのスプラッシュ画面は、次のいずれかの方法でカスタマイズできます。

  • テーマ属性を設定して外観を変更する

  • 長時間表示する

  • スプラッシュ画面を閉じるためのアニメーションをカスタマイズする

スプラッシュ画面のテーマを設定して外観を変更する

アプリのスプラッシュ画面をカスタマイズするには、アクティビティのテーマに以下の属性を指定します。android:windowBackground のような属性を使用する以前のスプラッシュ画面実装がある場合は、Android 12 以降用の代替リソース ファイルを提供することをご検討ください。

  1. 背景を特定の単色で塗りつぶすには、windowSplashScreenBackground を使用します。

    <item name="android:windowSplashScreenBackground">@color/...</item>
    
  2. 開始ウィンドウの中央にあるアイコンを置き換えるには、windowSplashScreenAnimatedIcon を使用します。AnimationDrawableAnimatedVectorDrawable でオブジェクトのアニメーション化と描画が可能な場合、開始ウィンドウを表示しつつアニメーションを再生するには、windowSplashScreenAnimationDuration も設定する必要があります。

    <item name="android:windowSplashScreenAnimatedIcon">@drawable/...</item>
    
  3. スプラッシュ画面のアイコン アニメーションの継続時間を指定するには、windowSplashScreenAnimationDuration を使用します。この設定はスプラッシュ画面の実際の表示時間には影響しませんが、スプラッシュ画面の終了アニメーションをカスタマイズするときに SplashScreenView#getIconAnimationDuration を使用して取得できます。 詳しくは、次のセクションのスプラッシュ画面を長時間表示するをご覧ください。

    <item name="android:windowSplashScreenAnimationDuration">1000</item>
    
  4. スプラッシュ画面のアイコンの背景を設定するには、windowSplashScreenIconBackgroundColor を使用します。これは、ウィンドウの背景とアイコンのコントラストが十分でない場合に便利です。

    <item name="android:windowSplashScreenIconBackgroundColor">@color/...</item>
    
  5. 必要に応じて windowSplashScreenBrandingImage を使用すると、スプラッシュ画面の下部に表示する画像を設定できます。デザイン ガイドラインでは、ブランディング画像の使用は推奨されていません。

    <item name="android:windowSplashScreenBrandingImage">@drawable/...</item>
    

スプラッシュ画面を長時間表示する

スプラッシュ画面は、アプリが最初のフレームを描画するとすぐに閉じます。アプリ内のテーマ設定など、少量のデータをローカル ディスクから非同期で読み込む必要がある場合は、ViewTreeObserver.OnPreDrawListener を使用して、最初のフレームを描画しないようアプリを一時停止します。

開始アクティビティが描画前に終了する場合(たとえば、コンテンツ ビューを設定せず、onResume の前に終了する場合など)は、描画前リスナーは必要ありません。

Kotlin

// Create a new event for the activity.
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    // Set the layout for the content view.
    setContentView(R.layout.main_activity)

    // Set up an OnPreDrawListener to the root view.
    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.
                return if (viewModel.isReady) {
                    // The content is ready; start drawing.
                    content.viewTreeObserver.removeOnPreDrawListener(this)
                    true
                } else {
                    // The content is not ready; suspend.
                    false
                }
            }
        }
    )
}

Java

// Create a new event for the activity.
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // Set the layout for the content view.
    setContentView(R.layout.main_activity);

    // Set up an OnPreDrawListener to the root view.
    final View content = findViewById(android.R.id.content);
    content.getViewTreeObserver().addOnPreDrawListener(
            new ViewTreeObserver.OnPreDrawListener() {
                @Override
                public boolean onPreDraw() {
                    // Check if the initial data is ready.
                    if (mViewModel.isReady()) {
                        // The content is ready; start drawing.
                        content.getViewTreeObserver().removeOnPreDrawListener(this);
                        return true;
                    } else {
                        // The content is not ready; suspend.
                        return false;
                    }
                }
            });
}

スプラッシュ画面を閉じるためのアニメーションをカスタマイズする

Activity.getSplashScreen() を使用すると、スプラッシュ画面のアニメーションをさらにカスタマイズできます。

Kotlin

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    // ...

    // Add a callback that's called when the splash screen is animating to
    // the app content.
    splashScreen.setOnExitAnimationListener { splashScreenView ->
        // Create your custom animation.
        val slideUp = ObjectAnimator.ofFloat(
            splashScreenView,
            View.TRANSLATION_Y,
            0f,
            -splashScreenView.height.toFloat()
        )
        slideUp.interpolator = AnticipateInterpolator()
        slideUp.duration = 200L

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

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

Java

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // ...

    // Add a callback that's called when the splash screen is animating to
    // the app content.
    getSplashScreen().setOnExitAnimationListener(splashScreenView -> {
        final ObjectAnimator slideUp = ObjectAnimator.ofFloat(
                splashScreenView,
                View.TRANSLATION_Y,
                0f,
                -splashScreenView.getHeight()
        );
        slideUp.setInterpolator(new AnticipateInterpolator());
        slideUp.setDuration(200L);

        // Call SplashScreenView.remove at the end of your custom animation.
        slideUp.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                splashScreenView.remove();
            }
        });

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

このコールバックの開始時、スプラッシュ画面のアニメーション化ベクター型ドローアブルは開始しています。アプリの起動にかかる時間によっては、ドローアブルがアニメーションの中ほどにある場合があります。SplashScreenView.getIconAnimationStart を使用して、アニメーションがいつ開始されたのかを把握します。アイコン アニメーションの残りの時間は次のように計算できます。

Kotlin

// Get the duration of the animated vector drawable.
val animationDuration = splashScreenView.iconAnimationDuration
// Get the start time of the animation.
val animationStart = splashScreenView.iconAnimationStart
// Calculate the remaining duration of the animation.
val remainingDuration = if (animationDuration != null && animationStart != null) {
    (animationDuration - Duration.between(animationStart, Instant.now()))
        .toMillis()
        .coerceAtLeast(0L)
} else {
    0L
}

Java

// Get the duration of the animated vector drawable.
Duration animationDuration = splashScreenView.getIconAnimationDuration();
// Get the start time of the animation.
Instant animationStart = splashScreenView.getIconAnimationStart();
// Calculate the remaining duration of the animation.
long remainingDuration;
if (animationDuration != null && animationStart != null) {
    remainingDuration = animationDuration.minus(
            Duration.between(animationStart, Instant.now())
    ).toMillis();
    remainingDuration = Math.max(remainingDuration, 0L);
} else {
    remainingDuration = 0L;
}