지난 시간 팁 계산 만들기 1편
JETPACK COMPOSE: 팁 계산기 만들기 - 1
이제 아래 Main 부분을 만들어 봅시다!
아래 부분은 좀 복잡해 보이는데요. 일단은 간단하게 Border와 shape를 만들어 봅시다.
동일하게 Composable function인 MainContent()을 선언하고 Surface를 만듭니다.
Surface안에 modifier를 통해 padding과 모서리를 동그랗게 만들어주고
border를 그려주는데 BorderStroke를 사용합니다. 안에 width와 color를 정해주면 됩니다.
@Preview
@Composable
fun MainContent() {
Surface(
modifier = Modifier
.padding(2.dp)
.fillMaxWidth(),
shape = RoundedCornerShape(corner = CornerSize(8.dp)),
border = BorderStroke(width = 1.dp, color = Color.LightGray)
) {
Column() {
Text(text = "Hello again 1")
Text(text = "Hello again 2")
Text(text = "Hello again 3")
}
}
}
간단하게 Hello again이 차례로 출력되는 것을 볼 수 있습니다.
먼저 우리가 해야될 것은 Enter Bill에 숫자를 입력할 수 있게 해줘야 합니다.
Split과 Tip은 나중에 생각해 봅시다.
숫자를 입력 받으려면 OutlinedTextField를 이용하면 됩니다.
잘 모르는 분은 아래 글을 참고 부탁드립니다.
JETPACK COMPOSE: OutlinedTextField, FilledTextField에 대해 알아보자
그냥 바로 Text가 있던 부분에 OutlinedTextField를 작성해도 되지만 코드를 깔끔하게 하기 위해 코드를 나눠보려고 합니다.
components라는 package를 만들고 그 안에 Components.kt 파일을 만듭니다.
package com.example.jettipapp.components
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.AttachMoney
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
@Composable
fun InputField(
modifier: Modifier = Modifier,
valueState: MutableState<String>,
labelId: String,
enabled : Boolean,
isSingleLine: Boolean,
keyboardType: KeyboardType = KeyboardType.Number,
imeAction: ImeAction = ImeAction.Next,
onAction: KeyboardActions = KeyboardActions.Default
) {
OutlinedTextField(value = valueState.value,
onValueChange = { valueState.value = it },
label = { Text(text = labelId)},
leadingIcon = { Icon(imageVector = Icons.Rounded.AttachMoney,
contentDescription = "Money Icon")},
singleLine = isSingleLine,
textStyle = TextStyle(fontSize = 18.sp,
color = MaterialTheme.colors.onBackground),
modifier = modifier
.padding(bottom = 10.dp, start = 10.dp, end = 10.dp),
enabled = enabled,
keyboardOptions = KeyboardOptions(keyboardType = keyboardType,
imeAction = imeAction),
keyboardActions = onAction
)
}
package를 선언하고 안에 OutlinedTextField를 채워줍니다.
이렇게 많은 매개변수를 InputField로 부터 받는 이유는 이 함수를 재활용하기 위함입니다.
InputField에 원하는 값들을 넣어서 외부에서 OutlinedTextField를 만들고 싶을 때 재사용할 수 있게 됩니다.
함수 매개변수에 대해 살펴보면,
@Composable
fun InputField(
modifier: Modifier = Modifier,
valueState: MutableState<String>,
labelId: String,
enabled : Boolean,
isSingleLine: Boolean,
keyboardType: KeyboardType = KeyboardType.Number,
imeAction: ImeAction = ImeAction.Next,
onAction: KeyboardActions = KeyboardActions.Default
) {
- valueState는 현재 입력된 숫자 값을 넘기기 위한 변수입니다.
- isSingleLine은 한줄만 허용할 것인지 여러 줄을 허용할 것인지에 대한 변수입니다.
- keyboardType은 어떤 keyboardType을 사용할지에 대한 정보이고 KeyboardType.Number로 설정하면 숫자를 입력받습니다.
- imeAction은 키보드의 아래부분에 어떤 Action을 설정할지 결정하는 것이고 여기서는 Next로 설정해줍니다.
- keyboardActions은 Default로 설정합니다.
OutlinedTextField를 살펴봅시다.
OutlinedTextField(value = valueState.value,
onValueChange = { valueState.value = it },
label = { Text(text = labelId)},
leadingIcon = { Icon(imageVector = Icons.Rounded.AttachMoney,
contentDescription = "Money Icon")},
singleLine = isSingleLine,
textStyle = TextStyle(fontSize = 18.sp,
color = MaterialTheme.colors.onBackground),
modifier = modifier
.padding(bottom = 10.dp, start = 10.dp, end = 10.dp),
enabled = enabled,
keyboardOptions = KeyboardOptions(keyboardType = keyboardType,
imeAction = imeAction),
keyboardActions = onAction
)
- value는 valueState.value를 설정해주고 onValueChange는 callback으로 valueState.value를 새롭게 update 되는 값으로 변경해 줍니다.
- label에는 Text를 만들고 text안에 labelId를 넣어줍니다. (이것은 Enter Bill이 될 것입니다.)
- leadingIcon에는 $를 그리게 될 것인데 다음과 같이 작성해주면 달러 아이콘이 생성됩니다.
하지만 AttachMoney는 사실 기본 Icons.Rounded에는 없으므로 추가로 가져와야합니다.
build.gradle (Module: JetTipApp.app)에 들어가서 dependencies에 아래 부분에 //Icons 아래 코드를 추가해줍니다.
dependencies {
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.4.0'
implementation 'com.google.android.material:material:1.4.0'
implementation "androidx.compose.ui:ui:$compose_version"
implementation "androidx.compose.material:material:$compose_version"
implementation "androidx.compose.ui:ui-tooling-preview:$compose_version"
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.4.0'
implementation 'androidx.activity:activity-compose:1.4.0'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version"
debugImplementation "androidx.compose.ui:ui-tooling:$compose_version"
//Icons
implementation "androidx.compose.material:material-icons-extended:$compose_version"
}
//Icons 아래 부분을 추가하면 이제 Icons.Rounded.AttachMoney를 가져올 수 있게 됩니다.
OutlinedTextField(value = valueState.value,
onValueChange = { valueState.value = it },
label = { Text(text = labelId)},
leadingIcon = { Icon(imageVector = Icons.Rounded.AttachMoney,
contentDescription = "Money Icon")},
singleLine = isSingleLine,
textStyle = TextStyle(fontSize = 18.sp,
color = MaterialTheme.colors.onBackground),
modifier = modifier
.padding(bottom = 10.dp, start = 10.dp, end = 10.dp),
enabled = enabled,
keyboardOptions = KeyboardOptions(keyboardType = keyboardType,
imeAction = imeAction),
keyboardActions = onAction
)
- textStyle로 fontSize를 18.sp로 color를 MaterialTheme.colors.onBackground로 설정해 줍니다.
- enabled는 매개변수 enabled를 받아서 설정해주고, keyboardOptions로 받아온 매개변수를 넣어줍니다.
- keyboardActions도 마찬가지로 세팅해줍니다.
이제 다시 돌아와서 MainContent()를 채워줍니다.
@ExperimentalComposeUiApi
@Preview
@Composable
fun MainContent() {
val totalBillState = remember {
mutableStateOf("")
}
val validState = remember(totalBillState.value) {
totalBillState.value.trim().isNotEmpty()
}
val keyboardController = LocalSoftwareKeyboardController.current
Surface(
modifier = Modifier
.padding(2.dp)
.fillMaxWidth(),
shape = RoundedCornerShape(corner = CornerSize(8.dp)),
border = BorderStroke(width = 1.dp, color = Color.LightGray)
) {
Column() {
InputField(valueState = totalBillState,
labelId = "Enter Bill",
enabled = false,
isSingleLine = true,
onAction = KeyboardActions {
if(!validState) return@KeyboardActions
//Todo - onvaluechanged
keyboardController?.hide()
})
}
}
}
Column() 부분을 주목해봅시다. 아까 만들었던 InputField의 매개변수를 채워주는 과정입니다.
valueState를 위해 빈 문자열을 가지는 totalBillState를 생성합니다. labelId는 app에서 보이는 글자 "Enter Bill"을 넣어주고 enabled를 false로 (true가 되어야 입력을 받을 수 있습니다)
isSingleLine을 true로 하여 한줄만 받게 합니다.
onAction에 KeyboardActions를 넣는데 lambda 함수 안에 validState를 넣고 validState가 false이면 KeyboardActions을 return 하고 아니면 keyboardController?.hide()를 하여 keyboard를 숨깁니다.
keyboardController는 LocalsoftwareKeyboardController.current로 @ExperimentalComposeUiApi를 위에 추가해 주지 않으면 동작되지 않습니다.
validState는 totalBillState에 입력된 String이 Empty가 아닌지를 체크합니다.
이제 MyApp부분에서 TopHeader부분을 잠깐 주석처리하고 MainContent()를 추가하겠습니다.
@ExperimentalComposeUiApi
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MyApp {
//TopHeader()
MainContent()
}
}
}
}
아래 app처럼 드디어 텍스트를 입력할 수 있는 부분이 생겼습니다. $ 아이콘과 Enter Bill text도 추가된 것을 볼 수 있습니다.
그럼 이제 MainContent에서 돈을 입력받는 부분을 리펙토링 해봅시다.
아까 구현했던 MainContent에서 구현된 모든 부분을 가지고와서 BillForm에 붙여 넣기 해줍니다.
BillForm은 매개변수로 modifier와 onValChange 람다 함수를 받는데 이 람다 함수에 주목해야 합니다.
onAction에서 우리는 totalBillState의 value를 onValChange로 MainContent에 알려줄 수 있게 된 것입니다. (callback 같은 개념)
@ExperimentalComposeUiApi
@Composable
fun BillForm(modifier: Modifier = Modifier,
onValChange: (String) -> Unit = {},
) {
val totalBillState = remember {
mutableStateOf("")
}
val validState = remember(totalBillState.value) {
totalBillState.value.trim().isNotEmpty()
}
val keyboardController = LocalSoftwareKeyboardController.current
Surface(
modifier = Modifier
.padding(2.dp)
.fillMaxWidth(),
shape = RoundedCornerShape(corner = CornerSize(8.dp)),
border = BorderStroke(width = 1.dp, color = Color.LightGray)
) {
Column() {
InputField(valueState = totalBillState,
labelId = "Enter Bill",
enabled = true,
isSingleLine = true,
onAction = KeyboardActions {
if(!validState) return@KeyboardActions
onValChange(totalBillState.value.trim())
keyboardController?.hide()
})
}
}
}
totalBillState가 OutlinedTextField에 입력된 숫자가 변함에 따라 변하게 되는 것이고 이를 MainContent()에서 알 수 있게 됩니다.
BillForm에서 람다함수를 구현하여 billAmt로 값을 받고 log로 출력하는 코드입니다.
@ExperimentalComposeUiApi
@Preview
@Composable
fun MainContent() {
BillForm() { billAmt ->
Log.d("AMT", "MainContent: $billAmt")
}
}
아래와 같이 입력받은 값을 받고 아래 화살표를 누르면 값이 logcat에 찍히는 것을 확인할 수 있습니다.
2022-02-07 17:09:06.453 9418-9418/com.example.jettipapp D/AMT: MainContent: 123
2022-02-07 17:09:29.001 9418-9418/com.example.jettipapp D/AMT: MainContent: 1234
이렇게 람다함수를 잘 사용하는 것이 앱을 프로그래밍하는데 굉장히 중요한 역할을 합니다.
'Android > Jetpack Compose App' 카테고리의 다른 글
JETPACK COMPOSE: 영화 앱 만들기 - 1, Navigation Component, Scaffold, LazyColumn, Passing Data Between Screens (0) | 2022.02.18 |
---|---|
JETPACK COMPOSE: 팁 계산기 만들기 - 3 (0) | 2022.02.08 |
JETPACK COMPOSE: 팁 계산기 만들기 - 1 (0) | 2022.01.26 |
JETPACK COMPOSE: 터치하면 돈이 올라가는 앱을 만들어보자 - 3 (0) | 2022.01.21 |
JETPACK COMPOSE: 터치하면 돈이 올라가는 앱을 만들어보자 - 2 (0) | 2022.01.21 |
댓글