Pemrograman Perangkat Bergerak - I
Material Design
Aisyah Nurhalimah - 5025201081
Material Design adalah sebuah gaya desain terbaru dari Google yang memiliki prinsip desain itu harus seperti sebuah kertas pada kenyataannya. Desain dengan Material Design pada prinsip nya bertujuan untuk memberikan fokus yang jelas kepada pengguna dengan menyediakan suatu struktur. Struktur disini lebih kepada menampilkan apa yang penting berada didepan daripada yang tidak penting. Pada pertemuan ini diminta untuk membuat sebuah aplikasi water bottle dengan mengikuti referensi ini.
Seperti pada tugas sebelumnya, buatlah projek baru dengan jenis Empty Activity, lalu beri nama yang sesuai. Pada bagian minimum SDK gunakan API 26 Oreo Android 8.0, lalu klik Finish. Selanjutnya ubah code pada file MainActivity.kt dan WaterBottle.kt.
Hasil:
Source code:
MainActivity.kt
package com.example.waterbottle | |
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.foundation.layout.width | |
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.Composable | |
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.tooling.preview.Preview | |
import androidx.compose.ui.unit.dp | |
import com.example.waterbottle.ui.theme.WaterBottleTheme | |
class MainActivity : ComponentActivity() { | |
override fun onCreate(savedInstanceState: Bundle?) { | |
super.onCreate(savedInstanceState) | |
setContent { | |
WaterBottleTheme { | |
// A surface container using the 'background' color from the theme | |
Surface( | |
modifier = Modifier.fillMaxSize(), | |
color = MaterialTheme.colorScheme.background | |
) { | |
var totalWaterAmount = remember{ | |
2400 | |
} | |
var usedAmount by remember { | |
mutableStateOf(400) | |
} | |
var incrementWaterAmount = 200 | |
var unitValue = "ml" | |
Column ( | |
modifier = Modifier.fillMaxSize(), | |
horizontalAlignment = Alignment.CenterHorizontally, | |
verticalArrangement = Arrangement.Center | |
){ | |
WaterBottle(totalWaterAmount = totalWaterAmount, unit = "", usedWaterAmount = usedAmount, modifier = Modifier.width(250.dp)) | |
Spacer(modifier = Modifier.height(20.dp)) | |
Text(text = "Total amount is: $totalWaterAmount $unitValue") | |
Row { | |
Button(onClick = { | |
if (usedAmount < totalWaterAmount){ | |
usedAmount += incrementWaterAmount | |
} | |
}, colors = ButtonDefaults.buttonColors(containerColor = Color(0xff279EFF)) | |
) { | |
Text(text = "Drink") | |
} | |
Spacer(modifier = Modifier.width(20.dp)) | |
Button(onClick = { usedAmount = 0 }, | |
colors = ButtonDefaults.buttonColors(containerColor = Color(0xff22649B)) | |
) | |
{ | |
Text(text = "Reset") | |
} | |
} | |
} | |
} | |
} | |
} | |
} | |
} |
WaterBottle.kt
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.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.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.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, | |
waterColor: Color = Color(0xFF6DC8F1), | |
bottleColor: Color = Color.LightGray, | |
capColor: Color = Color(0xFF425BFC) | |
) { | |
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 | |
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() | |
} | |
clipPath( | |
bottleBodyPath | |
) { | |
drawRect( | |
color = bottleColor, | |
size = size, | |
) | |
val waterWavesYPosition = (1 - waterPercentage) * size.height | |
val waterPath = 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 = waterPath, | |
color = waterColor | |
) | |
} | |
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) 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) | |
} | |
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 = 2400, | |
unit = "ml", | |
usedWaterAmount = 120, | |
) | |
} |