Skip to content

Instantly share code, notes, and snippets.

@jacksonfdam
Forked from AndroidPoet/BreathingAnimation.kt
Created October 3, 2024 08:50
Show Gist options
  • Save jacksonfdam/43c96ce09805bdbc105bc0dc1f5e895c to your computer and use it in GitHub Desktop.
Save jacksonfdam/43c96ce09805bdbc105bc0dc1f5e895c to your computer and use it in GitHub Desktop.
import androidx.compose.animation.core.*
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.material.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import kotlin.math.PI
import kotlin.math.sin
@Composable
fun BreathingAnimation() {
val animatedProgress = rememberInfiniteTransition(label = "").animateFloat(
initialValue = 0f, targetValue = 1f, animationSpec = infiniteRepeatable(
animation = tween(4000, easing = LinearEasing), repeatMode = RepeatMode.Reverse
), label = ""
)
Box(
modifier = Modifier
.fillMaxSize()
.background(Color(0xFF1D224F))
) {
Text(
text = if (animatedProgress.value < 0.5f) "Breathe out " else "Breathe in",
color = Color.White,
fontSize = 32.sp,
fontWeight = FontWeight.Bold,
modifier = Modifier
.align(Alignment.TopCenter)
.padding(top = 80.dp)
)
Canvas(modifier = Modifier.fillMaxSize()) {
val (blobHeight, blobTopY) = drawBreathingShape(animatedProgress.value)
drawFace(blobTopY, animatedProgress.value)
}
}
}
fun DrawScope.drawBreathingShape(progress: Float): Pair<Float, Float> {
val width = size.width
val height = size.height
val maxHeight = height * 0.7f
val minHeight = height * 0.2f
val currentHeight =
minHeight + (maxHeight - minHeight) * (0.5f + 0.5f * sin(progress * PI.toFloat()))
val topY = height - currentHeight
val path = Path().apply {
moveTo(0f, height)
lineTo(0f, topY)
quadraticBezierTo(
width / 2, topY - (maxHeight - minHeight) * 0.2f, width, topY
)
lineTo(width, height)
close()
}
drawPath(
path = path, color = Color(0xFFFF9800)
)
return Pair(currentHeight, topY)
}
fun DrawScope.drawFace(blobTopY: Float, progress: Float) {
val width = size.width
val faceY = blobTopY
val eyeWidth = width * 0.1f
val eyeHeight = eyeWidth * 0.5f
val mouthWidth = width * 0.4f
val faceColor = Color(0xFF4A4A4A)
val strokeWidth = 26f
drawArc(
color = faceColor,
startAngle = 0f,
sweepAngle = 180f,
useCenter = false,
topLeft = Offset(width * 0.35f, faceY + eyeHeight),
size = Size(eyeWidth, eyeHeight * 2),
style = Stroke(width = strokeWidth)
)
drawArc(
color = faceColor,
startAngle = 0f,
sweepAngle = 180f,
useCenter = false,
topLeft = Offset(width * 0.65f - eyeWidth, faceY + eyeHeight),
size = Size(eyeWidth, eyeHeight * 2),
style = Stroke(width = strokeWidth)
)
val smileProgress = sin(progress * PI.toFloat())
val smileHeight = 60f * (1 + smileProgress)
val smileWidth = mouthWidth * (0.5f + 0.5f * smileProgress)
drawArc(
color = faceColor,
startAngle = 0f,
sweepAngle = 180f,
useCenter = false,
topLeft = Offset(width * 0.5f - smileWidth / 2, faceY + eyeHeight * 4),
size = Size(smileWidth, smileHeight),
style = Stroke(width = strokeWidth)
)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment