Water Bottle Filling Animation in Jetpack Compose
In this tutorial, we'll build a dynamic water bottle UI using Jetpack Compose. The UI will feature a bottle with a gradient color that visually represents the amount of water consumed. We'll animate the water level and display the consumed amount dynamically as the user interacts with the UI.
Prerequisites
Kotlin:
A basic understanding of Kotlin programming language.
Jetpack Compose:
Familiarity with Jetpack Compose for Android UI development.
Android Studio:
Installed and set up for Android development.
We'll create two composables:
WaterBottleUi:
The main UI where the user can interact with the bottle.
WaterBottle:
The composable that renders the water bottle with animations and gradient effects.
Step 1: Setting Up the Project
1. Create a New Android Project:
Open Android Studio.
Start a new Android Studio project.
Choose "Empty Compose Activity" as the template.
Set your project name WaterBottleApp.
Step 2: Building the Water Bottle UI:
Create WaterBottleUi Composable:
WaterBottleUi
Copy this code →
package com.codingbihar.jetpackcomposeskill
import ...
@Composable
fun WaterBottleUi() {
Box(Modifier.fillMaxSize().background(color = MaterialTheme.colorScheme.background)) {
var usedWaterAmount by remember {
mutableIntStateOf(0)
}
val totalWaterAmount = remember {
1000
}
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
) {
WatterBottle(
totalWaterAmount = totalWaterAmount,
unit = "ml",
usedWaterAmount = usedWaterAmount
)
Spacer(modifier = Modifier.height(20.dp))
Text(
text = "Total Amount is : $totalWaterAmount",
textAlign = TextAlign.Center
)
Button(
onClick = { if (usedWaterAmount < 1000) usedWaterAmount += 200 },
colors = ButtonDefaults.buttonColors(containerColor = Color.Gray)
) {
Text(text = "Drink")
}
}
}
}
@Composable
fun WatterBottle(
modifier: Modifier = Modifier,
totalWaterAmount: Int,
unit: String,
usedWaterAmount: Int,
waterWavesColor: Color = Color(0xFF673AB7),
bottleColor: List = listOf(Color.Magenta, Color.Green),
capColor: Color = Color(0xFFE91E63)
) {
val waterPercentage = animateFloatAsState(
targetValue = (usedWaterAmount.toFloat() / totalWaterAmount.toFloat()),
label = "Water Waves animation",
animationSpec = tween(durationMillis = 1000)
).value
val usedWaterAmountAnimation = animateIntAsState(
targetValue = usedWaterAmount,
label = "Used water amount animation",
animationSpec = tween(durationMillis = 1000)
).value
Box(
modifier = modifier
.width(200.dp)
.height(600.dp)
) {
Canvas(modifier = Modifier.fillMaxSize()) {
val width = size.width
val height = size.height
val capWidth = size.width * 0.55f
val capHeight = size.height * 0.13f
//Draw the bottle body
val bodyPath = Path().apply {
moveTo(width * 0.3f, height * 0.1f)
lineTo(width * 0.3f, height * 0.2f)
quadraticBezierTo(
0f, height * 0.3f, // The pulling point
0f, height * 0.4f
)
lineTo(0f, height * 0.95f)
quadraticBezierTo(
0f, height,
width * 0.05f, height
)
lineTo(width * 0.95f, height)
quadraticBezierTo(
width, height,
width, height * 0.95f
)
lineTo(width, height * 0.4f)
quadraticBezierTo(
width, height * 0.3f,
width * 0.7f, height * 0.2f
)
lineTo(width * 0.7f, height * 0.2f)
lineTo(width * 0.7f, height * 0.1f)
close()
}
clipPath(
path = bodyPath
) {
// Draw the color of the bottle
drawRect(
brush = Brush.linearGradient(
colors = bottleColor,
start = Offset(0f, 0f),
end = Offset(0f, height)
),
size = size,
topLeft = Offset(0f, 0f)
)
//Draw the water waves
val waterWavesYPosition = (1 - waterPercentage) * size.height
val wavesPath = Path().apply {
moveTo(
x = 0f,
y = waterWavesYPosition
)
lineTo(
x = size.width,
y = waterWavesYPosition
)
lineTo(
x = size.width,
y = size.height
)
lineTo(
x = 0f,
y = size.height
)
close()
}
drawPath(
path = wavesPath,
color = waterWavesColor,
)
}
//Draw the bottle cap
drawRoundRect(
color = capColor,
size = Size(capWidth, capHeight),
topLeft = Offset(size.width / 2 - capWidth / 2f, 0f),
cornerRadius = CornerRadius(45f, 45f)
)
}
val text = buildAnnotatedString {
withStyle(
style = SpanStyle(
color = if (waterPercentage > 0.5f) Color.White else waterWavesColor,
fontSize = 44.sp
)
) {
append(usedWaterAmountAnimation.toString())
}
withStyle(
style = SpanStyle(
color = if (waterPercentage > 0.5f) Color.White else waterWavesColor,
fontSize = 22.sp
)
) {
append(" ")
append(unit)
}
}
Box(
modifier = Modifier
.fillMaxSize()
.fillMaxHeight(),
contentAlignment = Alignment.Center
) {
Text(text = text)
}
}
}
MainActivity
Copy this code →
package com.codingbihar.jetpackcomposeskill
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import com.codingbihar.jetpackcomposeskill.ui.theme.JetpackComposeSkillTheme
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
JetpackComposeSkillTheme {
WaterBottleUi()
}
}
}
}