맞춤 팁 계산하기

1. 시작하기 전에

이 Codelab에서는 Compose의 상태 소개 Codelab의 솔루션 코드를 사용하여, 청구 금액과 팁 비율을 입력할 때 자동으로 팁 금액을 계산하여 반올림할 수 있는 대화형 팁 계산기를 빌드합니다. 최종 앱은 다음 이미지와 같이 표시됩니다.

24370de6d667a700.png

기본 요건

  • Jetpack Compose에서 상태 사용 Codelab
  • 앱에 TextTextField 컴포저블을 추가하는 능력
  • remember 함수, 상태, 상태 호이스팅, 구성 가능한 스테이트풀(Stateful) 및 스테이트리스(Stateless) 함수의 차이점에 관한 지식

학습할 내용

  • 가상 키보드에 작업 버튼을 추가하는 방법
  • 키보드 작업을 설정하는 방법
  • Switch 컴포저블의 정의 및 사용 방법
  • Layout Inspector 정의

빌드할 항목

  • 사용자가 입력한 서비스 비용과 팁 비율을 기반으로 팁 금액을 계산하는 Tip Time 앱

필요한 항목

2. 시작 앱 개요

이 Codelab은 이전 Codelab의 Tip Time 앱으로 시작합니다. 이 앱은 고정 팁 비율로 팁을 계산하는 데 필요한 사용자 인터페이스를 제공합니다. Cost of Service 텍스트 상자를 통해 사용자가 서비스 비용을 입력할 수 있습니다. 앱은 팁 금액을 계산하여 Text 컴포저블에 표시합니다.

시작 코드 가져오기

시작하려면 시작 코드를 다운로드하세요.

또는 코드에 관한 GitHub 저장소를 클론해도 됩니다.

$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-tip-calculator.git
$ cd basic-android-kotlin-compose-training-tip-calculator
$ git checkout state

Tip Calculator GitHub 저장소에서 코드를 찾아볼 수 있습니다.

Tip Time 앱 실행

  1. Android 스튜디오에서 Tip Time 프로젝트를 열고 에뮬레이터 또는 기기에서 앱을 실행합니다.
  2. 서비스 비용을 입력합니다. 앱이 자동으로 팁 금액을 계산하여 표시합니다.

761df483de663721.png

현재 구현에서는 팁 비율이 15%로 하드코딩되어 있습니다. 이 Codelab에서는 앱이 맞춤 팁 비율을 계산하여 팁 금액을 반올림할 수 있는 텍스트 필드로 이 기능을 확장합니다.

필요한 문자열 리소스 추가

  1. Project 탭에서 res > values > strings.xml을 클릭합니다.
  2. strings.xml 파일의 <resources> 태그 사이에 다음 문자열 리소스를 추가합니다.
<string name="how_was_the_service">Tip (%)</string>
<string name="round_up_tip">Round up tip?</string>

strings.xml 파일은 다음 코드 스니펫과 같이 표시되며 여기에는 이전 Codelab의 문자열이 포함되어 있습니다.

strings.xml

<resources>
   <string name="app_name">TipTime</string>
   <string name="calculate_tip">Calculate Tip</string>
   <string name="cost_of_service">Cost of Service</string>
   <string name="how_was_the_service">Tip (%)</string>
   <string name="round_up_tip">Round up tip?</string>
   <string name="tip_amount">Tip Amount: %s</string>
</resources>
  1. Cost Of Service 문자열을 Bill Amount 문자열로 변경합니다. 일부 국가에서는 서비스을 의미하므로 이 변경을 통해 혼란을 방지할 수 있습니다.
  2. Cost of Service 문자열에서 속성의 name cost_of_service를 마우스 오른쪽 버튼으로 클릭하고 Refactor > Rename을 선택합니다. Rename 대화상자가 열립니다.

a2f301b95a8c0e3f.png

  1. Rename 대화상자에서 cost_of _servicebill_amount로 바꾸고 Refactor를 클릭합니다. 이렇게 하면 프로젝트의 cost_of_service 문자열 리소스 항목이 모두 업데이트되므로 Compose 코드를 수동으로 변경할 필요가 없습니다.

f525a371c2851d08.png

  1. strings.xml​​ 파일에서 문자열 값을 Cost of Service에서 Bill Amount로 변경합니다.
<string name="bill_amount">Bill Amount</string>
  1. MainActivity.kt 파일로 이동한 후 앱을 실행합니다. 다음 이미지와 같이 텍스트 상자의 라벨이 업데이트되었습니다.

Cost of Service 대신 Bill Amount가 표시된 텍스트 필드

3. 팁 비율 텍스트 필드 추가

고객은 제공된 서비스의 품질과 기타 여러 이유를 근거로 팁을 많이 주거나 적게 주려고 할 수 있습니다. 이를 위해 앱에서는 사용자가 맞춤 팁을 계산할 수 있도록 해야 합니다. 이 섹션에서는 다음 이미지와 같이 사용자가 맞춤 팁 비율을 입력하는 텍스트 필드를 추가합니다.

47b5e8543e5eb754.png

앱에는 이미 구성 가능한 스테이트리스(Stateless) EditNumberField() 함수인 Bill Amount 텍스트 필드 컴포저블이 있습니다. 이전 Codelab에서는 amountInput 상태를 EditNumberField() 컴포저블에서 TipTimeScreen() 함수로 끌어올려 EditNumberField() 컴포저블을 스테이트리스(Stateless)로 만들었습니다.

텍스트 필드를 추가하려면 동일한 EditNumberField() 컴포저블을 재사용하되 다른 라벨을 사용하면 됩니다. 이렇게 변경하려면 구성 가능한 EditNumberField() 함수 내에서 라벨을 하드코딩하는 대신 매개변수로 라벨을 전달해야 합니다.

구성 가능한 EditNumberField() 함수를 재사용 가능하도록 설정하려면 다음 안내를 따르세요.

  1. MainActivity.kt 파일에서 구성 가능한 EditNumberField() 함수의 매개변수에 Int 유형의 label 문자열 리소스를 추가합니다.
@Composable
fun EditNumberField(
   label: Int,
   value: String,
   onValueChange: (String) -> Unit
) 
  1. Modifier 유형의 modifier 인수를 구성 가능한 EditNumberField() 함수에 추가합니다.
@Composable
fun EditNumberField(
   label: Int,
   value: String,
   onValueChange: (String) -> Unit,
   modifier: Modifier = Modifier
) 
  1. 함수 본문에서 하드코딩된 문자열 리소스 ID를 label 매개변수로 바꿉니다.
@Composable
fun EditNumberField(
   //...
) {
   TextField(
       //...
       label = { Text(stringResource(label)) },
       //...
   )
}
  1. label 매개변수가 문자열 리소스 참조여야 함을 나타내려면 함수 매개변수에 @StringRes 주석을 추가합니다.
@Composable
fun EditNumberField(
   @StringRes label: Int,
   value: String,
   onValueChange: (String) -> Unit,
   modifier: Modifier = Modifier
) 
  1. 다음을 가져옵니다.
import androidx.annotation.StringRes
  1. EditNumberField() 함수의 TextField 컴포저블에서 label 매개변수를 stringResource() 함수에 전달합니다.
@Composable
fun EditNumberField(
   @StringRes label: Int,
   value: String,
   onValueChange: (String) -> Unit,
   modifier: Modifier = Modifier
) {
   TextField(
       //...
       label = { Text(stringResource(label)) },
       //...
   )
} 
  1. TipTimeScreen() 함수의 EditNumberField() 함수 호출에서 label 매개변수를 R.string.bill_amount 문자열 리소스로 설정합니다.
