FragmentでPredictive backをサポートする

Fragment 同士の遷移時アニメーションで Predictive back を有効にする時の実装メモです。
これを書いている時点では、alpha 版のライブラリや Experimental な機能を使用しているため、将来的に変更される可能性があります。

## 実装

### 依存関係

ライブラリの依存は以下のバージョン以降である必要があります。

- [Fragment](`https://developer.android.com/jetpack/androidx/releases/fragment`) 1.7.0 以降 or [Transition](https://developer.android.com/jetpack/androidx/releases/transition) 1.5.0 以降
- compileSdk 34
- [material-components](`https://github.com/material-components/material-components-android`) の Motion を使いたいならば 1.12.0 以降


### Predictive back の有効

AndroidManifest で Predictive back を有効にします。

```AndroidManifest.xml
<!-- Application 全体で有効 -->
<application
    ...
    android:enableOnBackInvokedCallback="true" />

<!-- or Activity 単位で有効 -->
<activity
    ...
    android:enableOnBackInvokedCallback="true" />
```

次に Fragment で Predictive back を有効にします。
この有効は Application の `onCreate` もしくは Activity の `super.onCreate` の前に設定する必要があります。 

```MainActivity.kt
class MainActivity : ComponentActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        FragmentManager.enablePredictiveBack(true)
        super.onCreate(savedInstanceState)
        ...
    }
}
```

### 遷移アニメーションの実装

Jetpack の Navigation ではデフォルトでは遷移時のアニメーションが入っておらず、Predictive back を有効にしただけでは遷移時のアニメーションや連携は機能しません。
また、Fragment のスタックを独自で管理している場合も Predictive back は機能しないので、Jetpack の Navigation か FragmentManager を使っている必要があります。

#### パターン 1 : Navigation Component で遷移時アニメーションを設定する

Navigation Component で遷移時のアニメーションを設定するパターンです。

```nav_graph.xml
<navigation ...>
    <fragment ...>
        <action
            ...
            app:enterAnim="@animator/nav_default_enter_anim"
            app:exitAnim="@animator/nav_default_exit_anim"
            app:popEnterAnim="@animator/nav_default_pop_enter_anim"
            app:popExitAnim="@animator/nav_default_pop_exit_anim" />
    </fragment> 
</navigation>            
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/126298/81c428fb-8a31-39c7-a0d1-f4d7695285f9.gif" width="300px" />


#### パターン ２ : Transition でアニメーションを設定する

Transition で標準で用意されている `AutoTransition` を設定して Fragment が表示するときのアニメーションを設定するパターンです。
`AutoTransition` で適用されるアニメーション自体は Fade になっています。

```kotlin
class FragmentA: Fragment() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        exitTransition = AutoTransition()
        reenterTransition = AutoTransition()
    }
}

class FragmentB: Fragment() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enterTransition = AutoTransition()
        exitTransition = AutoTransition()
    }
}
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/126298/4eb26a5a-1c67-55e2-805e-03e1b5e08b68.gif" width="300px" />

material-components の [Motion](https://github.com/material-components/material-components-android/blob/master/docs/theming/Motion.md) にある Transition 系の API を使っている場合、Predictive back がサポートされているライブラリバージョンを使っているならば追加実装なしで機能します。


<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/126298/2e3fd103-0b1b-d884-338b-be328bc12c7c.gif" width="300px" />


#### パターン 3 : Shared element transition を設定する

Transition を使用する Shared element transition もパターン 2 同様に機能します。
ただし、設定できる Transition に制限があり、Transition の実装クラスで `isSeekingSupported = true` であるものが Predictive back に対応しています。
また、Shared element trantision の Transition だけでなく、`enterTransition` のような通常の Transition も設定する必要があります。

```kotlin
// In FragmentA
binding.card.setOnClickListener {
    val extras = FragmentNavigatorExtras(
        it to "shared_element_container",
        binding.icon to "shared_element_icon"
    )
    findNavController().navigate(R.id.action_FragmentA_to_FragmentB, null, null, extras)
}

class FragmentB: Fragment() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enterTransition = AutoTransition()
        
        val transition = TransitionSet().apply {
            addTransition(ChangeBounds())
            addTransition(ChangeImageTransform())
        }
        sharedElementEnterTransition = transition
        sharedElementReturnTransition = transition
    }
}
```


<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/126298/96b7145c-219f-f94e-05b7-067ddb36b234.gif" width="300px" />
 
## 参考
- https://developer.android.com/guide/navigation/custom-back/predictive-back-gesture
- https://developer.android.com/guide/navigation/custom-back/support-animations
- https://github.com/android/platform-samples/tree/main/samples/user-interface/predictiveback