본문 바로가기
Android/Jetpack Compose App

JETPACK COMPOSE: 팁 계산기 만들기 - 2

by 개발자J의일상 2022. 2. 8.
반응형

지난 시간 팁 계산 만들기 1편

JETPACK COMPOSE: 팁 계산기 만들기 - 1

 

JETPACK COMPOSE: 팁 계산기 만들기 - 1

앱을 만들기 전에 몇가지에 대해서 배워보겠습니다. 아래 코드는 jetpack으로 default Activity를 생성했을 때 만들어지는 코드입니다. class MainActivity : ComponentActivity() { override fun onCreate(saved..

mypark.tistory.com

 

이제 아래 Main 부분을 만들어 봅시다!

아래 부분은 좀 복잡해 보이는데요. 일단은 간단하게 Bordershape를 만들어 봅시다.

 

 

동일하게 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에 대해 알아보자

 

JETPACK COMPOSE: OutlinedTextField, FilledTextField에 대해 알아보자

Text Field는 사용자가 일반적으로 양식에서 텍스트 정보를 UI에 입력할 수 있도록 하는 UI 구성요소입니다. material design의 Text Field에는 Filled text fields와 Outlined text fileds의 두 가지 유형이 있습..

mypark.tistory.com

 

그냥 바로 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

 

이렇게 람다함수를 잘 사용하는 것이 앱을 프로그래밍하는데 굉장히 중요한 역할을 합니다.

300x250

댓글