Mobile Development 10 min read

Jetpack Compose Animation Tutorial

This Jetpack Compose animation tutorial demonstrates how to create state‑driven, visibility, size, cross‑fade, single‑value, and composite animations using declarative APIs such as remember, mutableStateOf, AnimatedVisibility, animateContentSize, animate*AsState, and updateTransition, showing concise Kotlin code that simplifies UI animation development.

vivo Internet Technology
vivo Internet Technology
vivo Internet Technology
Jetpack Compose Animation Tutorial

Jetpack Compose is a new Android UI toolkit released by Google that simplifies and accelerates native UI development. It is a declarative UI framework that brings significant benefits such as more concise code, higher development efficiency, intuitive Kotlin APIs, and powerful preview tools.

Development Environment

The author uses Android Studio Canary to avoid extra configuration. The project is created with an Empty Compose Activity template. The build.gradle file must contain matching versions of com.android.tools.build:gradle and org.jetbrains.kotlin:kotlin-gradle-plugin :

dependencies {
  classpath "com.android.tools.build:gradle:7.0.0-alpha15"
  classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.30"
}

Jetpack Compose Animations

Compose provides a rich set of animation APIs that can be used to create various effects. The tutorial covers several common animation techniques.

3.1 State‑driven Animation

Animations are driven by state changes. remember and mutableStateOf are used to observe state values. When the state changes, the UI recomposes and the animation runs automatically.

3.2 Visibility Animation – AnimatedVisibility

@ExperimentalAnimationApi
@Composable
fun AnimatedVisibility(
    visible: Boolean,
    modifier: Modifier = Modifier,
    enter: EnterTransition = fadeIn() + expandIn(),
    exit: ExitTransition = shrinkOut() + fadeOut(),
    initiallyVisible: Boolean = visible,
    content: @Composable () -> Unit
) {
    AnimatedVisibilityImpl(visible, modifier, enter, exit, initiallyVisible, content)
}

The default animation is fade‑in‑expand for entering and shrink‑out‑fade‑out for exiting. An example toggles an image’s visibility with a button:

@Composable
fun AnimationDemo() {
    var visible by remember { mutableStateOf(true) }
    Column(
        Modifier.fillMaxSize(),
        verticalArrangement = Arrangement.Top,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Button(onClick = { visible = !visible }) {
            Text(text = if (visible) "Hide" else "Show")
        }
        Spacer(Modifier.height(16.dp))
        AnimatedVisibility(
            visible = visible,
            enter = slideInVertically() + fadeIn(),
            exit = slideOutVertically() + fadeOut()
        ) {
            Image(painter = painterResource(id = R.drawable.pikaqiu), contentDescription = null, Modifier.fillMaxSize())
        }
    }
}

3.3 Layout Size Animation – animateContentSize

fun Modifier.animateContentSize(
    animationSpec: FiniteAnimationSpec
= spring(),
    finishedListener: ((initialValue: IntSize, targetValue: IntSize) -> Unit)? = null
)

This extension on Modifier animates size changes. An example changes a Size variable with a button and applies animateContentSize() to a Box containing an image.

// Size animation example
var size by remember { mutableStateOf(Size(300F, 300F)) }
Column(Modifier.fillMaxSize(), Arrangement.Top, Alignment.CenterHorizontally) {
    Button(onClick = {
        size = if (size.height == 300F) Size(500F, 500F) else Size(300F, 300F)
    }) { Text(if (size.height == 300F) "Shrink" else "Expand") }
    Spacer(Modifier.height(16.dp))
    Box(Modifier.animateContentSize()) {
        Image(painter = painterResource(id = R.drawable.pikaqiu), contentDescription = null,
            Modifier.animateContentSize().size(size.height.dp))
    }
}

3.4 Crossfade Layout Switching

// Crossfade animation example
var fadeStatus by remember { mutableStateOf(true) }
Column(Modifier.fillMaxSize(), Arrangement.Top, Alignment.CenterHorizontally) {
    Button(onClick = { fadeStatus = !fadeStatus }) { Text(if (fadeStatus) "Fade In" else "Fade Out") }
    Spacer(Modifier.height(16.dp))
    Crossfade(targetState = fadeStatus, animationSpec = tween(3000)) { screen ->
        when (screen) {
            true -> Image(painter = painterResource(id = R.drawable.pikaqiu), contentDescription = null,
                Modifier.animateContentSize().size(300.dp))
            false -> Image(painter = painterResource(id = R.drawable.pikaqiu2), contentDescription = null,
                Modifier.animateContentSize().size(300.dp))
        }
    }
}

3.5 Single‑value Animations – animate*AsState

Functions such as animateColorAsState , animateDpAsState , and animateFloatAsState animate a single value from its current state to a target value.

// Example of animateFloatAsState
var transparent by remember { mutableStateOf(true) }
val alpha: Float by animateFloatAsState(if (transparent) 1f else 0.5f)
Column(Modifier.fillMaxSize(), Arrangement.Top, Alignment.CenterHorizontally) {
    Button(onClick = { transparent = !transparent }) { Text(if (transparent) "Light" else "Dark") }
    Spacer(Modifier.height(16.dp))
    Box {
        Image(painter = painterResource(id = R.drawable.pikaqiu), contentDescription = null,
            Modifier.animateContentSize().graphicsLayer(alpha = alpha).size(300.dp))
    }
}

3.6 Composite Animation – updateTransition

var imagePosition by remember { mutableStateOf(ImagePosition.TopLeft) }
val transition = updateTransition(targetState = imagePosition, label = "")
val boxOffset by transition.animateOffset(label = "") { position ->
    when (position) {
        ImagePosition.TopLeft -> Offset(-60F, 0F)
        ImagePosition.BottomRight -> Offset(60F, 120F)
        ImagePosition.TopRight -> Offset(60F, 0F)
        ImagePosition.BottomLeft -> Offset(-60F, 120F)
    }
}
Button(onClick = { imagePosition = ChangePosition(imagePosition) }) { Text("Change position") }
Box {
    Image(painter = painterResource(id = R.drawable.pikaqiu), contentDescription = null,
        Modifier.offset(boxOffset.x.dp, boxOffset.y.dp).animateContentSize().size(300.dp))
}

enum class ImagePosition { TopRight, TopLeft, BottomRight, BottomLeft }

fun ChangePosition(position: ImagePosition) = when (position) {
    ImagePosition.TopLeft -> ImagePosition.BottomRight
    ImagePosition.BottomRight -> ImagePosition.TopRight
    ImagePosition.TopRight -> ImagePosition.BottomLeft
    ImagePosition.BottomLeft -> ImagePosition.TopLeft
}

Conclusion

Jetpack Compose dramatically simplifies animation by allowing developers to declare UI changes in composable functions. The framework handles the underlying animation mechanics, leading to more readable code and faster development cycles.

animationAndroidKotlinUI DevelopmentJetpack ComposeCompose
vivo Internet Technology
Written by

vivo Internet Technology

Sharing practical vivo Internet technology insights and salon events, plus the latest industry news and hot conferences.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.