mxalbert1996 / compose-shared-elements Goto Github PK
View Code? Open in Web Editor NEWShared Elements Transition for Jetpack Compose
License: MIT License
Shared Elements Transition for Jetpack Compose
License: MIT License
Dude, you've done a great job maintaining the original library, thanks for that.
I've been exploring its capabilities and found a bug.
Consider a following use-case: we have a screen with a list of cards and want to animate a clicked card and some of its children to a new screen.
More generally, we want a transition of a container SharedElement
with some other transition of its children SharedElement
s. I found out that this bug occurs when using a navigation-compose
library, specifically for me it is androidx.navigation:navigation-compose:2.4.0-alpha10
. Compose versions do not affect this bug, but mine is 1.0.3
.
Here's some sample code: NavHost
is itself inside of a SharedElementsRoot
, switching composables for it.
SharedElementsRoot {
Surface(
modifier = Modifier
.fillMaxSize(),
color = MaterialTheme.colors.background
) {
val navController = rememberNavController()
NavHost(
navController = navController,
startDestination = "First_screen"
) {
composable(route = "First_screen") {
LazyColumn(
contentPadding = PaddingValues(vertical = 100.dp) // Simulating position in the list
) {
item {
SharedElement(
key = "Container",
screenKey = "First"
) {
SharedCard {
navController.navigate("Second_screen")
}
}
}
}
}
composable(route = "Second_screen") {
SharedElement(
key = "Container",
screenKey = "Second"
) {
SharedCardContent {
navController.popBackStack()
}
}
}
}
}
}
And here is the code for containers:
@Composable
fun SharedCard(
onClick: () -> Unit
) {
Card(
modifier = Modifier
.fillMaxWidth(),
elevation = 2.dp,
shape = RoundedCornerShape(5.dp),
onClick = onClick
) {
Column(
modifier = Modifier
.padding(
top = 10.dp,
bottom = 8.dp,
start = 15.dp,
end = 15.dp
)
) {
Text(
text = "Card title",
style = MaterialTheme.typography.h6
)
Spacer(modifier = Modifier.height(10.dp))
Column(
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
SharedElement(
key = "author",
screenKey = "First"
) {
Text("Author name")
}
SharedElement(
key = "subject",
screenKey = "First"
) {
Text("Subject name")
}
}
}
}
}
@Composable
fun SharedCardContent(
onClick: () -> Unit
) {
Surface(
modifier = Modifier
.fillMaxWidth()
.clickable(onClick = onClick),
color = MaterialTheme.colors.surface,
elevation = 1.dp
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(20.dp),
verticalArrangement = Arrangement.spacedBy(10.dp)
) {
SharedElement(
key = "author",
screenKey = "Second"
) {
Text("Author: Author name")
}
SharedElement(
key = "subject",
screenKey = "Second"
) {
Text("Subject: Subject name")
}
}
}
}
It looks exactly like we want during transition forwards, but fails to position nested SharedElement
s during transition backwards:
However, if for some reason the whole tree is recomposed after the forwards transition, transition backwards plays as expected:
I assume it's because when SharedElementsRoot
is recomposed, it considers the Second
screen to be the Start
state of the transition and vice versa, so the actual backwards transition becomes the forwards one, which explains its failure to execute the actual forwards transition as intended.
It would be super cool if you could help fix this issue. Thanks!
When I use Your library with LazyList with not fullscreen element(e.g. custom AlertDialog), when switching back to card, AlertDialog is blinking. File I used:
DeepDive.zip
Just wanted to send over a quick note of thanks for updating this library. A contractor who worked on our project used the original mobnetic version, it was a relief to see your updated fork. I hope you'll keep it going!
Hello ๐
I think this is a reliable library that is still working.
Please update the project dependencies (compose 1.2.1) and write document for the public functions. At first, I did confused.
Recent updates have stabilized the lazyVerticalGrid
, so the demo is outdated.
Hey Albert! I'm running into an issue where elements will disappear if their transitions are interrupted. I haven't put together a demo repo yet but you could reproduce it pretty easily:
TLDR: Create a simple view that has a shared element that animates slowly between three positions. Add a button to toggle the state from 1 -> 2 -> 3 -> repeat. Click to start an animation and then click again to interrupt it. You'll see that it never makes it to the updated target position; instead, it just disappears completely.
I think the expected behavior would be for it to snap to its end position when interrupted. If this was the case, interrupting the transition from position 1 to 2 should make it snap to 2 and then animate there to position 3.
I spent a little time trying to fix this in the code but I haven't found a good workaround. It looks like the root of the issue is that this coroutine calls transition.onTransitionFinished()
on whichever transition started first -- it never gets called for the second one. So in my example above, if I interrupt the transition from 1->2, the completion of transition 3 will call onTransitionFinished()
on transition2 instead of transition3. This then doesn't unhide the correct element here.
A possible start of a fix might involve tracking the active animation. I found that I could get my code to correctly identify when the wrong element was called in onTransitionFinished()
by adding val activeAnimations = mutableMapOf<Any, Any>()
to SharedElementsRootState
, where the key is the element key and value is the active animating screenKey, and then setting it during onElementDisposed()
(the start of the next animation) and checking it during onTransitionFinished()
. Something like this...
fun onElementDisposed(elementInfo: SharedElementInfo) {
activeAnimations.put(elementInfo.key, elementInfo.screenKey)
choreographer.postCallback(elementInfo) {
val tracker = getTracker(elementInfo)
tracker.onElementUnregistered(elementInfo)
if (tracker.isEmpty) trackers.remove(elementInfo.key)
}
}
@Composable
private fun SharedElementTransitionsOverlay(rootState: SharedElementsRootState) {
// unchanged...
scope.launch {
// unchanged...
activeScreenKey = rootState.activeAnimations.get(startElement.info.key)
val correctTarget = activeScreenKey == startElement.info.screenKey
Timber.tag("wtf").i("firing onTransitionFinished() for startElement ${startElement.info.screenKey}. Is this correct? $correctTarget")
transition.onTransitionFinished()
}
}
But I haven't figured out what to do with this information, everything I've tried breaks it in a new way.
I'd really appreciate your help with this, it breaks my interface in some ways that are complete show-stoppers. Thank you for all your work!
do you have plan to add support for jetbrains compose (multiplatform)
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.