Membuat Aplikasi Water Bottle Sederhana
PPB I - Tugas 7
Membuat Aplikasi Water Bottle Sederhana
Pada kesempatan kali ini, kita akan membahas tentang Material Design, sebuah panduan desain aplikasi perangkat lunak yang dibuat oleh Google, dengan fokus pada pembuatan aplikasi Water Bottle sederhana. Aplikasi ini memungkinkan pengguna untuk berinteraksi dengan minum melalui tombol Drink, dan total air yang diminum akan terlihat dalam botol dengan animasi yang menarik. Dalam pengembangan aplikasi ini, beberapa komponen komposabel digunakan, seperti Text, Button, dan Canvas, sesuai dengan prinsip-prinsip desain yang ditetapkan oleh Material Design.
Langkah pertama dalam proses ini adalah membuat proyek menggunakan activity kosong. Setelah membuat proyek, langkah selanjutnya adalah mengisi kolom Name dengan BottleWater, memilih level API minimum 26 (Oreo) di kolom Minimum SDK, dan kemudian mengklik tombol Finish untuk menyelesaikan proses pembuatan proyek. Selanjutnya, dibutuhkan pembuatan file WaterBottle.kt untuk menuliskan kode composable terkait antarmuka dan fungsionalitas botol air sesuai dengan spesifikasi yang diberikan.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.example.waterbottleapp | |
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.layout.Box | |
import androidx.compose.foundation.layout.fillMaxHeight | |
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.MutableState | |
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.Brush | |
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.tooling.preview.Preview | |
import androidx.compose.ui.unit.dp | |
import androidx.compose.ui.unit.sp | |
@Composable | |
fun WaterBottle( | |
modifier: Modifier = Modifier, | |
totalWaterAmount: Int, | |
unit: String, | |
usedWaterAmount: Int, | |
waterWavesColor: Color = Color(0xff325EFF), | |
bottleColor: Color = Color.White, | |
capStartColor: Color = Color(0xff47029B), | |
capEndColor: Color = Color(0xFF0065B9) | |
) { | |
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( | |
color = bottleColor, | |
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, | |
) | |
} | |
val capGradient = Brush.verticalGradient( | |
colors = listOf( | |
capStartColor, // Start color | |
capEndColor // End color | |
) | |
) | |
//Draw the bottle cap | |
drawRoundRect( | |
brush = capGradient, | |
size = Size(capWidth, capHeight), | |
topLeft = Offset(size.width / 2 - capWidth / 2f, 0f), | |
cornerRadius = CornerRadius(45f, 45f) | |
) | |
// Draw white lines on the cap | |
val lineWidth = capWidth * 0.97f // Adjust this factor as needed | |
val lineThickness = capHeight * 0.05f // Adjust thickness as needed | |
drawRect( | |
color = Color(0xFF243F55), | |
size = Size(lineWidth, lineThickness), | |
topLeft = Offset(size.width / 2 - lineWidth / 2f, capHeight * 0.30f) | |
) | |
drawRect( | |
color = Color(0xFF243F55), | |
size = Size(lineWidth, lineThickness), | |
topLeft = Offset(size.width / 2 - lineWidth / 2f, capHeight * 0.45f) | |
) | |
drawRect( | |
color = Color(0xFF243F55), | |
size = Size(lineWidth, lineThickness), | |
topLeft = Offset(size.width / 2 - lineWidth / 2f, capHeight * 0.60f) | |
) | |
drawRect( | |
color = Color(0xFF243F55), | |
size = Size(lineWidth, lineThickness), | |
topLeft = Offset(size.width / 2 - lineWidth / 2f, capHeight * 0.750f) | |
) | |
} | |
val text = buildAnnotatedString { | |
withStyle( | |
style = SpanStyle( | |
color = if (waterPercentage > 0.5f) bottleColor else waterWavesColor, | |
fontSize = 44.sp | |
) | |
) { | |
append(usedWaterAmountAnimation.toString()) | |
} | |
withStyle( | |
style = SpanStyle( | |
color = if (waterPercentage > 0.5f) bottleColor else waterWavesColor, | |
fontSize = 22.sp | |
) | |
) { | |
append(" ") | |
append(unit) | |
} | |
append("\n") | |
withStyle( | |
style = SpanStyle( | |
color = Color.Black, | |
fontSize = 16.sp | |
) | |
) { | |
append("Water Level: ${String.format("%.0f%%", waterPercentage * 100)}") | |
} | |
} | |
Box( | |
modifier = Modifier | |
.fillMaxSize() | |
.fillMaxHeight(), | |
contentAlignment = Alignment.Center | |
) { | |
Text(text = text) | |
} | |
} | |
} | |
@Preview | |
@Composable | |
fun WaterBottlePreview() { | |
WaterBottle( | |
totalWaterAmount = 2200, | |
unit = "ml", | |
usedWaterAmount = 100, | |
) | |
} |
Komponen WaterBottle menerima beberapa parameter yang penting untuk menentukan tampilan dan fungsionalitasnya. Parameter-parameter tersebut meliputi totalWaterAmount yang menunjukkan jumlah total air dalam botol, unit yang menentukan satuan untuk total air tersebut, dan usedWaterAmount yang mencatat jumlah air yang telah diminum. Selain itu, terdapat juga parameter opsional seperti waterColor yang mengatur warna air dalam botol, bottleColor yang menentukan warna botol, dan capColor yang mengatur warna penutup botol.
Di dalam komponen, kita menggunakan Canvas untuk menggambar botol air, sebuah elemen kunci dalam menciptakan tampilan visual yang akurat dan menarik. Selain itu, animasi juga dimasukkan ke dalam desain dengan mengatur efek animasi pada tingkat air yang berubah. Fungsi animateFloatAsState dan animateIntAsState digunakan untuk mengontrol animasi tersebut. Dalam proses penggambaran botol air, Path digunakan untuk menentukan bentuk dari botol air tersebut, sedangkan drawPath berfungsi untuk mengisi botol dengan air sesuai dengan jumlah yang telah ditentukan, dengan warna yang disesuaikan berdasarkan parameter waterColor yang telah diatur sebelumnya.
Selain menampilkan botol air dan mengatur animasi perubahannya, komponen juga menyertakan teks yang menampilkan jumlah air yang telah diminum. Pengaturan warna teks dan animasi perubahan jumlah air disesuaikan dengan tingkat air yang ada dalam botol. Dengan demikian, pengguna dapat dengan mudah melihat dan memantau seberapa banyak air yang telah diminum secara visual melalui tampilan komponen ini. Integrasi antara elemen-elemen ini memberikan pengalaman pengguna yang lebih menyeluruh dan menarik dalam menggunakan aplikasi Water Bottle.
Langkah berikutnya adalah memodifikasi file MainActivity.kt sebagai berikut.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.example.waterbottleapp | |
import android.os.Bundle | |
import androidx.activity.ComponentActivity | |
import androidx.activity.compose.setContent | |
import androidx.compose.foundation.layout.Arrangement | |
import androidx.compose.foundation.layout.Column | |
import androidx.compose.foundation.layout.Row | |
import androidx.compose.foundation.layout.Spacer | |
import androidx.compose.foundation.layout.fillMaxSize | |
import androidx.compose.foundation.layout.height | |
import androidx.compose.material3.Button | |
import androidx.compose.material3.ButtonDefaults | |
import androidx.compose.material3.MaterialTheme | |
import androidx.compose.material3.Surface | |
import androidx.compose.material3.Text | |
import androidx.compose.runtime.getValue | |
import androidx.compose.runtime.mutableStateOf | |
import androidx.compose.runtime.remember | |
import androidx.compose.runtime.setValue | |
import androidx.compose.ui.Alignment | |
import androidx.compose.ui.Modifier | |
import androidx.compose.ui.graphics.Color | |
import androidx.compose.ui.text.style.TextAlign | |
import androidx.compose.ui.unit.dp | |
import com.example.waterbottleapp.ui.theme.WaterBottleAppTheme | |
class MainActivity : ComponentActivity() { | |
override fun onCreate(savedInstanceState: Bundle?) { | |
super.onCreate(savedInstanceState) | |
setContent { | |
WaterBottleAppTheme { | |
// A surface container using the 'background' color from the theme | |
Surface( | |
modifier = Modifier.fillMaxSize(), | |
color = Color(0xff72849B) | |
) { | |
var usedWaterAmount by remember { | |
mutableStateOf(0) | |
} | |
val totalWaterAmount = remember { | |
2200 | |
} | |
var waterPercentages = remember { | |
mutableStateOf(0) | |
} | |
Column( | |
modifier = Modifier.fillMaxSize(), | |
horizontalAlignment = Alignment.CenterHorizontally, | |
verticalArrangement = Arrangement.Center | |
) { | |
WaterBottle( | |
totalWaterAmount = totalWaterAmount, | |
unit = "ml", | |
usedWaterAmount = usedWaterAmount, | |
) | |
Spacer(modifier = Modifier.height(20.dp)) | |
Text( | |
text = "Total Amount is : $totalWaterAmount", | |
textAlign = TextAlign.Center | |
) | |
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { | |
Button( | |
onClick = { usedWaterAmount += 200 }, | |
colors = ButtonDefaults.buttonColors( | |
containerColor = Color(0xff325EFF) | |
) | |
) { | |
Text(text = "Drink") | |
} | |
Button( | |
onClick = { usedWaterAmount = 0 }, | |
colors = ButtonDefaults.buttonColors( | |
containerColor = Color(0xff46049B) | |
) | |
) { | |
Text(text = "Reset") | |
} | |
} | |
} | |
} | |
} | |
} | |
} | |
} |
Column digunakan untuk menempatkan konten secara vertikal di tengah layar dengan presisi menggunakan alignment dan arrangement yang ditawarkan oleh Compose. Di dalam Column, terdapat komponen WaterBottle yang bertanggung jawab untuk menampilkan botol air dengan jumlah air yang sudah diminum. Data mengenai jumlah air yang sudah diminum dan total air dalam botol diingat (remember) menggunakan mutableIntStateOf dan remember untuk memastikan kekonsistenan tampilan antarmuka pengguna. Selanjutnya, di bawah WaterBottle, disisipkan sebuah teks yang menampilkan total air dalam botol. Dan akhirnya, terdapat sebuah tombol yang saat diklik akan menambah jumlah air yang sudah diminum sebanyak 200 ml, memberikan interaksi langsung kepada pengguna untuk mengelola asupan air mereka. Dengan demikian, struktur Column ini memberikan tata letak yang teratur dan intuitif bagi pengguna dalam menggunakan aplikasi Water Bottle.
Berikut tampilan WaterBottleApp yang telah dijalankan.
Komentar
Posting Komentar