Open zach-klippenstein opened 4 years ago
I have a terrible memory apparently, this is almost identical to https://github.com/mobnetic/compose-shared-element. Ideally I think it would be simplest to just make this library a wrapper around that one, and provide the basic backstack features like determining direction and saved state.
Excellent documentation 👏
Will it be implemented? I am looking forward to this
No updates at this time. I'm kind of waiting to see what the new transition APIs in compose turn out like.
Animating entire screens is one use case for this library, but it's not uncommon to need to animate particular parts of those screens differently or independently. The Android animation APIs have the concept of "shared element transitions", which use string tags to animate views inside screens separate from the rest of the screen (e.g. expanding a hero image). This library currently doesn't provide a way to do this. Neither does Compose itself.
The simplest solution I've been able to come up with so far borrows some concepts from Compose's
Transition
API (animation keys) and some from this library's current API (more dynamic screen key management).Example
It's probably easiest to introduce with a sketch. Note the uses of the
transitionElement
modifier.Frontend API
These are the important APIs used by this sketch:
TransitionScope
A wrapper composable that defines the scope in which
transitionElement
s are associated bytag
. Transitions are performed when the key passed to this composable changes. EachtransitionElement
that is present in the previous screen is animated out, and each one that is present in the new screen is animated in. More on what "in" and "out" mean below.transitionElement
Returns a
Modifier
that will tag the modified element with a stringtag
and an associated transition type. Thetag
is used to associate elements between different screens in theTransitionScope
. Thetransition
defines the animations used to animate the element when it is added to or removed from the composition. More on this below.Animation keys
This isn't used in the above sample, but each
transitionElement
can also optionally take a map of keys (actually Compose'sPropKey
) of arbitrary types. These keys behave like they would for aTransitionDefinition
, but instead of having each key's state be defined statically, the state is defined at the use site. So for example, a key forColor
could be defined,itemColor
, and passed totransitionElement
along with a value. The transition would then be able to read the incoming/outgoing values for this key and animate between them. E.g.:transitionElement("tag", customTransition, colorKey to Color.Red)
.Note that all elements implicitly get the bounds of their modified composables as an implicit "key". The
Slide
andBounds
transitions only use that value, so no additional keys need to be specified.Transitions.Slide
A transition that animates incoming and outgoing elements separately, by sliding them horizontally side-by-side. The direction of the movement needs to be specified somehow, not sure what makes the most sense for that. A backstack would need to calculate this direction from whether or not the screen existed in the previous stack or not. Note that because this transition is applied around each screen, the entire screen's contents will be transformed.
Transitions.Bounds
A transition that takes both the bounds from the element being removed and the one being added, does some math to map coordinates from each other's parent layouts, and then animates the bounds of the outgoing element to the incoming one (both scaling and transforming). Note that this animation's coordinates need to be relative to the
TransitionScope
, since the wrappingSlide
transition will also be animating each hero element along with its containing screen.Note that a special use case for this transition is when two elements that exist in two screens with teh same bounds are nested inside another
transitionElement
-modified Composable, such as the "screen" one in the example. In this case, the transition causes the nested elements to appear as if they are static, and not moving, while the rest of the screen animates around them.Backend API
A transition (such as
Slide
orBounds
) is defined as something like the following interface:Transition implementations can be composed by composing the returned modifiers. For example, a
Bounds
transition might be combined with aCrossfade
transition to make the transition look even smoother. Transition implementations can take parameters (e.g. theBounds
transition might take an enum that determines whether to only animate the composable being added, the one being removed, or both to supportCrossfade
behavior;Slide
needs to know which direction to slide).Transitioning between existing elements is only one potential use case. A "transition" could also be defined that only animates elements being added for the very first time or removed (e.g.
Transitions.SlideIn
,Transitions.FadeOut
, etc), and doesn't transition between existing elements at all (such a transition would simply returnModifier
whenreplacingBounds
orreplacedByBounds
is non-null).Benefits over existing
Backstack
APIThis transition API is (intended to be) a superset of the existing one in this library. All the functionality currently provided (except maybe "inspectors", see below) should be obtainable by using a
transitionElement
immediately inside theTransitionScope
, like the "screen" element in the example.Benefits over Compose's
Transition
APIThe
Transition
API is provides functionality that gets pretty close to this, but requires a pre-definedTransitionDefinition
. One of the main use cases of the proposed API is to determine the coordinates of shared elements before and after their screen transitions, which can only be known when those elements are actually composed. It might be possible to tweak the standardTransition
API to support this, but I haven't thought about what that would look like.Open Questions
transitionElement
modifiers register themselves with theTransitionScope
? A couple possibilities are thetransitionElement
function is itself Composable, so it is aware of its state and could access anAmbient
provided by theTransitionScope
, although this technique is being replaced in the standard library withcomposed
modifiers. I'm not surecomposed
modifiers support the other functionality we need though (see below questions).transitionElement
is effectively a modifier that needs to apply other modifiers to the thing that it's modifying. I'm not sure this sort of "meta-modifier" concept is even supported at all, or supported in a performant-enough way for animations. Might need to use a wrapper composable instead.Slide
transition needs additional information to choose a movement direction. How should that be communicated?TransitionScopes
be supported, or explicitly forbidden? If supported, shouldtransitionElement
s only apply to the innermost enclosing scope or be able to specify which of any of their enclosing scopes they belong to? Simplest to forbidden them for an MVP at least.Bounds
in the example would not be transformed by the inspector, since they explicitly break out of the transformations from their parent.