Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Shared transitions #1550

Open
wants to merge 75 commits into
base: main
Choose a base branch
from
Open

Shared transitions #1550

wants to merge 75 commits into from

Conversation

stagg
Copy link
Collaborator

@stagg stagg commented Aug 2, 2024

Shared Element API

  • Creating two base elements for using shared transitions SharedElementTransitionLayout and SharedElementTransitionScope
  • This is done to be able to indirectly provide a SharedTransitionScope to child elements (ie Screen) without having to pipe the scope all the way down into Circuit Ui's
  • Circuit foundation is updated to provide a Navigation scoped AnimatedVisibilityScope via a new AnimatedNavDecoration variant of NavDecoration
  • Circuit overlays were also updated to provide an Overlay AnimatedVisibilityScope

SharedElementTransitionScope

  • Provides a SharedTransitionScope for the standard shared elements/shared bounds animations
  • The SharedElementTransitionScope instance can then be access via the composable SharedElementTransitionScope
  • The SharedElementTransitionScope composable should be used like SharedTransitionScope to access the sharedElement and sharedBound modifiers
  • Based on where/how the shared element will be shared getAnimatedScope or requireAnimatedScope can be used to access a needed AnimatedVisibilityScope
  • AnimatedVisibilityScopes can be provided with the ProvideAnimatedTransitionScope composable
  • By default Circuit provides Navigation and Overlay scopes when a SharedElementTransitionScope is available

SharedElementTransitionLayout

  • SharedElementTransitionLayout is a layout that creates and provides a SharedElementTransitionScope
  • Normally SharedElementTransitionLayout should be setup around the a standard circuit setup, between CircuitCompositionLocals and before NavigableCircuitContent or ContentWithOverlays
  • A PreviewSharedElementTransitionLayout is also provided to help with @Preview use

Update star to use SharedElementTransitions

  • Wired the shared elements into Star as a demonstration for the Navigation and Overlay use cases
  • Gesture predictive back (for Android) has been updated to use a seekable transition to drive shared element transitions
Scenario Demo
No shared elements
none.mp4
Navigation Scope
navigation.mp4
Overlay Scope
overlay.mp4
Seekable Predictive back
predictive-back.mp4

@stagg stagg changed the base branch from 0.24.x to compose-1.7.x August 2, 2024 18:41
@ZacSweers
Copy link
Collaborator

Some high level early thoughts

  • Can we add a control to disable the default nav decoration? This way sharedBounds() could be used without colliding with it. I'm almost wondering if we should disable the nav decoration by default if a shared element is present
  • Related from the above, can we leverage sharedBounds() in one of the samples?
  • Overlay API definitely seems like something we should get figured out with this

@chrisbanes
Copy link
Contributor

Do you have any sense of how much work is involved? I'd like to see a bit more progress before landing this personally, as I think there's going to be tests which are broken.

If only just getting the sample looking right to prove it out fully.

layoutDirection: LayoutDirection,
density: Density,
): Path {
path.reset()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use rewind() it's cheaper for the next outline

*/
@InternalCircuitApi
@OptIn(ExperimentalSharedTransitionApi::class)
public data object NoOpSharedTransitionScope : SharedTransitionScope {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Curious on the need for this, vs just not adding the modifiers / layout when not required. Hard to really understand all the details of Circuit but maybe you've explored that option already?

Copy link
Collaborator Author

@stagg stagg Aug 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was initially experimenting with this from the view that a Screen can be reused regardless of the context. Thinking on it some more its not really needed, a Screen really shouldn't be getting reused as such.

name = state.name,
photoUrlMemoryCacheKey = state.photoUrlMemoryCacheKey,
modifier =
Modifier.sharedElementWithCallerManagedVisibility(
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there no way to expose an AnimatedVisibilityScope from Navigation framework here, so that you could use the standard shared element modifiers?

I think from a usability perspective for developers, this would be easier to work with than needing to use sharedElementWithCallerManagedVisibility.

I also think you'd need to use it to pass through the correct Transition object, if looking at supporting predictive back correctly.

Take a look at how its used here in Jetsnack for animating rounded corners as the animation progresses - https://github.com/android/compose-samples/pull/1314/files#diff-cd670f26d99bb64790bdbee3a8965fca378714cb988996912f2e0e137931b72dR156-R163
For a simple example of Shared elements with SeekableTransitionState, no NavHost usage, we have this snippet that might be helpful to tie your transitions into - android/snippets#273

You may want to consider using context receivers in Kotlin if your library would use an experimental feature (Kotlin/KEEP#259), you could then expose both the AnimatedVisibilityScope and SharedTransitionScope to one function without needing to pass it around.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This case is a bit different as it's dealing with an element being shared across navigation and again as an Overlay is being shown. Need to really work on the Overlay API for this a bit more, currently missing a AnimatedVisibilityScope from it 😅 Think the goal would be to dynamically switch to the correct AnimatedVisibilityScope based on whats happening.

Thanks for the SeekableTransitionState snippet 🙇🏻 The predictive back implementation/api is definitely going to need to provide some of the progress information.

* [SharedElementTransitionScope].
*/
@Composable
private fun staticAnimatedVisibilityScope(): AnimatedVisibilityScope {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels like it could potentially introduce some UI inconsistencies, is it possible that a developer somehow relies on the scope to perform a different UI animations unrelated to shared elements? Maybe it'd be less error-prone to rather have a nullable scope instead.

Or have the Nav framework itself always expose the AnimatedVisibilityScope and throw an exception if its not there as something would've gone quite wrong to have that happen?

Copy link

@doris4lt doris4lt Aug 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I second what Rebecca said. This custom scope will always have the target visibility be EnterExitState.Visible. That would potentially prevent the shared elements from identifying the target bounds. More specifically when both sharedElement/sharedBounds call sites of the same key report their target visibility to be visible, we would not know which bounds to use as the target bounds since they are both "entering".

Also, consumers of this API may be confused as to why their Modiifer.animateEnterExit doesn't work. A nullable scope in this case would be more explicit.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup, it's pretty broken always being in the single state right now. Going to lean on the nav for providing this.

@riggaroo
Copy link

riggaroo commented Aug 5, 2024

Love the visuals to aid the PR description! Thanks for showcasing them 🎉

The animations are looking great too - love the back from full screen to the pager.

Could the Active Scope (layout.mp4) example at 0:07 be improved to have the image shared to that screen too? It seems like there is a blank screen at that point. Maybe its an image loading issue, perhaps it could be corrected by setting a placeholder cache key to the same as the memory cache key? .placeholderMemoryCacheKey("image-key") and having the placeholder be null, similar to this example https://developer.android.com/develop/ui/compose/animation/shared-elements/common-use-cases#async-images

From details -> list of dogs, the final clipping is applied immediately to the dog, consider animating the rounded corners too as it progresses between the states, the bottom corners can animation from rounded to straight - something similar was done here - https://youtu.be/PR6rz1QUkAM?si=DsGg4huAxgbel9c_&t=989

I do agree about getting predictive back working correctly too - as this may heavily influence your API design more, as developers would probably want to tie into the Transition object to do other surrounding animations, as well as it can help with debugging to be able to seek between the states.

@stagg
Copy link
Collaborator Author

stagg commented Aug 13, 2024

  • Can we add a control to disable the default nav decoration? This way sharedBounds() could be used without colliding with it. I'm almost wondering if we should disable the nav decoration by default if a shared element is present

We're going to need some form of AnimatedVisibilityScope from the nav decoration to run the transitions, so would this set the animation to something predefined?

Do you have any sense of how much work is involved? I'd like to see a bit more progress before landing this personally, as I think there's going to be tests which are broken.

Think most of the work is in supporting seekable transitions for predictive back, then maybe building some simpler api's on top of that?

@chrisbanes
Copy link
Contributor

Think most of the work is in supporting seekable transitions for predictive back, then maybe building some simpler api's on top of that?

Sounds good to me! I just don't see any tests being ran on CI?

@stagg
Copy link
Collaborator Author

stagg commented Aug 14, 2024

Made some progress with the overlay transitions, bit more to tidy up with it and then on to predictive back support. Think that will involve some NavDecoration rework if we want to control the animation scope setup better.

transitions.mp4

@ZacSweers
Copy link
Collaborator

Looking better. Def game to make whatever changes we need to nav decoration APIs (and overlays)

@stagg stagg deleted the branch main October 18, 2024 00:51
@stagg stagg closed this Oct 18, 2024
@stagg stagg reopened this Oct 18, 2024
@stagg stagg changed the base branch from compose-1.7.x to main October 18, 2024 00:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants