Irbisa · Cheatsheet
Compose Animation & Gestures
animate*AsState, Transition, AnimatedContent, Animatable, pointerInput, drag, nestedScroll, Canvas
How do you animate a composable's size when its content grows, for example an expandable card?
@Composablefun ExpandableCard(title: String, details: String) { var expanded by remember { mutableStateOf(false) } Card(onClick = { expanded = !expanded }) { Column( Modifier…How do you animate a composable appearing and disappearing, and how do you customize the enter and exit animation?
@Composablefun Details(details: String) { var visible by remember { mutableStateOf(false) } Column { Button(onClick = { visible = !visible }) { Text("Toggle") } AnimatedVisibility(…How do you make an animation repeat forever, such as a pulsing dot or a loading shimmer?
@Composablefun PulsingDot(modifier: Modifier = Modifier) { val transition = rememberInfiniteTransition(label = "pulse") val scale by transition.animateFloat( initialValue = 0.8f, targetValue = 1.2f,…The counter below animates, but the leaving and the arriving text both show the new number. What is wrong?
@Composablefun Total(count: Int) { AnimatedContent( targetState = count, transitionSpec = { slideInVertically { it } togetherWith slideOutVertically { -it } }, label = "total", ) { Text("Total: $count") }}When do you use
clickable,combinedClickableorpointerInput { detectTapGestures }to handle taps?// a post: tap opens, double tap likes, long press shows the menuBox( Modifier .clip(MaterialTheme.shapes.medium) // clip first, so the ripple is rounded .combinedClickable( onClick = onOpen,…How do you draw custom graphics in Compose, and what is the difference between
Canvas,drawBehindanddrawWithContent?@Composablefun ProgressRing(progress: () -> Float, modifier: Modifier = Modifier) { val track = MaterialTheme.colorScheme.surfaceVariant val fill = MaterialTheme.colorScheme.primary Canvas(modifier.size(64.dp)) { // progress() is read inside the draw lambda: redraw only val stroke = Stroke(width = 6.dp.toPx(), cap = StrokeCap.Round)…What is the difference between
animate*AsState,updateTransitionandAnimatable, and when do you pick each?Versions A and B animate the same two
Animatables. How long does each take, and in what order do the values move?val alpha = remember { Animatable(0f) }val offsetY = remember { Animatable(40f) } // version ALaunchedEffect(Unit) { alpha.animateTo(1f, tween(300)) offsetY.animateTo(0f, tween(300))} // version BLaunchedEffect(Unit) { launch { alpha.animateTo(1f, tween(300)) } launch { offsetY.animateTo(0f, tween(300)) }}Find the bug: each time
progresschanges, this bar drops to empty and refills from zero instead of moving on from its current width.@Composablefun ProgressBar(progress: Float, modifier: Modifier = Modifier) { val animated = Animatable(0f) LaunchedEffect(progress) { animated.animateTo(progress, animationSpec = tween(500)) } Box( modifier .fillMaxWidth() .height(8.dp) .drawBehind { drawRect(Color(0xFF3D7BFF), size = size.copy(width = size.width * animated.value)) } )}When do you choose
spring,tweenorkeyframesas an animation spec, and what isvisibilityThresholdfor?// exact curve from the design specval alpha by animateFloatAsState( targetValue = if (visible) 1f else 0f, animationSpec = tween(300, easing = CubicBezierEasing(0.2f, 0f, 0f, 1f)), label = "alpha",)…How do you animate items being added, removed and reordered in a
LazyColumn?@Composablefun TaskList(tasks: List<Task>, onToggle: (String) -> Unit) { LazyColumn { items(tasks, key = { it.id }) { task -> // stable, unique key TaskRow( task = task,…How do shared element transitions work in Compose, and how do you wire them up with Navigation Compose?
@Serializable object Covers@Serializable data class Detail(val id: String) @OptIn(ExperimentalSharedTransitionApi::class)@Composablefun App(nav: NavHostController) {…How does Compose treat the system "Remove animations" setting, and what do you still have to handle yourself?
What does
Modifier.graphicsLayerdo, and why is it the preferred way to animate alpha, scale and rotation?@Composablefun FlipCard(flipped: Boolean, front: @Composable () -> Unit, back: @Composable () -> Unit) { val rotation by animateFloatAsState(if (flipped) 180f else 0f, label = "flip") val showBack by remember { derivedStateOf { rotation > 90f } } Box( Modifier.graphicsLayer { // rotation is read here, in the draw phase…How do you write a custom gesture with
Modifier.pointerInput, and what do its keys control?@Composablefun VolumeStrip(onChange: (Float) -> Unit, modifier: Modifier = Modifier) { val change by rememberUpdatedState(onChange) // always the latest callback Box( modifier .size(48.dp, 200.dp)…Find the bug: while you drag this chip, it runs ahead of the finger, and the gap grows the farther you drag.
@Composablefun DraggableChip(label: String) { var offsetX by remember { mutableFloatStateOf(0f) } Text( text = label, modifier = Modifier .offset(x = offsetX.dp) .pointerInput(Unit) { detectHorizontalDragGestures { change, dragAmount -> change.consume() offsetX += dragAmount } } )}A drag handle built with
detectDragGesturesinside aLazyColumnrow blocks the list from scrolling. How does Compose decide which gesture wins?How do you implement pinch-to-zoom and pan on an image in Compose?
@Composablefun ZoomableImage(painter: Painter, modifier: Modifier = Modifier) { var scale by remember { mutableFloatStateOf(1f) } var offset by remember { mutableStateOf(Offset.Zero) } val state = rememberTransformableState { zoomChange, panChange, _ -> scale = (scale * zoomChange).coerceIn(1f, 5f)…How do you build a swipe-to-dismiss row, or any draggable that snaps to fixed positions?
enum class Swipe { Settled, Dismissed }@Composablefun SwipeToDeleteRow(item: Item, onDelete: () -> Unit) { val state = remember { AnchoredDraggableState(initialValue = Swipe.Settled) } LaunchedEffect(state.settledValue) { // act when the animation lands if (state.settledValue == Swipe.Dismissed) onDelete()…Your custom drawing allocates a
Pathand aBrushon every frame insidedrawBehind. What doesdrawWithCachechange?@Composablefun Gauge(progress: Float, modifier: Modifier = Modifier) { val sweep by animateFloatAsState(progress * 270f, label = "sweep") Box( modifier .size(160.dp)…How do you make a dragged element keep moving with the finger's velocity after release, and what does
Animatableneed during and after the drag?@Composablefun FlingCard(modifier: Modifier = Modifier) { val offsetX = remember { Animatable(0f) } val scope = rememberCoroutineScope() val decay = rememberSplineBasedDecay<Float>()…How does nested scrolling work in Compose, and how do you build a collapsing header with a
NestedScrollConnection?class CollapsingHeader(private val maxPx: Float) : NestedScrollConnection { var offset by mutableFloatStateOf(0f) // -maxPx..0f private set // finger up: the header shrinks BEFORE the list scrolls override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset { if (available.y >= 0f) return Offset.Zero…How do you animate a layout change where items move to new positions, for example a list switching to a two-column grid?
@OptIn(ExperimentalSharedTransitionApi::class, ExperimentalLayoutApi::class)@Composablefun Gallery(items: List<Item>, grid: Boolean) { LookaheadScope { FlowRow( Modifier.animateContentSize(), // the container size changes too…How do you blur a composable or apply a custom shader, and what do you ship on Android versions that cannot do it?
@Composablefun CoverImage(modifier: Modifier = Modifier) { // fallback decided in one place if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { // API 31+ Box(modifier.blur(16.dp, BlurredEdgeTreatment.Unbounded)) { Cover() } } else {…