EditNumberField(
   label = R.string.bill_amount,
   value = amountInput,
   onValueChange = { amountInput = it }
)
  1. Design 창에서 Build & Refresh 2d40b921003ab5eb.png를 클릭합니다. 앱의 UI가 다음 이미지와 같이 표시됩니다.

a84cd50c50235a9f.png

  1. TipTimeScreen() 함수에서 EditNumberField() 함수 호출 뒤에 맞춤 팁 비율에 사용할 텍스트 필드를 하나 더 추가합니다. 다음 매개변수를 사용하여 구성 가능한 EditNumberField() 함수를 호출합니다.
EditNumberField(
   label = R.string.how_was_the_service,
   value = "",
   onValueChange = { }
)

이렇게 하면 맞춤 팁 비율용 텍스트 상자가 추가됩니다.

  1. Design 창에서 Build & Refresh 2d40b921003ab5eb.png를 클릭합니다. 이제 다음 이미지와 같이 앱 미리보기에 Tip (%) 텍스트 필드가 표시됩니다.

9d2c01d577d077ae.png

  1. TipTimeScreen() 함수 상단에 추가된 텍스트 필드의 상태 변수에 사용할 tipInput이라는 var 속성을 추가합니다. mutableStateOf("")를 사용하여 변수를 초기화하고 remember 함수로 호출을 둘러쌉니다.
var tipInput by remember { mutableStateOf("") }
  1. EditNumberField() 함수 호출에서 이름이 value인 매개변수를 tipInput 변수로 설정하고 onValueChange 람다 표현식에서 tipInput 변수를 업데이트합니다.
EditNumberField(
   label = R.string.how_was_the_service,
   value = tipInput,
   onValueChange = { tipInput = it }
)
  1. TipTimeScreen() 함수에서 tipInput 변수 정의 뒤에 tipInput 변수를 Double 유형으로 변환하는 tipPercent라는 val 변수를 정의하고, elvis 연산자를 사용하여 값이 null인 경우 0.0을 반환합니다.
val tipPercent = tipInput.toDoubleOrNull() ?: 0.0
  1. TipTimeScreen() 함수에서 calculateTip() 함수 호출을 업데이트하고 tipPercent 변수를 두 번째 매개변수로 전달합니다.
val tip = calculateTip(amount, tipPercent)

이제 TipTimeScreen() 함수의 코드가 다음 코드 스니펫과 같이 표시됩니다.

@Composable
fun TipTimeScreen() {
   var amountInput by remember { mutableStateOf("") }
   var tipInput by remember { mutableStateOf("") }

   val tipPercent = tipInput.toDoubleOrNull() ?: 0.0
   val amount = amountInput.toDoubleOrNull() ?: 0.0
   val tip = calculateTip(amount, tipPercent)

   Column(
       modifier = Modifier.padding(32.dp),
       verticalArrangement = Arrangement.spacedBy(8.dp)
   ) {
       Text(
           text = stringResource(R.string.calculate_tip),
           fontSize = 24.sp,
           modifier = Modifier.align(Alignment.CenterHorizontally)
       )
       Spacer(Modifier.height(16.dp))
       EditNumberField(
           label = R.string.bill_amount,
           value = amountInput,
           onValueChange = { amountInput = it }
       )
       EditNumberField(
           label = R.string.how_was_the_service,
           value = tipInput,
           onValueChange = { tipInput = it }
       )
       Spacer(Modifier.height(24.dp))
       Text(
           text = stringResource(R.string.tip_amount, tip),
           modifier = Modifier.align(Alignment.CenterHorizontally),
           fontSize = 20.sp,
           fontWeight = FontWeight.Bold
       )
   }
}
  1. 에뮬레이터나 기기에서 앱을 실행하고 청구 금액과 팁 비율을 입력합니다. 앱에서 팁 금액을 올바르게 계산하나요?

bdc482b015472300.png

4. 작업 버튼 설정

