Last active
August 8, 2023 13:28
-
-
Save DavidIbrahim/5f4c0387b571f657f4de976822c2a225 to your computer and use it in GitHub Desktop.
allows to switch between two layouts with a Slide in and out animation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import androidx.compose.animation.core.* | |
import androidx.compose.foundation.layout.Box | |
import androidx.compose.material.Text | |
import androidx.compose.runtime.* | |
import androidx.compose.ui.Modifier | |
import androidx.compose.ui.geometry.Offset | |
import androidx.compose.ui.graphics.graphicsLayer | |
import androidx.compose.ui.util.fastForEach | |
/** | |
* [CrossSlide] allows to switch between two layouts with a CrossSlide animation. | |
* | |
* | |
* | |
* @param targetState is a key representing your target layout state. Every time you change a key | |
* the animation will be triggered. The [content] called with the old key will be faded out while | |
* the [content] called with the new key will be faded in. | |
* @param modifier Modifier to be applied to the animation container. | |
* @param animationSpec the [AnimationSpec] to configure the animation. | |
* @param reverseAnimation to reverse the sliding, for example when pressing the back button | |
*/ | |
@Composable | |
fun <T> CrossSlide( | |
targetState: T, | |
modifier: Modifier = Modifier, | |
animationSpec: FiniteAnimationSpec<Offset> = tween(500), | |
reverseAnimation: Boolean = false, | |
content: @Composable (T) -> Unit | |
) { | |
val direction: Int = if (reverseAnimation) -1 else 1 | |
val items = remember { mutableStateListOf<SlideInOutAnimationState<T>>() } | |
val transitionState = remember { MutableTransitionState(targetState) } | |
val targetChanged = (targetState != transitionState.targetState) | |
transitionState.targetState = targetState | |
val transition: Transition<T> = updateTransition(transitionState) | |
if (targetChanged || items.isEmpty()) { | |
// Only manipulate the list when the state is changed, or in the first run. | |
val keys = items.map { it.key }.run { | |
if (!contains(targetState)) { | |
toMutableList().also { it.add(targetState) } | |
} else { | |
this | |
} | |
} | |
items.clear() | |
keys.mapTo(items) { key -> | |
SlideInOutAnimationState(key) { | |
val xTransition by transition.animateOffset( | |
transitionSpec = { animationSpec }, label = "" | |
) { if (it == key) Offset(0f, 0f) else Offset(1000f, 1000f) } | |
Box(modifier.graphicsLayer { | |
this.translationX = | |
if (transition.targetState == key) direction * xTransition.x else direction * -xTransition.x | |
}) { | |
content(key) | |
} | |
} | |
} | |
} else if (transitionState.currentState == transitionState.targetState) { | |
// Remove all the intermediate items from the list once the animation is finished. | |
items.removeAll { it.key != transitionState.targetState } | |
} | |
Box(modifier) { | |
items.forEach { | |
key(it.key) { | |
it.content() | |
} | |
} | |
} | |
} | |
data class SlideInOutAnimationState<T>( | |
val key: T, | |
val content: @Composable () -> Unit | |
) | |
@Composable | |
fun CrossSlideExample(){ | |
var currentPage by remember { mutableStateOf("A") } | |
CrossSlide(targetState = currentPage) { screen -> | |
when (screen) { | |
"A" -> Text("Page A") | |
"B" -> Text("Page B") | |
} | |
} | |
} |
you're welcome.
yeah, I built it as a temporary solution for animation during navigation between entire screens.
For anyone looking: I use now https://github.com/Syer10/accompanist .
That is a port of ViewPager that works on both Compose for Desktop and Android.
that seems promising, thanks for sharing 👍
Hi, thanks for sharing. I did a small adjustment, that chooses the direction based on the last state that will be compared to the targetState based on a List of possible states. Maybe it's interesting for somebody
Only showing lines 24-31
[...]
@Composable
fun <T> CrossSlide(
currentState: T,
targetState: T,
orderedContent: List<T> = emptyList<T>(),
modifier: Modifier = Modifier,
animationSpec: FiniteAnimationSpec<Offset> = tween(500),
content: @Composable (T) -> Unit
) {
val direction: Int = if(orderedContent.indexOf(currentState) < orderedContent.indexOf(targetState)) 1 else -1
[...]
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thank you for sharing this sample. It also works on Compose for Desktop.
But unfortunately it seems to need still some work in regards that the page that slides in is not clipped.
So I guess this only works if your CrossSlide fills the entire screen and looks strange otherwise.