Build a Simple Windmill with Rotating Animation Jetpack Compose: A Beginner-Friendly Guide

Jai KeerthickJai Keerthick
4 min read

If you’ve ever wanted to animate something in Jetpack Compose but didn’t know where to start, this post is for you. Today, we’re going to build a cute, animated windmill, where the blades spin continuously using Jetpack Compose’s animation APIs.

And don’t worry – we’ll break down every line of code so that even if you’re just starting out with Compose, you’ll feel right at home.

What We’re Building

  • A blue sky background 🌤️

  • A green land base 🌱

  • A static windmill body 🏗️

  • A rotating windmill blade (animation magic!) 🔄

Here’s a quick preview of what it’ll look like:

Resources:

Please find the required drawables here:

  • blade.xml - This is the turbine blade that rotates
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="94dp"
    android:height="94dp"
    android:viewportWidth="94"
    android:viewportHeight="94">
  <path
      android:pathData="M37.516,24.108L35.648,24.97L57.096,71.43L58.964,70.568L37.516,24.108Z"
      android:fillColor="#C4AE9B"/>
  <path
      android:pathData="M28.937,0.007L22.867,2.811L36.452,32.225L42.522,29.422L28.937,0.007Z"
      android:fillColor="#996641"/>
  <path
      android:pathData="M57.398,61.633L51.328,64.437L64.913,93.851L70.983,91.048L57.398,61.633Z"
      android:fillColor="#996641"/>
  <path
      android:pathData="M28.937,0L25.902,1.402L39.487,30.816L42.522,29.415L28.937,0Z"
      android:fillColor="#825434"/>
  <path
      android:pathData="M57.398,61.626L54.363,63.028L67.948,92.442L70.983,91.041L57.398,61.626Z"
      android:fillColor="#825434"/>
  <path
      android:pathData="M70.546,34.932L24.086,56.379L24.948,58.247L71.408,36.799L70.546,34.932Z"
      android:fillColor="#C4AE9B"/>
  <path
      android:pathData="M29.414,51.333L0,64.918L2.803,70.988L32.218,57.403L29.414,51.333Z"
      android:fillColor="#996641"/>
  <path
      android:pathData="M91.047,22.868L61.633,36.453L64.436,42.523L93.851,28.938L91.047,22.868Z"
      android:fillColor="#996641"/>
  <path
      android:pathData="M29.418,51.331L0.004,64.916L1.406,67.951L30.82,54.366L29.418,51.331Z"
      android:fillColor="#825434"/>
  <path
      android:pathData="M91.043,22.868L61.629,36.453L63.03,39.487L92.445,25.902L91.043,22.868Z"
      android:fillColor="#825434"/>
  <path
      android:pathData="M49.563,46.859C49.563,48.471 48.36,49.778 46.873,49.778C45.387,49.778 44.184,48.471 44.184,46.859C44.184,45.247 45.387,43.94 46.873,43.94C48.36,43.94 49.563,45.247 49.563,46.859Z"
      android:fillColor="#996641"/>
</vector>
  • body.xml - This is the windmill body
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="50dp"
    android:height="80dp"
    android:viewportWidth="50"
    android:viewportHeight="80">
  <path
      android:pathData="M48.323,30.97H1.523V79.827H48.323V30.97Z"
      android:fillColor="#996641"/>
  <path
      android:pathData="M24.923,0.884L1.523,30.97H48.323L24.923,0.884Z"
      android:fillColor="#BF8C6A"/>
  <path
      android:pathData="M33.669,43.056H16.184V78.541H33.669V43.056Z"
      android:fillColor="#453D39"/>
  <path
      android:pathData="M49.096,32.384C49.096,33.449 48.543,34.313 46.91,34.313H2.682C1.049,34.313 0.496,33.449 0.496,32.384C0.496,31.32 1.049,30.456 2.682,30.456H46.91C48.543,30.456 49.096,31.32 49.096,32.384Z"
      android:fillColor="#C4AE9B"/>
</vector>

Okay now we have all that we want, Let’s dive in!


Step 1: Creating the Base Layout

We start with a Box that fills the whole screen and sets the background color to a sky blue.

Box(
    modifier = Modifier
        .fillMaxSize()
        .background(color = SkyBlue),
    contentAlignment = Alignment.Center
)

val SkyBlue = Color(0xFF96e2ee)

This gives us a full-screen canvas to build on.


Step 2: ConstraintLayout for Positioning

Inside the Box, we use a ConstraintLayout to position two main elements:

  • The land (green rectangle at the bottom)

  • The windmill (centered just above the land)

ConstraintLayout(
    modifier = Modifier.fillMaxSize()
)

We define two references (land, windMill) and use constrainAs() to position them relative to each other.


Step 3: Drawing the Land

This part is simple—we use a Spacer to create a green strip at the bottom:

Spacer(
    modifier = Modifier
        .constrainAs(ref = land){
            bottom.linkTo(parent.bottom)
        }
        .fillMaxWidth()
        .height(50.dp)
        .background(color = LandGreen)
)

val LandGreen = Color(0xFF66a558)

Think of this as the "ground" our windmill stands on.


Step 4: Adding the Windmill Body and Blades

Inside another ConstraintLayout, we draw two images:

  1. Body – the static part of the windmill

  2. Blade – the part that rotates

Image(
    modifier = Modifier
        .constrainAs(ref = body) {},
    painter = painterResource(id = R.drawable.body),
    contentDescription = "Body"
)

The body stays still. It just gets centered above the land.

The blade, however, is where the fun begins.


Step 5: Rotating the Blades with Animation

We use rememberInfiniteTransition() to create a smooth, repeating animation.

val infiniteTransition = rememberInfiniteTransition(label = "InfiniteTransition")

val turbineState = infiniteTransition.animateFloat(
    initialValue = 0F,
    targetValue = 360F,
    animationSpec = infiniteRepeatable(
        animation = tween(
            durationMillis = 2000,
            easing = LinearEasing
        )
    ),
    label = "Turbine"
)

Here’s what’s happening:

  • We animate from 0° to 360° (a full circle)

  • It takes 2 seconds per rotation

  • It repeats forever

  • The animation uses LinearEasing (constant speed)

Then we apply the rotation to the blade image:

.rotate(degrees = turbineState.value)

Boom 💥 – your windmill blades now spin endlessly!

So, the entire WindMill()composable will look like this:

// WindMill.kt

@Composable
fun WindMill() {

    val infiniteTransition = rememberInfiniteTransition(label = "InfiniteTransition")

    val turbineState = infiniteTransition.animateFloat(
        initialValue = 0F,
        targetValue = 360F,
        animationSpec = infiniteRepeatable(
            animation = tween(
                durationMillis = 2000,
                easing = LinearEasing
            )
        ),
        label = "Turbine"
    )

    Box(
        modifier = Modifier
            .fillMaxSize()
            .background(color = SkyBlue),
        contentAlignment = Alignment.Center
    ) {
        ConstraintLayout(
            modifier = Modifier
                .fillMaxSize()
        ) {

            val (land, windMill) = createRefs()

            Spacer(
                modifier = Modifier
                    .constrainAs(ref = land){
                        bottom.linkTo(parent.bottom)
                    }
                    .fillMaxWidth()
                    .height(50.dp)
                    .background(color = LandGreen)
            )

            ConstraintLayout(
                modifier = Modifier
                    .constrainAs(ref = windMill){
                        bottom.linkTo(land.top)
                        centerHorizontallyTo(land)
                    }
            ) {

                val (blade, body) = createRefs()

                Image(
                    modifier = Modifier
                        .constrainAs(ref = body) {},
                    painter = painterResource(id = R.drawable.body),
                    contentDescription = "Body"
                )

                Image(
                    modifier = Modifier
                        .constrainAs(ref = blade) {
                            centerHorizontallyTo(body)
                            centerAround(body.top)
                        }
                        .padding(top = 6.dp)
                        .rotate(degrees = turbineState.value),
                    painter = painterResource(id = R.drawable.blade),
                    contentDescription = "Blade"
                )
            }
        }
    }
}

And, Move the custom colors that we have added above in your Color.kt file

// Color.kt

val LandGreen = Color(0xFF66a558)
val SkyBlue = Color(0xFF96e2ee)

Final Thoughts

You’ve just built a complete animated component in Jetpack Compose using:

  • Layouts (Box, Spacer, ConstraintLayout)

  • Images

  • Infinite animation

  • Modifiers for styling and rotation

This example is perfect for learning how to compose UIs declaratively and make them come alive with animation.


Want More?

Try experimenting with:

  • Different animation durations and easing

  • Multiple windmills!

  • Sun, clouds, or birds using Canvas or images


Feel free to copy this code and build your own animated farm. Compose is fun once you get the hang of it. Happy spinning! 🌪️

0
Subscribe to my newsletter

Read articles from Jai Keerthick directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Jai Keerthick
Jai Keerthick