Skip to content

Instantly share code, notes, and snippets.

@DavidIbrahim
Last active August 8, 2023 13:28
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save DavidIbrahim/5f4c0387b571f657f4de976822c2a225 to your computer and use it in GitHub Desktop.
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
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")
}
}
}
Copy link

ghost commented Jul 14, 2021

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.

@DavidIbrahim
Copy link
Author

you're welcome.

yeah, I built it as a temporary solution for animation during navigation between entire screens.

Copy link

ghost commented Jul 30, 2021

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.

@DavidIbrahim
Copy link
Author

that seems promising, thanks for sharing 👍

@jochen-oko
Copy link

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