Best Tutorial for Android Jetpack Compose

Android App Development

Stay ahead with the latest tools, trends, and best practices in Android development

Water Bottle Filling Animation in Jetpack Compose

Water Bottle Filling Animation in Jetpack Compose - Coding Bihar
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:

  1. Open Android Studio.
  2. Start a new Android Studio project.
  3. Choose "Empty Compose Activity" as the template.
  4. 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:

Special Message

Welcome to Coding