Skip to content

Instantly share code, notes, and snippets.

@brkckr
Last active January 14, 2026 21:50
Show Gist options
  • Select an option

  • Save brkckr/c72e7c9a69df9d6e0022489614856895 to your computer and use it in GitHub Desktop.

Select an option

Save brkckr/c72e7c9a69df9d6e0022489614856895 to your computer and use it in GitHub Desktop.
Rotating 3D sphere animation in Jetpack Compose using Canvas
@Composable
fun Sphere() {
val infiniteTransition = rememberInfiniteTransition(label = "SphereRotation")
// Smooth 30-second full rotation around Y-axis
val rotationAngleDegrees by infiniteTransition.animateFloat(
initialValue = 0f,
targetValue = 360f,
animationSpec = infiniteRepeatable(
animation = tween(durationMillis = 15000, easing = LinearEasing),
repeatMode = RepeatMode.Restart
),
label = "RotationAngle"
)
Box(
modifier = Modifier
.fillMaxSize()
.background(Color(0xFF121212))
) {
Canvas(modifier = Modifier.fillMaxSize()) {
val canvasWidth = size.width
val canvasHeight = size.height
val center = Offset(canvasWidth / 2f, canvasHeight / 2f)
val sphereRadius = minOf(canvasWidth, canvasHeight) * 0.4f
// Google-inspired vibrant colors for four quadrants
val quadrantColors = listOf(
Color(0xFFFF3B30), // Red
Color(0xFF4285F4), // Blue
Color(0xFFFFD60A), // Yellow
Color(0xFF34A853) // Green
)
// Precompute rotation values to avoid recalculation in tight loops
val rotationRadians = Math.toRadians(rotationAngleDegrees.toDouble()).toFloat()
val cosRotation = cos(rotationRadians)
val sinRotation = sin(rotationRadians)
// Configuration for latitude bands (phi)
val numLatitudeBands = 12
val minPointsPerBand = 2
val maxPointsPerBand = 9
// Draw each of the four spherical quadrants
for (quadrantIndex in 0 until 4) {
val baseColor = quadrantColors[quadrantIndex]
val thetaStart = quadrantIndex * (PI / 2f) // Start angle in radians
val thetaEnd = (quadrantIndex + 1) * (PI / 2f) // End angle
for (bandIndex in 0 until numLatitudeBands) {
// Normalized position in latitude (0 at south pole, 1 at north pole)
val latitudeFraction = (bandIndex + 0.5f) / numLatitudeBands
val phi = latitudeFraction * PI.toFloat() // Latitude angle (0 to PI)
// More points near equator for smoother appearance
val latitudeDensityFactor = sin(phi)
val pointsInBand = (
minPointsPerBand + (maxPointsPerBand - minPointsPerBand) * latitudeDensityFactor
).toInt().coerceAtLeast(2)
val thetaStep = (thetaEnd - thetaStart) / pointsInBand
for (pointIndex in 0 until pointsInBand) {
val theta = thetaStart + (pointIndex + 0.5f) * thetaStep
// Spherical coordinates → Cartesian (before rotation)
val x = sphereRadius * sin(phi) * cos(theta)
val y = sphereRadius * sin(phi) * sin(theta)
val z = sphereRadius * cos(phi)
// Apply Y-axis rotation
val rotatedX = x * cosRotation + z * sinRotation
val rotatedZ = -x * sinRotation + z * cosRotation
// Back-face culling: only draw particles on the visible hemisphere
if (rotatedZ >= 0f) {
// Depth-based sizing and opacity for 3D illusion
val depthFactor = (rotatedZ + sphereRadius) / (2f * sphereRadius)
val particleRadius = 6f + depthFactor * 12f
val alpha = 0.75f + depthFactor * 0.25f
drawCircle(
color = baseColor.copy(alpha = alpha.toFloat()),
radius = particleRadius.toFloat(),
center = center + Offset(rotatedX.toFloat(), y.toFloat())
)
}
}
}
}
}
}
}

Rotating 3D sphere animation in Jetpack Compose using Canvas

animation

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment