Skip to content

Instantly share code, notes, and snippets.

@Rahkeen
Created April 11, 2024 05:22
Show Gist options
  • Save Rahkeen/43c42a37599967a340e77b49d8daa8b3 to your computer and use it in GitHub Desktop.
Save Rahkeen/43c42a37599967a340e77b49d8daa8b3 to your computer and use it in GitHub Desktop.
A really simple grid-to-full-screen transition using Shared Elements
enum class Scene {
Grid,
Closeup;
fun toggle() = if (this == Grid) Closeup else Grid
}
data class ColorItemState(
val id: Int,
val color: Color,
val name: String,
)
val CoolColors = listOf(
ColorItemState(
id = 1,
color = CoolRed,
name = "Red"
),
ColorItemState(
id = 2,
color = CoolYellow,
name = "Yellow"
),
ColorItemState(
id = 3,
color = CoolGreen,
name = "Green"
),
ColorItemState(
id = 4,
color = CoolBlue,
name = "Blue"
),
ColorItemState(
id = 5,
color = CoolPurple,
name = "Purple"
),
)
data class ContainerTransformState(
val selectedItemId: Int? = null,
val colors: List<ColorItemState> = emptyList()
) {
val selectedItem = colors.find { it.id == selectedItemId }
}
@Composable
private fun rememberContainerTransformState(colors: List<ColorItemState>): MutableState<ContainerTransformState> {
return remember { mutableStateOf(ContainerTransformState(colors = colors)) }
}
@Preview
@Composable
fun ContainerTransformExample() {
var state by rememberContainerTransformState(
colors = CoolColors
)
var scene by remember { mutableStateOf(Scene.Grid) }
SharedTransitionLayout(
modifier = Modifier.fillMaxSize()
) {
AnimatedContent(
targetState = scene,
transitionSpec = {
fadeIn(
animationSpec = tween(
durationMillis = DURATION_EXTRA_LONG,
easing = EmphasizedEasing
)
).togetherWith(
fadeOut(
animationSpec = tween(
durationMillis = DURATION_EXTRA_LONG,
easing = EmphasizedEasing
)
)
)
},
label = "Scene Transition"
) { target ->
when (target) {
Scene.Grid -> {
LazyVerticalGrid(
modifier = Modifier
.fillMaxSize()
.windowInsetsPadding(WindowInsets.statusBars),
columns = GridCells.Fixed(2),
contentPadding = PaddingValues(4.dp),
verticalArrangement = Arrangement.spacedBy(4.dp),
horizontalArrangement = Arrangement.spacedBy(4.dp),
) {
items(state.colors, key = { it.id }) { item ->
Box(
modifier = Modifier
.sharedElement(
state = rememberSharedContentState(key = item.id),
animatedVisibilityScope = this@AnimatedContent,
boundsTransform = { _, _ ->
tween(durationMillis = DURATION_EXTRA_LONG, easing = EmphasizedEasing)
}
)
.fillMaxWidth()
.height(250.dp)
.clip(RoundedCornerShape(16.dp))
.background(color = item.color)
.clickable {
state = state.copy(
selectedItemId = item.id
)
scene = scene.toggle()
}
.padding(8.dp)
) {
Text(
modifier = Modifier
.align(Alignment.BottomCenter)
.renderInSharedTransitionScopeOverlay(
renderInOverlay = { item.id == state.selectedItemId }
)
.animateEnterExit(
enter = fadeIn(tween(delayMillis = 200)) + slideInVertically(
animationSpec = tween(delayMillis = 200),
initialOffsetY = { it / 2 }
),
exit = fadeOut(tween(durationMillis = 200))
),
text = item.name,
fontSize = 18.sp,
color = Color.White
)
}
}
}
}
Scene.Closeup -> {
val item = state.selectedItem!!
BackHandler {
scene = scene.toggle()
}
Box(
modifier = Modifier
.sharedElement(
state = rememberSharedContentState(key = item.id),
animatedVisibilityScope = this@AnimatedContent,
boundsTransform = { _, _ ->
tween(durationMillis = DURATION_EXTRA_LONG, easing = EmphasizedEasing)
},
)
.fillMaxSize()
.clip(RoundedCornerShape(16.dp))
.background(
color = item.color
)
.windowInsetsPadding(WindowInsets.statusBars)
) {
}
}
}
}
}
}
const val DURATION_EXTRA_LONG = 1000
private val emphasizedPath = Path().apply {
moveTo(0f, 0f)
cubicTo(0.05f, 0f, 0.133333f, 0.06f, 0.166666f, 0.04f)
cubicTo(0.208333f, 0.82f, 0.25f, 1f, 1f, 1f)
}
val emphasized = PathInterpolator(emphasizedPath)
val EmphasizedEasing = Easing { emphasized.getInterpolation(it) }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment