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 102 commits into
base: main
Choose a base branch
from
Open

Shared transitions #1550

wants to merge 102 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

This comment was marked as outdated.

@ZacSweers
Copy link
Collaborator

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

@stagg stagg requested a review from ZacSweers November 20, 2024 19:32
@ZacSweers
Copy link
Collaborator

ZacSweers commented Nov 20, 2024

Some early thoughts from going around the sample before looking through the code

  • Is there a reason it never works the first time in debug? It seems to only work the second+ time. On release builds it's fine

  • Anything we can do to improve the transition for the photo viewer?

starsampleone.webm
  • Do you think it would be possible to, in the photo viewer when it's dragged but not flung, do a shared element transition instead?

  • The transition from the home list to the detail view feels asymmetric. When going from A -> B, the background content translates away. When going from B -> A, some content translates but the bottom nav bar translates up

  • I notice when seeking through predictive back that the top of the stack exerts a significant shadow, which I think is causing some of the artifacting I'm seeing in the above bits. Anything we should tweak in NavigableCircuitContent or elsewhere to improve this? What's particularly weird is that it appears behind some content on the previous screen and separate from the top screen too.

starsample2.webm
  • The sample crashes when I go to detail and rotate 😬. Doesn't crash on just the home screen.

  • For the card, I wonder if we can animate its color quicker. It looks a little like it blows out/flashes.

  • If the photo carousel is scrolled in the detail screen, the shared element transition still runs but it blips back to the first image at the start.

starsample3.webm

@stagg
Copy link
Collaborator Author

stagg commented Nov 30, 2024

  • Is there a reason it never works the first time in debug? It seems to only work the second+ time. On release builds it's fine

Not sure I've run into this?

  • Anything we can do to improve the transition for the photo viewer?

Changed it a bit with 0d1b246

  • Do you think it would be possible to, in the photo viewer when it's dragged but not flung, do a shared element transition instead?

Nice idea, added with e972508

  • I notice when seeking through predictive back that the top of the stack exerts a significant shadow

Fixed with 2779634, the drawbacks of dark mode 😅

  • For the card, I wonder if we can animate its color quicker. It looks a little like it blows out/flashes.
  • If the photo carousel is scrolled in the detail screen, the shared element transition still runs but it blips back to the first image at the start.

Changed the animation timings for both of these. Jumps directly to the first image which fades in, but now won't do an odd cross fade.

  • The transition from the home list to the detail view feels asymmetric. When going from A -> B, the background content translates away. When going from B -> A, some content translates but the bottom nav bar translates up

Yaaa this has been the main problem. We don't know if there are shared transitions in time to disable the animated content transition.
We can either have the screens tell us there's going to be a shared animation, which is some of what I was playing with using AnimatedScreen. That or I think we'd need to rebuild AnimatedContent in some way to let us compose both screens ahead of time. Which lets us check for SharedTransitionScope.isTransitionActive, and then build the transition spec 😬

I like the idea of the AnimatedScreen/ AnimatedNavigationOverride as it'd dovetail into a more capable API for changing the enter/exit animation based on the screens involved in the transition. Think that should be a separate PR though.

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