Android
ProgressBar
Circular Progress
UI Design
Android Development

How to Create a circular progressbar in Android which rotates on it?

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

When an Android screen is waiting on network or background work, a rotating circular indicator is the standard visual cue. The shortest path is to use an indeterminate ProgressBar, but custom styling is often needed to match the app design. The key is to decide whether the built-in widget is enough or whether you need a custom drawable and animation.

Start with the Built-In Indeterminate ProgressBar

If you just need a rotating loader, Android already provides it. In XML:

xml
1<?xml version="1.0" encoding="utf-8"?>
2<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
3    android:layout_width="match_parent"
4    android:layout_height="match_parent">
5
6    <ProgressBar
7        android:id="@+id/progress"
8        style="?android:attr/progressBarStyleLarge"
9        android:layout_width="wrap_content"
10        android:layout_height="wrap_content"
11        android:layout_gravity="center"
12        android:indeterminate="true" />
13
14</FrameLayout>

In an Activity:

kotlin
1import android.os.Bundle
2import android.widget.ProgressBar
3import androidx.appcompat.app.AppCompatActivity
4
5class MainActivity : AppCompatActivity() {
6    override fun onCreate(savedInstanceState: Bundle?) {
7        super.onCreate(savedInstanceState)
8        setContentView(R.layout.activity_main)
9
10        val progress = findViewById<ProgressBar>(R.id.progress)
11        progress.visibility = ProgressBar.VISIBLE
12    }
13}

This is enough for many apps. Do not build a custom spinner just to reproduce the default one.

Create a Custom Circular Drawable

If you want your own color, stroke width, or visual style, create a ring drawable in res/drawable/circular_loader.xml:

xml
1<?xml version="1.0" encoding="utf-8"?>
2<shape xmlns:android="http://schemas.android.com/apk/res/android"
3    android:shape="ring"
4    android:innerRadiusRatio="3"
5    android:thicknessRatio="12"
6    android:useLevel="false">
7
8    <solid android:color="@android:color/transparent" />
9    <stroke
10        android:width="6dp"
11        android:color="#1E88E5" />
12</shape>

This gives you a circular ring that can be rotated. On its own, it is just a static drawable, so you still need animation.

Apply a Rotation Animation

Create res/anim/rotate_clockwise.xml:

xml
1<?xml version="1.0" encoding="utf-8"?>
2<rotate xmlns:android="http://schemas.android.com/apk/res/android"
3    android:fromDegrees="0"
4    android:toDegrees="360"
5    android:pivotX="50%"
6    android:pivotY="50%"
7    android:duration="900"
8    android:repeatCount="infinite"
9    android:interpolator="@android:anim/linear_interpolator" />

Then attach it to an ImageView or other view that uses the circular drawable:

xml
1<?xml version="1.0" encoding="utf-8"?>
2<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
3    android:id="@+id/loaderView"
4    android:layout_width="48dp"
5    android:layout_height="48dp"
6    android:layout_gravity="center"
7    android:src="@drawable/circular_loader" />

Kotlin code:

kotlin
1import android.os.Bundle
2import android.view.animation.AnimationUtils
3import android.widget.ImageView
4import androidx.appcompat.app.AppCompatActivity
5
6class MainActivity : AppCompatActivity() {
7    override fun onCreate(savedInstanceState: Bundle?) {
8        super.onCreate(savedInstanceState)
9        setContentView(R.layout.activity_main)
10
11        val loaderView = findViewById<ImageView>(R.id.loaderView)
12        val rotate = AnimationUtils.loadAnimation(this, R.anim.rotate_clockwise)
13        loaderView.startAnimation(rotate)
14    }
15}

That gives you a custom rotating circular progress indicator.

Show and Hide It with Real Work

The spinner should reflect actual background state. For example:

kotlin
1import androidx.lifecycle.lifecycleScope
2import kotlinx.coroutines.Dispatchers
3import kotlinx.coroutines.delay
4import kotlinx.coroutines.launch
5import kotlinx.coroutines.withContext
6
7loaderView.visibility = ImageView.VISIBLE
8
9lifecycleScope.launch {
10    withContext(Dispatchers.IO) {
11        delay(2000)
12    }
13    loaderView.clearAnimation()
14    loaderView.visibility = ImageView.GONE
15}

The exact background work will differ, but the principle is the same: show the loader before work starts and hide it on completion or failure.

If you are on Material Components, also consider CircularProgressIndicator, which provides a modern default with less custom code.

Pick the Simplest UI Layer for Your App

If the screen already uses classic Views, the XML and ProgressBar approach above is the lowest-friction option. If the app is on Jetpack Compose, use the framework-provided indicator instead of trying to port old animation resources.

kotlin
1import androidx.compose.material3.CircularProgressIndicator
2import androidx.compose.runtime.Composable
3
4@Composable
5fun LoadingIndicator() {
6    CircularProgressIndicator()
7}

The principle stays the same across UI toolkits: prefer the platform default unless you have a strong design requirement that justifies a custom loader.

Common Pitfalls

  • Rebuilding a custom loader when the built-in indeterminate ProgressBar would already solve the problem.
  • Using a non-linear interpolator, which makes the rotation feel jerky instead of steady.
  • Forgetting to stop or hide the animation when the background work ends.
  • Styling a static ring drawable without attaching an actual rotation animation.
  • Running long work on the main thread so the spinner freezes instead of animating smoothly.

Summary

  • Use an indeterminate ProgressBar first if the default look is acceptable.
  • For custom visuals, create a circular drawable and rotate it with an animation resource.
  • Keep the animation linear so the motion feels continuous.
  • Tie spinner visibility to real async work rather than leaving it on screen permanently.
  • Prefer simple built-in widgets unless the app genuinely needs a branded loader.

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.