# Predictive Back Samples

Shows different types of predictive back animations for a Views based app, including:

+ Back-to-home
+ Cross-activity
+ Custom cross-activity
+ Cross-fragment animation
+ Shared element cross-fragment animation
+ Custom Progress API animation
+ Custom AndroidX Transition
+ Cross-fragment animation with MaterialSharedAxis
+ setCustomAnimations

For predictive back on Compose, see [Predictive back in Compose](https://developer.android.com/develop/ui/compose/system/predictive-back)
and [Predictive back Compose snippets](https://github.com/android/snippets/blob/030dddb8f6f319ffb1b3809fe71add6417531fe2/compose/snippets/src/main/java/com/example/compose/snippets/predictiveback/PredictiveBackSnippets.kt)

## Custom cross-activity

In general, rely on the default cross-activity animation; however, if required use
`overrideActivityTransition` instead of `overridePendingTransition`. Although animation resources are
expected for `overrideActivityTransition`, we strongly recommend to stop using animation and to
instead use animator and androidx transitions for most use cases. For more details see the
[developer documentation](https://developer.android.com/guide/navigation/custom-back/predictive-back-gesture).

```kotlin
    override fun onCreate(savedInstanceState: Bundle?) {
        "..."

        overrideActivityTransition(
            OVERRIDE_TRANSITION_OPEN,
            android.R.anim.fade_in,
            0
        )
    
        overrideActivityTransition(
            OVERRIDE_TRANSITION_CLOSE,
            0,
            android.R.anim.fade_out
        )
    }
```

## Cross-fragment animation

Example code uses navigation component default animations.

```xml
<action
    android:id="..."
    app:destination="..."
    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" />
```

## Shared element cross-fragment animation

Example code shows a simple cross-fragment animation between two [shared elements](https://developer.android.com/guide/fragments/animate#shared)
that automatically works with predictive back gesture navigation provided fragment and androidx
transitions dependencies are upgraded to
[1.7.0](https://developer.android.com/jetpack/androidx/releases/fragment#1.7.0-alpha08) and
[1.5.0](https://developer.android.com/jetpack/androidx/releases/transition#1.5.0-alpha06) respectively.

On the first fragment, where "second_card" is the `android:transitionName` of a view in the second
fragment:
```kotlin
binding.sharedElementCrossFragment.setOnClickListener {
   findNavController().navigate(
       R.id.show_PBSharedElementTransitionFragment,
       null,
       null,
       FragmentNavigatorExtras(it to "second_card")
   )
}
```

On the second fragment:
```kotlin
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    enterTransition = AutoTransition()
    sharedElementEnterTransition = AutoTransition()
}
```

Note that `enterTransition` must be specified in addition to `sharedElementEnterTransition`, which
will mark the shared element as seekable and therefore enable the predictive back gesture to play.

## Custom Progress API animation

The following example using the Progress API follows the
[Predictive Back Design Guidance](https://developer.android.com/design/ui/mobile/guides/patterns/predictive-back).

```kotlin
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    "..."

    val windowWidth = requireActivity().windowManager.currentWindowMetrics.bounds.width()
    val maxXShift = windowWidth / 20

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

        override fun handleOnBackProgressed(backEvent: BackEventCompat) {
            when (backEvent.swipeEdge) {
                BackEventCompat.EDGE_LEFT ->
                    binding.box.translationX = backEvent.progress * maxXShift
                BackEventCompat.EDGE_RIGHT ->
                    binding.box.translationX = -(backEvent.progress * maxXShift)
            }
            binding.box.scaleX = 1F - (0.1F * backEvent.progress)
            binding.box.scaleY = 1F - (0.1F * backEvent.progress)
        }

        override fun handleOnBackPressed() {
            // your back handling logic
        }

        override fun handleOnBackCancelled() {
            binding.box.scaleX = 1F
            binding.box.scaleY = 1F
            binding.box.translationX = 0F
        }
    }
    
    requireActivity().onBackPressedDispatcher.addCallback(
        this.viewLifecycleOwner,
        predictiveBackCallback
    )
}
```

## Custom AndroidX Transition
For more details see the
[developer documentation](https://developer.android.com/about/versions/14/features/predictive-back#androidx-transitions).

```kotlin
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

            override fun handleOnBackStarted(backEvent: BackEvent) {
                // Create the transition
                controller = TransitionManager.controlDelayedTransition(
                    // textContainer is a FrameLayout containing the shortText and longText TextViews
                    binding.textContainer,
                    transitionSet
                )
                changeTextVisibility(ShowText.SHORT)
            }

            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
            }

            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.textContainer,
            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
            }
        }
    }
}
```

## Cross-fragment animation with MaterialSharedAxis

MaterialSharedAxis is a Visibility transition. A Visibility transition is triggered when the target
Fragment's visibility is changed or when the Fragment is added or removed. This means MaterialSharedAxis
requires a View to be changing in visibility or to be added or removed to trigger its animation.

For more details see the
[developer documentation](https://m2.material.io/develop/android/theming/motion#shared-axis).

```kotlin
// FragmentA.kt

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

    exitTransition = MaterialSharedAxis(MaterialSharedAxis.Z, /* forward= */ true)
    reenterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, /* forward= */ false)
}

// FragmentB.kt

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

    enterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, /* forward= */ true)
    returnTransition = MaterialSharedAxis(MaterialSharedAxis.Z, /* forward= */ false)
}
```
## setCustomAnimations

Use `setEnterTransition`, `setExitTransition`, `setReenterTransition`, `setReturnTransition`,
`setSharedElementEnterTransition`, `setSharedElementReturnTransition` instead of `setCustomAnimations`
where possible. 

However, if you are using `setCustomAnimations`, here's a code sample showing
predictive back working with animators and fragment manager.

```kotlin
// PBSetCustomAnimationsActivity.kt

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    
    "..."
    supportFragmentManager.commit {
        replace(R.id.fragment_container, PBSetCustomAnimationsFirstFragment())
    }

}

// PBSetCustomAnimationsFirstFragment.kt

override fun onCreateView(
    inflater: LayoutInflater, container: ViewGroup?,
    savedInstanceState: Bundle?,
): View {
    _binding = FragmentSetCustomAnimationsBinding
        .inflate(inflater, container, false)

    binding.box.setOnClickListener {
        parentFragmentManager.commit {
            setCustomAnimations(
                android.R.animator.fade_in, // enter
                android.R.animator.fade_out, // exit
                android.R.animator.fade_in, // popEnter
                android.R.animator.fade_out) // popExit
            replace(R.id.fragment_container,PBSetCustomAnimationsSecondFragment())
            setReorderingAllowed(true)
            addToBackStack(null)
        }
    }
    return binding.root
}
```

