Charts
Chartsanimated

Needle gauge

A speedometer with tick marks and a needle that swings to its reading.

Installation

caveui components are copy-paste Jetpack Compose built entirely on Material 3 — there's no caveui dependency to add. Make sure Material 3 is on your classpath (it ships with the Compose BOM), then copy the Usage snippet below into your project.

kotlin
// build.gradle.kts (module)
dependencies {
    implementation(platform("androidx.compose:compose-bom:2025.06.00"))
    implementation("androidx.compose.material3:material3")
}

Usage

kotlin
@Composable
fun NeedleGauge(
    value: Float = 0.7f,
    modifier: Modifier = Modifier,
) {
    val angle by animateFloatAsState(
        targetValue = 180f * value,
        animationSpec = spring(0.6f, 120f),
        label = "angle",
    )
    val tickColor = MaterialTheme.colorScheme.outline
    val zones = listOf(Color(0xFF22C55E), Color(0xFFF59E0B), Color(0xFFF43F5E))
    Canvas(modifier.size(170.dp, 100.dp)) {
        val arc = Size(size.width, size.width)
        zones.forEachIndexed { i, c ->
            drawArc(c, 180f + i * 60f, 60f, false, size = arc, style = Stroke(10.dp.toPx(), cap = StrokeCap.Butt))
        }
        repeat(11) { i ->
            val a = (180f + i * 18f) * PI.toFloat() / 180f
            val outer = center.copy(y = size.height) + Offset(cos(a), sin(a)) * (size.width / 2 - 14.dp.toPx())
            val inner = center.copy(y = size.height) + Offset(cos(a), sin(a)) * (size.width / 2 - 22.dp.toPx())
            drawLine(tickColor, inner, outer, 1.5.dp.toPx())
        }
        val pivot = center.copy(y = size.height)
        val na = (180f + angle) * PI.toFloat() / 180f
        drawLine(Color(0xFF111827), pivot, pivot + Offset(cos(na), sin(na)) * (size.width / 2 - 26.dp.toPx()), 3.dp.toPx(), cap = StrokeCap.Round)
        drawCircle(Color(0xFF111827), 6.dp.toPx(), pivot)
    }
}