이전 Codelab에서는 KeyboardOptions 클래스를 사용하여 키보드 유형을 설정하는 방법을 알아봤습니다. 이 섹션에서는 동일한 KeyboardOptions로 키보드 작업 버튼을 설정하는 방법을 알아봅니다. 키보드 작업 버튼은 키보드 끝에 있는 버튼입니다. 다음 표에서 몇 가지 예시를 확인할 수 있습니다.

속성

키보드의 작업 버튼

ImeAction.Search 사용자가 검색을 실행하려고 할 때 사용됩니다.

ImeAction.Send 사용자가 입력란에 텍스트를 보내려고 할 때 사용됩니다.

ImeAction.Go 사용자가 입력한 텍스트 대상으로 이동하려고 할 때 사용됩니다.

이 작업에서는 텍스트 상자에 다음 두 가지 작업 버튼을 설정합니다.

  • Bill Amount 텍스트 상자의 Next 작업 버튼: 사용자가 현재 입력을 완료했고 다음 텍스트 상자로 이동하려고 함을 나타냅니다.
  • Tip % 텍스트 상자의 Done 작업 버튼: 사용자가 입력을 완료했음을 나타냅니다.

다음 이미지에서 이러한 작업 버튼이 있는 키보드의 예시를 확인할 수 있습니다.

키보드 옵션을 추가합니다.

  1. EditNumberField() 함수의 TextField() 함수 호출에서 KeyboardOptions 생성자에 ImeAction.Next 값으로 설정된 imeAction이라는 인수를 전달합니다. KeyboardOptions.Default.copy 함수를 사용하여 대문자 사용 및 자동 수정과 같은 다른 기본 옵션을 사용합니다.
@Composable
fun EditNumberField(
   //...
) {
   TextField(
       //...
       keyboardOptions = KeyboardOptions.Default.copy(
           keyboardType = KeyboardType.Number,
           imeAction = ImeAction.Next
       )
   )
}
  1. 에뮬레이터나 기기에서 앱을 실행합니다. 다음 이미지와 같이 이제 키보드에 Next 작업 버튼이 표시됩니다.

그러나 텍스트 필드에는 작업 버튼 두 개가 있어야 합니다. 이 문제는 곧 수정합니다.

  1. EditNumberField() 함수를 검사합니다. TextField() 함수의 keyboardOptions 매개변수가 하드코딩됩니다. 텍스트 필드에 다른 작업 버튼을 만들려면 KeyboardOptions 객체를 인수로 전달해야 하며 이 작업은 다음 단계에서 실행합니다.
// No need to copy, just examine the code.
fun EditNumberField(
   @StringRes label: Int,
   value: String,
   onValueChange: (String) -> Unit
) {
   TextField(
       //...
       keyboardOptions = KeyboardOptions.Default.copy(
          keyboardType = KeyboardType.Number,
          imeAction = ImeAction.Next
       )
   )
}
  1. EditNumberField() 함수 정의에서 KeyboardOptions 유형의 keyboardOptions 매개변수를 추가합니다. 함수 본문에서 TextField() 함수의 keyboardOptions라는 매개변수에 할당합니다.
@Composable
fun EditNumberField(
   @StringRes label: Int,
   keyboardOptions: KeyboardOptions,
   value: String,
   onValueChange: (String) -> Unit
){
   TextField(
       //...
       keyboardOptions = keyboardOptions
   )
}
  1. TipTimeScreen() 함수에서 첫 번째 EditNumberField() 함수 호출을 업데이트하고 Bill Amount 텍스트 필드의 keyboardOptions라는 매개변수를 전달합니다.
EditNumberField(
   label = R.string.bill_amount,
   keyboardOptions = KeyboardOptions(
       keyboardType = KeyboardType.Number,
       imeAction = ImeAction.Next
   ),
   value = amountInput,
   onValueChange = { amountInput = it }
)
  1. 두 번째 EditNumberField() 함수 호출에서 Tip % 텍스트 필드의 imeActionImeAction.Done으로 변경합니다. 함수는 다음 코드 스니펫과 같이 표시됩니다.
EditNumberField(
   label = R.string.how_was_the_service,
   keyboardOptions = KeyboardOptions(
       keyboardType = KeyboardType.Number,
       imeAction = ImeAction.Done
   ),
   value = tipInput,
   onValueChange = { tipInput = it }
)
  1. 앱을 실행합니다. NextDone 작업 버튼이 다음 이미지와 같이 표시됩니다.

  1. 청구 금액을 입력하고 Next 작업 버튼을 클릭한 후 팁 비율을 입력하고 Done 작업 버튼을 클릭합니다. 아무 일도 일어나지 않습니다. 아직 버튼에 기능을 추가하지 않았기 때문입니다. 이 작업은 다음 섹션에서 실행합니다.

5. 키보드 작업 설정

이 섹션에서는 KeyboardActions 클래스를 통해 포커스를 다음 텍스트 필드로 옮기고 키보드를 닫는 기능을 구현하여 사용자 환경을 개선합니다. 이를 통해 개발자는 소프트웨어 키보드에서의 사용자 입력 방식 편집기(IME) 작업에 응답하여 트리거되는 작업을 지정할 수 있습니다. IME 작업의 예시는 사용자가 Next 또는 Done 작업 버튼을 클릭하는 경우입니다.

다음을 구현합니다.

  • Next 작업: 다음 텍스트 필드(Tip % 텍스트 상자)로 포커스를 이동합니다.
  • Done 작업: 가상 키보드를 닫습니다.
  1. EditNumberField() 함수에서 focusManager라는 val 변수를 추가하고 LocalFocusManager.current 속성 값을 할당합니다.
val focusManager = LocalFocusManager.current

LocalFocusManager 인터페이스는 Compose에서 포커스를 제어하는 데 사용합니다. 이 변수를 사용하여 텍스트 상자로 포커스를 이동하고 텍스트 상자에서 포커스를 지웁니다.

  1. import androidx.compose.ui.platform.LocalFocusManager를 가져옵니다.
  2. EditNumberField() 함수 서명에서 KeyboardActions 유형의 또 다른 keyboardActions 매개변수를 추가합니다.
@Composable
fun EditNumberField(
   @StringRes label: Int,
   keyboardOptions: KeyboardOptions,
   keyboardActions: KeyboardActions,
   value: String,
   onValueChange: (String) -> Unit
) {
   //...
}
  1. EditNumberField() 함수 본문에서 TextField() 함수 호출을 업데이트하고 keyboardActions 매개변수를 전달된 keyboardActions 매개변수로 설정합니다.
@Composable
fun EditNumberField(
   //...
) {
   TextField(
       //...
       keyboardActions = keyboardActions
   )
}

이제 각 작업 버튼의 다른 기능으로 텍스트 필드를 맞춤설정할 수 있습니다.

  1. TipTimeScreen() 함수 호출에서 keyboardActions라는 매개변수를 새 인수로 포함하도록 첫 번째 EditNumberField() 함수 호출을 업데이트합니다. KeyboardActions( onNext = { } ) 값을 할당합니다.
// Bill amount text field
EditNumberField(
   //...
   keyboardActions = KeyboardActions(
       onNext = { }
   ),
   //...
)

onNext라는 매개변수의 람다 표현식은 사용자가 키보드에서 Next 작업 버튼을 누를 때 실행됩니다.

  1. 람다를 정의하고 FocusManager에 포커스를 다음 컴포저블인 Tip %로 아래로 이동하도록 요청합니다. 람다 표현식의 focusManager 객체에서 moveFocus() 함수를 호출하고 FocusDirection.Down 인수를 전달합니다.
// Bill amount text field
EditNumberField(
   label = R.string.bill_amount,
   keyboardOptions = KeyboardOptions(
       keyboardType = KeyboardType.Number,
       imeAction = ImeAction.Next
   ),
   keyboardActions = KeyboardActions(
       onNext = { focusManager.moveFocus(FocusDirection.Down) }
   ),
   value = amountInput,
   onValueChange = { amountInput = it }
)

moveFocus() 함수는 지정된 방향으로 포커스를 이동합니다. 여기서는 Tip % 텍스트 필드로 아래로 이동합니다.

  1. 다음을 가져옵니다.
import androidx.compose.ui.focus.FocusDirection
  1. Tip % 텍스트 필드에도 비슷한 구현을 추가합니다. 차이점은 onNext 대신 onDone이라는 매개변수를 정의해야 한다는 점입니다.
// Tip% text field
EditNumberField(
   //...
   keyboardActions = KeyboardActions(
       onDone = { }
   ),
   //...
)
  1. 사용자가 맞춤 팁을 입력한 후에는 키보드의 Done 작업에서 포커스가 지워져야 합니다. 그러면 키보드가 닫힙니다. 람다를 정의하고 FocusManager에 포커스를 지우도록 요청합니다. 람다 표현식의 focusManager 객체에서 clearFocus() 함수를 호출합니다.
EditNumberField(
   label = R.string.how_was_the_service,
   keyboardOptions = KeyboardOptions(
       keyboardType = KeyboardType.Number,
       imeAction = ImeAction.Done
   ),
   keyboardActions = KeyboardActions(
       onDone = { focusManager.clearFocus() }),
   value = tipInput,
   onValueChange = { tipInput = it }
)

clearFocus() 함수는 포커스가 있는 구성요소에서 포커스를 삭제합니다.

  1. 앱을 실행합니다. 이제 키보드 작업이 다음 GIF와 같이 포커스가 있는 구성요소를 변경합니다.

3164e7a2f39a2d7b.gif

6. 스위치 추가

스위치는 단일 항목의 상태를 켜거나 끕니다. 전환 스위치에는 두 가지 상태가 있으며 사용자는 두 옵션 중에서 선택할 수 있습니다. 전환 스위치는 다음 이미지와 같이 thumb과 트랙으로 구성됩니다.

1. Thumb
2. 트랙

스위치는 다음 이미지와 같이 결정을 입력하거나 설정과 같은 환경설정을 선언하는 데 사용할 수 있는 선택 컨트롤입니다.

a90c4e22e48b30e0.png

사용자는 thumb을 앞뒤로 드래그하여 옵션을 선택하거나 간단히 스위치를 탭하여 전환할 수 있습니다. 다음 GIF에서 보여주는 또 다른 전환 스위치 예시에서는 시각적 옵션 설정이 어두운 모드로 전환됩니다.

91b7bd7a6e02e5ff.gif

스위치에 관한 자세한 내용은 스위치 문서를 참고하세요.

Switch 컴포저블을 사용하면 다음 이미지와 같이 사용자가 팁을 가장 가까운 정수로 반올림할지 선택할 수 있습니다.

cf89a61484296bab.png

TextSwitch 컴포저블의 행을 추가합니다.

  1. EditNumberField() 함수 뒤에 구성 가능한 RoundTheTipRow() 함수를 추가한 다음 기본 ModifierEditNumberField() 함수와 비슷한 인수로 전달합니다.
@Composable
fun RoundTheTipRow(modifier: Modifier = Modifier) {
}
  1. RoundTheTipRow() 함수를 구현하고 다음 modifier를 사용하여 Row 레이아웃 컴포저블을 추가하여 하위 요소의 너비를 화면에 최대로 설정하고 정렬을 가운데로 설정하고 크기가 48 dp가 되도록 합니다.
Row(
   modifier = Modifier
       .fillMaxWidth()
       .size(48.dp),
   verticalAlignment = Alignment.CenterVertically
) {
}
  1. 다음을 가져옵니다.
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Size
  1. Row 레이아웃 컴포저블의 람다 블록에서 R.string.round_up_tip 문자열 리소스를 사용하여 Round up tip? 문자열을 표시하는 Text 컴포저블을 추가합니다.
Text(text = stringResource(R.string.round_up_tip))
  1. Text 컴포저블 뒤에 Switch 컴포저블을 추가하고 checked라는 매개변수를 전달한 후 roundUp으로 설정하고 onCheckedChange라는 매개변수를 전달하고 onRoundUpChanged로 설정합니다.
Switch(
    checked = roundUp,
    onCheckedChange = onRoundUpChanged,
)

다음 표에는 이러한 매개변수에 관한 정보가 포함되어 있으며 RoundTheTipRow() 함수에 정의한 매개변수와 같습니다.

매개변수

설명

checked

스위치 선택 여부를 나타냅니다. Switch 컴포저블의 상태입니다.

onCheckedChange

스위치를 클릭할 때 호출될 콜백입니다.

  1. 다음을 가져옵니다.
import androidx.compose.material.Switch
  1. RoundTipRow() 함수에서 Boolean 유형의 roundUp 매개변수와 Boolean을 사용하고 아무것도 반환하지 않는 onRoundUpChanged 람다 함수를 추가합니다.
@Composable
fun RoundTheTipRow(
   roundUp: Boolean,
   onRoundUpChanged: (Boolean) -> Unit,
   modifier: Modifier = Modifier
)

그러면 스위치의 상태를 끌어올립니다.

  1. Switch 컴포저블에서 이 modifier를 추가하여 Switch 컴포저블을 화면 끝에 맞춥니다.
       Switch(
           modifier = modifier
               .fillMaxWidth()
               .wrapContentWidth(Alignment.End),
           //...
       )
  1. 다음을 가져옵니다.
import androidx.compose.foundation.layout.wrapContentWidth
  1. TipTimeScreen() 함수에서 Switch 컴포저블의 상태에 관한 var 변수를 추가합니다. roundUp이라는 var 변수를 만들고 mutableStateOf()로 설정합니다. 기본 인수는 false입니다. remember { }로 호출을 둘러쌉니다.
fun TipTimeScreen() {
   //...
   var roundUp by remember { mutableStateOf(false) }

   //...
   Column(
       ...
   ) {
     //...
  }
}

이는 Switch 컴포저블 상태의 변수이며 false가 기본 상태가 됩니다.

  1. TipTimeScreen() 함수의 Column 블록에서 Tip % 텍스트 필드 뒤에 다음 인수를 사용하여 RoundTheTipRow() 함수를 호출합니다. roundUp으로 설정된 roundUp이라는 매개변수, roundUp 값을 업데이트하는 람다 콜백으로 설정된 onRoundUpChanged라는 매개변수
@Composable
fun TipTimeScreen() {
   //...

   Column(
       ...
   ) {
       Text(
           ...
       )
       Spacer(...)
       EditNumberField(
           ...
       )
       EditNumberField(
           ...
       )
       RoundTheTipRow(roundUp = roundUp, onRoundUpChanged = { roundUp = it })
       Spacer(...)
       Text(
           ...
       )
   }
}

그러면 Round up tip 행이 표시됩니다.

  1. 앱을 실행합니다. 앱에 Round up tip? 전환 스위치가 표시되지만 다음 이미지와 같이 전환 스위치의 thumb이 거의 보이지 않습니다.

선택 해제된 스위치와 선택된 스위치. 번호는 두 가지 요소와 상태를 식별합니다.1. Thumb
2. 트랙

다음 단계에서는 thumb을 진한 회색으로 변경하여 가시성을 향상합니다.

  1. RoundTheTipRow() 함수의 Switch() 컴포저블에서 colors라는 매개변수를 추가합니다.
  2. colors라는 매개변수를 Color.DarkGray 인수로 설정된 uncheckedThumbColor라는 매개변수를 허용하는 SwitchDefaults.colors() 함수로 설정합니다.
Switch(
   //...
   colors = SwitchDefaults.colors(
       uncheckedThumbColor = Color.DarkGray
   )
)
  1. 다음을 가져옵니다.
import androidx.compose.material.SwitchDefaults
import androidx.compose.ui.graphics.Color

이제 구성 가능한 RoundTheTipRow() 함수가 다음 코드 스니펫과 같이 표시됩니다.

@Composable
fun RoundTheTipRow(roundUp: Boolean, onRoundUpChanged: (Boolean) -> Unit) {
   Row(
       modifier = Modifier
           .fillMaxWidth()
           .size(48.dp),
       verticalAlignment = Alignment.CenterVertically
   ) {
       Text(stringResource(R.string.round_up_tip))
       Switch(
           modifier = Modifier
               .fillMaxWidth()
               .wrapContentWidth(Alignment.End),
           checked = roundUp,
           onCheckedChange = onRoundUpChanged,
           colors = SwitchDefaults.colors(
               uncheckedThumbColor = Color.DarkGray
           )
       )
   }
}
  1. 앱을 실행합니다. 스위치의 thumb 색상이 다음 이미지와 같이 다르게 표시됩니다.

24370de6d667a700.png

  1. 청구 금액과 팁 비율을 입력한 다음 Round up tip? 전환 스위치를 선택합니다. 팁 금액은 반올림되지 않습니다. 여전히 calculateTip() 함수를 업데이트해야 하기 때문입니다. 다음 섹션에서 알아봅니다.

calculateTip() 함수를 업데이트하여 팁 반올림하기

Boolean 변수를 허용하여 팁을 가장 가까운 정수로 반올림하도록 calculateTip() 함수를 수정합니다.

  1. 팁을 반올림하려면 calculateTip() 함수가 Boolean인 스위치의 상태를 알아야 합니다. calculateTip() 함수에서 Boolean 유형의 roundUp 매개변수를 추가합니다.
private fun calculateTip(
   amount: Double,
   tipPercent: Double = 15.0,
   roundUp: Boolean
): String {
   //...
}
  1. calculateTip() 함수에서 return 문 앞에 roundUp 값을 확인하는 if() 조건을 추가합니다. roundUptrue이면 tip 변수를 정의하고 kotlin.math.ceil() 함수로 설정한 후 tip 함수를 인수로 전달합니다.
if (roundUp)
   tip = kotlin.math.ceil(tip)

완성된 calculateTip() 함수는 다음 코드 스니펫과 같습니다.

private fun calculateTip(amount: Double, tipPercent: Double = 15.0, roundUp: Boolean): String {
   var tip = tipPercent / 100 * amount
   if (roundUp)
       tip = kotlin.math.ceil(tip)
   return NumberFormat.getCurrencyInstance().format(tip)
}
  1. TipTimeScreen() 함수에서 calculateTip() 함수 호출을 업데이트하고 roundUp 매개변수를 전달합니다.
val tip = calculateTip(amount, tipPercent, roundUp)
  1. 앱을 실행합니다. 이제 다음 이미지와 같이 팁 금액이 반올림됩니다.

7. 솔루션 코드 가져오기

완료된 Codelab의 코드를 다운로드하려면 이 git 명령어를 사용하면 됩니다.

$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-tip-calculator.git

또는 ZIP 파일로 저장소를 다운로드한 다음 압축을 풀고 Android 스튜디오에서 열어도 됩니다.

솔루션 코드를 보려면 GitHub에서 확인하세요.

8. 결론

축하합니다. Tip Time 앱에 맞춤 팁 기능을 추가했습니다. 이제 사용자가 앱에서 맞춤 팁 비율을 입력하고 팁 금액을 반올림할 수 있습니다. #AndroidBasics를 사용해 소셜 미디어에서 작업을 공유하세요.

자세히 알아보기