Water Bottle Filling Animation in Jetpack Compose

Water Bottle Filling Animation in Jetpack Compose

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()
              
              }
            }
        }
    }

OUTPUT: