Naufal Khairul Rizky - 5025221127
Bottle Water UI - Pertemuan 10
Aplikasi ini merupakan implementasi tampilan visual pengisian air dalam botol menggunakan Jetpack Compose. Aplikasi ini menampilkan indikator botol air yang bisa diisi secara bertahap saat tombol ditekan, sebagai simulasi proses pengisian air secara interaktif.
1. MainActivity.kt
Saat aplikasi dijalankan, fungsi onCreate()
akan dipanggil:
-
enableEdgeToEdge()
digunakan agar tampilan aplikasi memenuhi seluruh layar perangkat tanpa batasan edge. -
setContent { ... }
memanggil fungsiBottleWaterApp()
sebagai UI utama yang akan ditampilkan. -
MaterialTheme
digunakan untuk menerapkan gaya desain Material 3 pada seluruh antarmuka aplikasi.
2. BottleWaterApp()
Fungsi @Composable
utama yang membangun antarmuka pengguna secara keseluruhan.
a. State Management
Menggunakan remember
dan mutableStateOf
untuk mengatur status:
-
usedWaterAmount
: Menyimpan jumlah air yang sudah terisi di dalam botol. Nilai ini akan meningkat setiap kali tombol ditekan. -
totalWaterAmount
: Menentukan kapasitas maksimum air dalam botol (misalnya 1000 ml). -
incrementStep
: Besarnya peningkatan air setiap kali tombol ditekan. -
animationDelay
: Waktu jeda antar peningkatan untuk membuat efek animasi halus saat mengisi. -
coroutineScope
: Digunakan untuk menjalankan coroutine saat tombol ditekan, denganrememberCoroutineScope()
.
b. Struktur Tata Letak UI
Menggunakan Column
untuk menyusun komponen secara vertikal dan rata tengah.
Komponen-Komponen:
-
Progress Bar
MenggunakanBox
danCanvas
untuk menggambarkan botol air:-
Tinggi dari air yang ditampilkan dihitung berdasarkan persentase dari
usedWaterAmount
terhadaptotalWaterAmount
. -
Tampilan air di dalam botol divisualisasikan sebagai
Color.Cyan
yang tumbuh dari bawah ke atas. -
Label di atas botol menampilkan jumlah air saat ini dalam satuan ml.
-
-
Tombol "Fill"
MenggunakanButton
untuk mengisi botol:-
Saat ditekan, coroutine akan dijalankan melalui
scope.launch { ... }
untuk menambah jumlah air secara bertahap dengandelay
. -
Efek animasi dicapai dengan menambah
usedWaterAmount
dalamstep
kecil (misalnya 20 ml) dengan jeda waktu tertentu (animationDelay
).
-
3. Logika Animasi Pengisian
-
launch
digunakan untuk menjalankan coroutine di dalamrememberCoroutineScope
. -
delay()
digunakan untuk menunda setiap langkah pengisian air agar terlihat seperti animasi bertahap. -
coerceAtMost(totalWaterAmount)
memastikan nilai tidak melebihi batas maksimum kapasitas botol.
MainActivity.kt:
package com.example.waterbottle
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import com.example.waterbottle.ui.theme.WaterBottleTheme
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.delay
import androidx.compose.runtime.rememberCoroutineScope
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
WaterBottleTheme {
MainScreen()
}
}
}
}
@Composable
fun MainScreen() {
var usedWaterAmount by remember { mutableStateOf(0) }
val totalWaterAmount = 2400
val scope = rememberCoroutineScope()
val incrementStep = 200
val animationDelay = 100L
Column(
modifier = Modifier
.fillMaxSize()
.background(Color(0xFF111111)),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
WaterBottle(
totalWaterAmount = totalWaterAmount,
unit = "ml",
usedWaterAmount = usedWaterAmount
)
Spacer(modifier = Modifier.height(24.dp))
Text(
text = "Total amount is: $totalWaterAmount",
color = Color.White
)
Spacer(modifier = Modifier.height(8.dp))
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
// Button Fill (gradual fill)
Button(
onClick = {
if (usedWaterAmount < totalWaterAmount) {
scope.launch {
val target = usedWaterAmount + incrementStep
for (i in usedWaterAmount + 20..target step 20) {
usedWaterAmount = i.coerceAtMost(totalWaterAmount)
delay(animationDelay)
}
}
}
},
colors = ButtonDefaults.buttonColors(containerColor = Color(0xFF4CAF50))
) {
Text("Fill", color = Color.White)
}
// Button Drink (gradual decrease)
Button(
onClick = {
if (usedWaterAmount > 0) {
scope.launch {
val target = usedWaterAmount - incrementStep
for (i in usedWaterAmount - 20 downTo target step 20) {
usedWaterAmount = i.coerceAtLeast(0)
delay(animationDelay)
}
}
}
},
colors = ButtonDefaults.buttonColors(containerColor = Color(0xFF6C4AB6))
) {
Text("Drink", color = Color.White)
}
// Button Drink All (instant reset)
Button(
onClick = { usedWaterAmount = 0 },
colors = ButtonDefaults.buttonColors(containerColor = Color.Red)
) {
Text("Drink All", color = Color.White)
}
}
}
}
package com.example.waterbottle
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.animateIntAsState
import androidx.compose.animation.core.tween
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.width
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.CornerRadius
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.clipPath
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
@Composable
fun WaterBottle(
modifier: Modifier = Modifier,
totalWaterAmount: Int,
unit: String,
usedWaterAmount: Int,
waterColor: Color = Color(0xff279eff),
bottleColor: Color = Color.White,
capColor: Color = Color(0xff0065b9)
) {
val waterPercentage by animateFloatAsState(
targetValue = usedWaterAmount.toFloat() / totalWaterAmount.toFloat(),
label = "Water Waves Animation",
animationSpec = tween(durationMillis = 1000)
)
val usedWaterAmountAnimation by animateIntAsState(
targetValue = usedWaterAmount,
label = "Used Water Amount Animation",
animationSpec = tween(durationMillis = 1000)
)
Box(
modifier = modifier
.width(200.dp)
.height(600.dp)
.background(Color(0xFF111111)) // Full dark background
) {
Canvas(modifier = Modifier.fillMaxSize()) {
val width = size.width
val height = size.height
val capWidth = size.width * 0.55f
val capHeight = size.height * 0.13f
// Bottle shape
val bottleBodyPath = Path().apply {
moveTo(x = width * 0.3f, y = height * 0.1f)
lineTo(x = width * 0.3f, y = height * 0.2f)
quadraticBezierTo(x1 = 0f, y1 = height * 0.3f, x2 = 0f, y2 = height * 0.4f)
lineTo(x = 0f, y = height * 0.95f)
quadraticBezierTo(x1 = 0f, y1 = height, x2 = width * 0.05f, y2 = height)
lineTo(x = width * 0.95f, y = height)
quadraticBezierTo(x1 = width, y1 = height, x2 = width, y2 = height * 0.95f)
lineTo(x = width, y = height * 0.4f)
quadraticBezierTo(x1 = width, y1 = height * 0.3f, x2 = width * 0.7f, y2 = height * 0.2f)
lineTo(x = width * 0.7f, y = height * 0.1f)
close()
}
// Draw bottle fill
clipPath(bottleBodyPath) {
drawRect(
color = bottleColor,
size = size,
)
val waterY = (1 - waterPercentage) * size.height
val waterPath = Path().apply {
moveTo(x = 0f, y = waterY)
lineTo(x = size.width, y = waterY)
lineTo(x = size.width, y = size.height)
lineTo(x = 0f, y = size.height)
close()
}
drawPath(
path = waterPath,
color = waterColor
)
}
// Cap
drawRoundRect(
color = capColor,
size = Size(capWidth, capHeight),
topLeft = Offset(size.width / 2 - capWidth / 2f, 0f),
cornerRadius = CornerRadius(45f, 45f)
)
// Removed bottle outline
}
// Centered water amount text
val text = buildAnnotatedString {
withStyle(
style = SpanStyle(
color = if (waterPercentage > 0.5f) bottleColor else waterColor,
fontSize = 44.sp
)
) {
append(usedWaterAmountAnimation.toString())
}
withStyle(
style = SpanStyle(
color = if (waterPercentage > 0.5f) bottleColor else waterColor,
fontSize = 22.sp
)
) {
append(" ")
append(unit)
}
}
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
Text(text = text)
}
}
}
Komentar
Posting Komentar