Gradle(Module)import android.os.Bundle import android.util.Log import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.foundation.Image import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.* import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.runtime.Composable import androidx.compose.runtime.State import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.lifecycle.ViewModel import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavController import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController import kotlin.math.pow class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { val viewModel = viewModel<BmiViewModel>() // 화면 전환을 할 수 있는 객체 val navController = rememberNavController() // viewModel.bmiCalculate(height, weight)을 통해 계산 후에 리컴포즈 되면서 값을 갖게 됨 val bmi = viewModel.bmi.value // 화면 구성 NavHost(navController = navController, startDestination = "home") { composable(route = "home") { HomeScreen() { height, weight -> Log.d("콜백 여기!", "콜백해서 여기로 옴!"+height) viewModel.bmiCalculate(height, weight) navController.navigate("result") } } composable(route = "result") { ResultScreen( navController, bmi = bmi, ) } } } } } @Composable fun HomeScreen(onResultClicked: (Double, Double) -> Unit) { val (height, setHeight) = rememberSaveable { mutableStateOf("") } val (weight, setWeight) = rememberSaveable { mutableStateOf("") } Scaffold( topBar = { TopAppBar( title = { Text(text = "비만도 계산기") } ) } ) { Column( modifier = Modifier .padding(it) .padding(16.dp) ) { OutlinedTextField( value = height, onValueChange = setHeight, label = { Text(text = "키") }, modifier = Modifier.fillMaxWidth(), keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), ) OutlinedTextField( value = weight, onValueChange = setWeight, label = { Text(text = "몸무게") }, modifier = Modifier.fillMaxWidth(), keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), ) Spacer(modifier = Modifier.height(8.dp)) Button( onClick = { if (height.isNotEmpty() && weight.isNotEmpty()) { onResultClicked(height.toDouble(), weight.toDouble()) } }, modifier = Modifier.align(Alignment.End) ) { Text(text = "결과") } } } } @Composable fun ResultScreen( navController: NavController, bmi: Double, ) { val text = when { bmi >= 35 -> "고도 비만" bmi >= 30 -> "2단계 비만" bmi >= 25 -> "1단계 비만" bmi >= 23 -> "과체중" bmi >= 18.5 -> "정상" else -> "저체중" } val imageRes = when { bmi >= 23 -> R.drawable.baseline_sentiment_very_dissatisfied_24 bmi >= 18.5 -> R.drawable.baseline_sentiment_satisfied_24 else -> R.drawable.baseline_sentiment_dissatisfied_24 } Scaffold(topBar = { TopAppBar( title = { Text(text = "비만도 계산기") }, navigationIcon = { Icon( imageVector = Icons.Default.ArrowBack, contentDescription = "home", modifier = Modifier.clickable { navController.popBackStack() } ) } ) }) { Column( modifier = Modifier .padding(it) .fillMaxSize(), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally, ) { Text(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), ) } } } //@Preview //@Composable //fun Preview() { //// ResultScreen(bmi = 35.0) //} class BmiViewModel : ViewModel() { private val _bmi = mutableStateOf(0.0) // State의 값이 변경되었을 때 화면이 다시 그려짐 val bmi: State<Double> = _bmi // 외부에서 읽는 곳 fun bmiCalculate( height: Double, weight: Double ) { _bmi.value = weight / (height / 100.0).pow(2.0) } }
drawble// ViewModel implementation "androidx.lifecycle:lifecycle-viewmodel-compose:2.5.1" // Navigation implementation "androidx.navigation:navigation-compose:2.5.3"
baseline_sentiment_very_dissatisfied_24.xml
baseline_sentiment_satisfied_24.xml
baseline_sentiment_dissatisfied_24.xml
val (height, setHeight) = rememberSaveable {
mutableStateOf("")
}
val (weight, setWeight) = rememberSaveable {
mutableStateOf("")
}
구조분해를 이용하여 객체가 가지고 있는 여러 값을 분해해서 여러 변수에 한꺼번에 초기화해서 사용한다.AppBar
topBar = {
TopAppBar(
title = { Text(text = "비만도 계산기") }
)
}
AppBar를 컨트롤 하는 부분이다. title를 지정할 땐 꼭 대괄호로 해주어야된다.
OutlinedTextField(
value = height,
onValueChange = setHeight,
label = { Text(text = "키") },
modifier = Modifier.fillMaxWidth(),
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
)
맨 위의 구조분해로 적어준 것을 적용하여 텍스트 입력을 할 수 있게한다.keyboardOptions
부분은 숫자만 입력할 수 있도록 한 곳이다.Button(
onClick = {
if (height.isNotEmpty() && weight.isNotEmpty()) {
onResultClicked(height.toDouble(), weight.toDouble())
}
},
modifier = Modifier.align(Alignment.End)
) {
Text(text = "결과")
}
결과 버튼을 구현한 코드이다.HomeScreen(){height, weight->
viewModel.bmiCalculate(height, weight)
navController.navigate("result")
}
이 부분 작업을 진행하게 된다.class BmiViewModel : ViewModel() {
private val _bmi =mutableStateOf(0.0)
// State의 값이 변경되었을 때 화면이 다시 그려짐
val bmi: State<Double> = _bmi // 외부에서 읽는 곳
fun bmiCalculate(
height: Double,
weight: Double
) {
_bmi.value = weight / (height / 100.0).pow(2.0)
}
}
// viewModel.bmiCalculate(height, weight)을 통해 계산 후에 리컴포즈 되면서 값을 갖게 됨
val bmi = viewModel.bmi.value
위 콜백 함수를 통해 bmi 계산 함수(bmiCalculate
)가 실행되면 _bmi의 값은 변경되고 변경된 값은 State로 선언해준 bmi에 들어가게 된다.fun <T> mutableStateOf(
value: T,
policy: SnapshotMutationPolicy<T> =structuralEqualityPolicy()
): MutableState<T> =createSnapshotMutableState(value, policy)
mutableStateOf
부분을 뜯어보면 value와 policy를 받아 createSnapshotMutableState
을 통해SnapshotMutableState
를 생성하고 MutableState
인터페이스의 구현체가 된다.