계속 xml로만 안드로이드 개발을 해왔는데 이제부터 익숙해지려면 Compose로만 해야할 것 같았다..
그래서 간단히 이번 글에서는 xml이 아닌 compose로만 bmi 계산기를 만들어보자!
먼저 첫 번째 화면을 만들어보자.
첫 번째 화면에서의 앱의 가장 상단바를 Scaffold를 이용해서 만들 수 있다.
Scaffold(
topBar = {
TopAppBar(
title = { Text("비만도 계산기")}
)
}
)
위와 같이 title을 만들어주면 앱의 상단바가 완성된다.
입력할 수 있는 TextField 두 개, 버튼 한 개가 있는데, 여기서 그냥 TextField가 아닌, 입력할 때 hint값이 올라가는 OutLinedTextField를 이용해야 한다.
Column(
modifier = Modifier.padding(16.dp)
){
OutlinedTextField(
value = height,
onValueChange = setHeight,
label = { Text("키") },
modifier = Modifier.fillMaxWidth(),
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
)
OutlinedTextField(
value = weight,
onValueChange = setWeight,
label = { Text("몸무게") },
modifier = Modifier.fillMaxWidth(),
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
)
컬럼을 다음과 같이 만들어주고, TextField 두 개 만들어주었다. 숫자만 입력할 수 있게 해야하기 때문에 KeyboradType을 Number로 설정하였다.
Button(
onClick = {
if(height.isNotEmpty() && weight.isNotEmpty()){
onResultClicked(height.toDouble(), weight.toDouble())
}
},
modifier = Modifier.align(Alignment.End),
){
Text("결과")
}
다음은 버튼이다. 버튼이 클릭되었을 때 viewModel에서 계산 후, 두 번째 스크린에서 해당 값에 따른 결과를 나타내주어야 하기 때문에 콜백으로 다시 값들을 보내주어야한다. 따라서 다음과 같이 HomeScreen에서 콜백을 설정해주었다.
fun HomeScreen(
onResultClicked : (Double, Double) -> Unit,
){
val (height, setHeight) = rememberSaveable{
mutableStateOf("") //데이터가 날라가지 않게
}
val (weight, setWeight) = rememberSaveable{
mutableStateOf("")
}
또한, 텍스트필드에서 계속해서 컴포지션이 되면서 값이 초기화되지 않게끔 코틀린의 구조분해를 통해 해당 텍스트의 값을 heigth로, 그리고 변화될 때 마다 onValueChange를 각각의 setter로 설정하여 입력되게끔 하였다. 그리고 화면을 돌렸을 때 액티비티의 생명주기에 따라 값이 날라가게 되는데, 이를 방지하기 위하여 Saveable를 붙혀주어 값이 유지되게끔 하였다.
이제 홈화면을 완성했으니, 두 번째 화면으로 넘어가기전 viewModel에서 계산을 하고 넘어가야하니 viewModel부터 살펴보자.
class BmiViewModel : ViewModel(){
private val _bmi = mutableStateOf(0.0)
val bmi : State<Double> = _bmi
fun bmiCalculate(height : Double, weight : Double){
_bmi.value = weight / (height / 100.0).pow(2.0)
}
}
setContent {
val viewModel = viewModel<BmiViewModel>()
val navController = rememberNavController()
val bmi = viewModel.bmi.value
NavHost(navController = navController, startDestination = "home"){
composable(route = "home"){
HomeScreen(){ height, weight ->
viewModel.bmiCalculate(height, weight)
navController.navigate("result")
}
}
composable(route = "result"){
ResultScreen(navController, bmi)
}
}
}
setContent에서 NavHost에 navController를 통해 화면간의 이동을 설정해주고, startDestination으로 앱을 실행했을 때 처음 나오는 화면을 설정하였다.
첫 화면인 HomeScreen에서 콜백으로 받은 height, weight을 viewModel로 넘겨주어 계산을 한 후, bmi에 집어넣은 다음
navController.navigate(route)를 통해 다음화면인 ResultScreen으로 bmi를 넘겨주었다.
여기서 navController를 같이 넘겨준 이유는 뒤로가기버튼을 클릭하였을 때 popBackStack() 을 통해 첫 번째 화면으로 돌아가기 위함이다.
fun ResultScreen(
navController: NavController,
bmi : Double
){
val text = when{
bmi >= 35 -> "고도 비만"
bmi >= 30 -> "2단계 비만"
bmi >= 25 -> "1단계 비만"
bmi >= 23 -> "과체중"
bmi >= 18.5 -> "정상"
else -> "저체중"
}
두 번째 화면에서의 시작은 mbi에 따라 해당 텍스트가 변경되어야 하기 때문에 위와 같이 설정해주었다.
Scaffold (
topBar = {
TopAppBar(
title = { Text("비만도 계산기")},
navigationIcon = {
Icon(
imageVector = Icons.Default.ArrowBack,
contentDescription = "home",
modifier = Modifier.clickable {
navController.popBackStack()
}
)
}
)
}
)
이번에는 TopAppBar에 title만이 있는 것이 아니라 뒤로가기 Icon이 추가되었다. modifier를 통해 아이콘을 클릭하였을 때
생성자로 받은 navController로 뒤로가는 기능을 구현하였다.
나머지는 다음과 같이 위에서 bmi별로 설정한 text와 imgResource를 넣어주면 끝!!
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
){
Text(text, fontSize = 30.sp)
Spacer(modifier = Modifier.height(50.dp))
Image(painter = painterResource(id = imageRes),
contentDescription = null,
modifier = Modifier.size(100.dp),
colorFilter = ColorFilter.tint(
color = Color.Black
)
)
}
여태까지 공부한 내용을 합쳐서 만들어 보았다.
먼저, TextField에서 value값을 " "단순히 이런식으로 초기화해준다면 아무리 값이 변화한다고 하더라도, value값이 변하지 않기 때문에 입력해도 아무것도 표시되지 않을 것이다.
따라서 상태를 저장하고 변경되었을 때 재구성을 하기위해 관찰가능한 객체 MutableState를 사용하고, 이를 구조분해 형태로 사용하였다. 또한, 값이 초기화 되지 않게끔 Saveable까지 사용하였다!
또한, bmi계산을 위한 키와 몸무게 값을 콜백으로 넘겨주어 계산 후 두 번째 화면으로 navigation으로 넘겨준거까지 체크하고 가면 좋을 것 같다!
'Android > Compose' 카테고리의 다른 글
[Compose]스톱워치 (0) | 2022.09.29 |
---|---|
[Compose] ViewModel, State (1) | 2022.09.23 |
[Compose] Navigation (0) | 2022.09.22 |
[Compose] TextField, Scaffold, SnackBar (0) | 2022.09.21 |
[Compose] Image, Card, State (0) | 2022.09.15 |