Android/Compose

[Compose]비만도 계산기

쏭식 2022. 9. 24. 23:59

계속 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으로 넘겨준거까지 체크하고 가면 좋을 것 같다!