組み込みおよびカスタムの「戻る」予測アニメーションのサポートを追加する

すでにアプリを新しいシステムの「戻る」API に移行している場合は、予測型「戻る」にオプトインしてアプリ内アニメーションを自動的に受信し、カスタム遷移をサポートすることもできます。

組み込みのアプリ内アニメーションのサポートを追加する

動画: 予測型「戻る」アニメーション

オプトインすると、アプリは「ホームに戻る」、アクティビティ間、タスク間のアニメーションを表示します。

また、マテリアル コンポーネントの依存関係を MDC Android の v1.10.0 にアップグレードし、次のようなマテリアル コンポーネント アニメーションを受け取ることもできます。

詳しくは、GitHub のマテリアル コンポーネント デベロッパー ガイダンスをご覧ください。

この動画では、Android 設定アプリを使用して、アクティビティ間と「ホームに戻る」の予測型「戻る」アニメーションの簡単な例を紹介します。

  1. アニメーションでは、ユーザーは後方にスワイプして前の設定画面に戻ります(アクティビティ間アニメーションの例)。
  2. 前の画面で、ユーザーが 2 回目のスワイプを始めると、ホーム画面のプレビューが壁紙とともに表示されます(「ホームに戻る」アニメーションの例)。
  3. ユーザーが右にスワイプし続けると、ウィンドウがホーム画面のアイコンまで縮小するアニメーションが表示されます。
  4. これで、ユーザーは完全にホーム画面に戻ります。

詳しくは、予測型「戻る」のサポートの説明をご覧ください。

カスタムのアプリ内遷移とアニメーションを追加する

Progress API とカスタムのアクティビティ間アニメーション メソッド overrideActivityTransition を使用して、カスタムのアプリ内プロパティ アニメーションと遷移を作成できます。

Progress API を使用してカスタム遷移を追加する

AndroidX Activity 1.8.0-alpha01 以降では、Predictive Back Progress API を使用して、アプリ内の予測型「戻る」ジェスチャーのカスタム アニメーションを開発できます。OnBackPressedCallback 内に handleOnBackProgressed メソッド、handleOnBackCancelled メソッド、handleOnBackStarted メソッドを導入し、ユーザーが後方にスワイプしている間にオブジェクトをアニメーション化するようにしました。これらのメソッドは、新しいシステム アニメーションやマテリアル コンポーネント アニメーションで提供されるデフォルトのアニメーションよりも高度なカスタマイズが必要な場合に使用します。

ほとんどのアプリでは、下位互換性のある AndroidX API が使用されることが想定されますが、OnBackAnimationCallback インターフェース内には、Android 14 デベロッパー プレビュー 1 以降でテストできる同様のプラットフォーム API もあります。

AndroidX の遷移で Progress API を使用する

Android 14 以降では、AndroidX Transitions 1.5.0-alpha01 以降で Progress API を使用して、予測型「戻る」遷移を作成できます。

  1. ユーザーが後方にスワイプしたときに遷移を再生するには、beginDelayedTransition ではなく TransitionManager#controlDelayedTransition を使用します。
  2. handleOnBackStarted 内に遷移を作成します。
  3. currentFractionBackEvent.progress に関連付けることで、handleOnBackProgressed 内の「戻る」イベントで遷移を再生します。これにより、ユーザーが過去にスワイプした距離が開示されます。
  4. ユーザーが handleOnBackPressed で「戻る」操作を確定したら、遷移を終了します。
  5. 最後に、handleOnBackCancelled 内で遷移の状態をリセットします。

次の動画、Kotlin コード、XML は、OnBackPressedCallback で実装された 2 つのボックス間のカスタム遷移を示しています。

class MyFragment : Fragment() {

    val transitionSet = TransitionSet().apply {
        addTransition(Fade(Fade.MODE_OUT))
        addTransition(ChangeBounds())
        addTransition(Fade(Fade.MODE_IN))
    }
    ...
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        val callback = object : OnBackPressedCallback(enabled = false) {

            var controller: TransitionSeekController? = null

            @RequiresApi(34)
            override fun handleOnBackStarted(backEvent: BackEvent) {
                // Create the transition
                controller = TransitionManager.controlDelayedTransition(
                    binding.card,
                    transitionSet
                )
                changeTextVisibility(ShowText.SHORT)
            }

            @RequiresApi(34)
            override fun handleOnBackProgressed(backEvent: BackEvent) {
                // Play the transition as the user swipes back
                if (controller?.isReady == true) {
                    controller?.currentFraction = backEvent.progress
                }
            }

            override fun handleOnBackPressed() {
                // Finish playing the transition when the user commits back
                controller?.animateToEnd()
                this.isEnabled = false
            }

            @RequiresApi(34)
            override fun handleOnBackCancelled() {
                // If the user cancels the back gesture, reset the state
                transition(ShowText.LONG)
            }
        }

        binding.shortText.setOnClickListener {
            transition(ShowText.LONG)
            callback.isEnabled = true
        }

        this.requireActivity().onBackPressedDispatcher.addCallback(callback)
    }

    private fun transition(showText: ShowText) {
        TransitionManager.beginDelayedTransition(
            binding.card,
            transitionSet
        )
        changeTextVisibility(showText)
    }

    enum class ShowText { SHORT, LONG }
    private fun changeTextVisibility(showText: ShowText) {
        when (showText) {
            ShowText.SHORT -> {
                binding.shortText.isVisible = true
                binding.longText.isVisible = false
            }
            ShowText.LONG -> {
                binding.shortText.isVisible = false
                binding.longText.isVisible = true
            }
        }
    }
}
<?xml version="1.0" encoding="utf-8"?>
...
    <androidx.constraintlayout.widget.ConstraintLayout
        android:id="@+id/card"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        ...>

        <TextView
            android:id="@+id/short_text"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            ... />

        <TextView
            android:id="@+id/long_text"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:visibility="gone"
            .../>

    </androidx.constraintlayout.widget.ConstraintLayout>

予測型「戻る」遷移を使用する場合は、次の点に注意してください。

  • isSeekingSupported を使用して、遷移が予測型「戻る」をサポートしているかどうかを確認します。
  • カスタム遷移について true を返すように isSeekingSupported をオーバーライドします。
  • アニメーションごとに 1 つのコントローラを作成します。
  • 予測型「戻る」遷移は AndroidX 遷移でサポートされていますが、フレームワーク遷移ではサポートされていません。フレームワーク遷移から移行することをおすすめします。
  • 予測型「戻る」遷移は Android 14 以降のデバイスでサポートされており、下位互換性はありません。
  • XML シーンで作成された遷移もサポートされています。handleOnBackStarted で、TransitionSeekControllercontrolDelayedTransition の結果ではなく TransitionManager.createSeekController の結果に設定します。

Android 14 以降でカスタム アクティビティ遷移を追加する

Android 14 以降でカスタムのアクティビティ遷移で予測型「戻る」をサポートするには、overridePendingTransition ではなく overrideActivityTransition を使用します。つまり、ユーザーが後方にスワイプすると、遷移アニメーションが再生されます。

この仕組みを示す例として、アクティビティ B がバックスタック内のアクティビティ A の上に重ねられている場合を考えます。カスタム アクティビティ アニメーションは次のように処理します。

  • アクティビティ B の onCreate メソッド内で、開始遷移または終了遷移を呼び出します。
  • ユーザーがアクティビティ B に移動する場合は、OVERRIDE_TRANSITION_OPEN を使用します。ユーザーがスワイプしてアクティビティ A に戻る場合は、OVERRIDE_TRANSITION_CLOSE を使用します。
  • OVERRIDE_TRANSITION_CLOSE を指定した場合、enterAnim はアクティビティ A の開始アニメーション、exitAnim はアクティビティ B の終了アニメーションです。