연습: 클릭 동작

1. 시작하기 전에

본 개발자 과정에서는 앱에 버튼을 추가하는 방법과 앱이 버튼 클릭에 응답하도록 수정하는 방법을 배웠습니다. 이제 앱을 빌드하여 지금까지 배운 내용을 연습해 볼 차례입니다.

연습에서는 Lemonade 앱을 만듭니다. 먼저 Lemonade 앱의 요구사항을 읽고 앱의 모습과 작동 방식을 알아보세요. 한 단계 더 나아가고 싶다면 거기서부터 자신만의 앱을 만들어 볼 수 있습니다. 잘 모르겠는 부분이 있다면 이어지는 섹션에서 문제를 단계별로 나눈 다음 한 단계씩 해결하는 방법에 관한 힌트와 가이드를 읽어 보세요.

자신에게 맞는 속도로 연습 문제를 진행하세요. 앱 기능의 각 부분을 빌드하는 데 필요한 만큼 충분한 시간을 할애하시기 바랍니다. 본 연습의 끝부분에 Lemonade 앱의 솔루션 코드가 나와 있습니다. 솔루션을 확인하기 전에 직접 앱을 빌드해 보시기 바랍니다. 제공된 솔루션은 Lemonade 앱을 빌드하는 유일한 방법은 아니므로 앱 요구사항을 충족하는 한 다른 방법으로 빌드해도 좋습니다.

기본 요건

  • Compose에서 텍스트 및 이미지 컴포저블을 사용하여 간단한 UI 레이아웃을 만들 수 있음
  • 버튼 클릭에 응답하는 대화형 앱을 빌드할 수 있음
  • 컴포지션 및 리컴포지션에 관한 기본 이해
  • 함수, 변수, 조건문, 람다와 같은 Kotlin 프로그래밍 언어에 대한 기본 지식

필요한 항목

  • 인터넷 액세스가 가능하고 Android 스튜디오가 설치된 컴퓨터

2. 앱 개요

디지털 레모네이드를 만든다는 비전을 실현할 수 있도록 도와주세요! 목표는 화면의 이미지를 탭하면 레몬을 압착하여 레모네이드 한 잔을 만드는 간단한 대화형 앱을 만드는 것입니다. 여기서 레모네이드란 은유라고 생각해도 좋고, 시간을 보내는 재미있는 방법이라고 생각해도 좋아요.

7531970ea087859f.png

앱을 사용하는 방법은 다음과 같습니다.

  1. 사용자가 처음으로 앱을 실행하면 레몬 나무가 표시됩니다. 레몬 나무 이미지를 탭하여 나무에서 레몬을 '선택'하라고 안내하는 텍스트가 있습니다.
  2. 사용자가 레몬 나무를 탭하면 레몬이 표시됩니다. 레몬을 탭해서 '압착'하여 레모네이드를 만들라고 안내하는 텍스트가 있습니다. 레몬을 압착하려면 여러 번 탭해야 합니다. 레몬을 압착하는 데 필요한 탭 횟수는 매번 2와 4 사이의 무작위로 생성된 숫자로 설정됩니다.
  3. 필요한 횟수만큼 레몬을 탭하면 상쾌한 레모네이드 한 잔이 표시됩니다. 잔을 탭하여 레모네이드를 '마시라고' 안내하는 텍스트가 표시됩니다.
  4. 레모네이드 잔을 탭하면 빈 잔이 표시됩니다. 다시 시작하려면 빈 잔을 탭하라고 안내하는 메시지가 표시됩니다.
  5. 빈 잔을 탭하면 다시 레몬 나무가 표시되고 처음부터 다시 시작할 수 있습니다. 레모네이드를 더 만들어 보세요!

다음은 앱을 사용하는 방법을 보여주는 큰 스크린샷입니다.

레모네이드 만들기의 각 단계마다 화면에 서로 다른 이미지와 텍스트 라벨이 표시되고 앱이 클릭에 응답하는 방식이 다릅니다. 예를 들어 사용자가 레몬 나무를 탭하면 앱에 레몬이 표시됩니다.

여러분이 할 일은 앱의 UI 레이아웃을 빌드하고 사용자가 레모네이드 만들기의 모든 단계를 진행하기 위한 로직을 구현하는 것입니다.

3. 시작하기

프로젝트 만들기

Android 스튜디오에서 다음 정보를 사용하여 Empty Activity 템플릿으로 새 프로젝트를 만듭니다.

  • Name: Lemonade
  • Package name: com.example.lemonade
  • Minimum SDK: 24

앱이 성공적으로 생성되고 프로젝트가 빌드되면 다음 섹션으로 진행합니다.

이미지 추가하기

Lemonade 앱에 사용할 4개의 벡터 드로어블 파일이 제공됩니다.

다음과 같이 파일을 가져옵니다.

  1. 앱의 이미지 ZIP 파일을 다운로드합니다.
  2. ZIP 파일을 더블클릭합니다. 그러면 이미지가 폴더에 압축 해제됩니다.
  3. 이미지를 앱의 drawable 폴더에 추가합니다. 방법을 잘 모르겠으면 양방향 Dice Roller 앱 만들기 Codelab을 참고하세요.

프로젝트 폴더는 아래 스크린샷과 같습니다. lemon_drink.xml, lemon_restart.xml, lemon_squeeze.xml, lemon_tree.xml 애셋이 res > drawable 디렉터리 아래에 나타납니다.

최상위 폴더가 res 폴더인 폴더 계층 구조. res 폴더에는 drawable 폴더가 있습니다. drawable 폴더에는 ic_launcher_lemonade_background.xml, ic_launcher_lemonade_foreground.xml, lemon_drink.xml, lemon_restart.xml, lemon_squeeze.xml, lemon_tree.xml 이렇게 6개 파일의 목록이 세로 방향으로 나열되어 있습니다.

  1. 벡터 드로어블 파일을 하나 더블클릭하여 이미지를 미리 봅니다.
  2. (Code 뷰나 Split 뷰가 아니라) Design 창을 선택하여 이미지를 전체 너비로 봅니다.

Android 스튜디오에서 lemon_drink.xml 드로어블 파일이 선택된 Project 창. Design 창에서 레모네이드가 든 커다란 잔 이미지인 드로어블 파일의 미리보기가 열려 있습니다.

앱에 이미지 파일을 추가했으면 코드에서 이미지 파일을 참조할 수 있습니다. 예를 들어, 벡터 드로어블 파일의 이름이 lemon_tree.xml이라면 Kotlin 코드에서 R.drawable.lemon_tree와 같은 형식으로 리소스 ID를 사용하여 드로어블을 참조할 수 있습니다.

문자열 리소스 추가하기

res > values > strings.xml 파일에서 프로젝트에 다음 문자열을 추가합니다.

  • Tap the lemon tree to select a lemon
  • Keep tapping the lemon to squeeze it
  • Tap the lemonade to drink it
  • Tap the empty glass to start again

프로젝트에는 아래의 문자열도 필요합니다. 이들 문자열은 사용자 인터페이스 화면에는 표시되지 않지만, 앱에서 이미지가 무엇인지 설명하는 이미지 콘텐츠 설명에 사용됩니다. 앱의 strings.xml 파일에 아래의 문자열을 추가합니다.

  • Lemon tree
  • Lemon
  • Glass of lemonade
  • Empty glass

앱에서 문자열 리소스를 선언하는 방법을 잘 모르겠으면 양방향 Dice Roller 앱 만들기 Codelab 또는 문자열을 참고하세요. 각 문자열 리소스에 문자열 리소스가 포함하는 값을 설명하는 적절한 식별자 이름을 지정합니다. 예를 들어, 문자열 "Lemon"의 경우 strings.xml 파일에서 식별자 이름 lemon_content_description을 사용하여 선언하고 코드에서 리소스 ID R.string.lemon_content_description을 사용하여 참조할 수 있습니다.

레모네이드 만들기의 각 단계

이제 앱을 구현하는 데 필요한 문자열 리소스와 이미지 애셋이 준비되었습니다. 아래에서 앱의 각 단계와 화면에 표시되는 내용을 알아보세요.

1단계:

  • 텍스트: Tap the lemon tree to select a lemon
  • 이미지: 레몬 나무(lemon_tree.xml)

9504828af9af42b.png

2단계:

  • 텍스트: Keep tapping the lemon to squeeze it
  • 이미지: 레몬(lemon_squeeze.xml)

8700874ce7068619.png

3단계:

  • 텍스트: Tap the lemonade to drink it
  • 이미지: 레모네이드 한 잔(lemon_drink.xml)

6d7476e99ff21441.png

4단계:

  • 텍스트: Tap the empty glass to start again
  • 이미지: 빈 잔(lemon_restart.xml)

3ce421faec0c0187.png

시각 콘텐츠 다듬기

여러분이 만든 앱이 최종 스크린샷처럼 보이도록 하려면 앱에서 몇 가지 시각 콘텐츠를 다듬어야 합니다.

  • 텍스트의 크기가 기본 글꼴 크기보다 크도록 글꼴 크기를 키웁니다(18sp).
  • 텍스트와 이미지 사이에 적당한 간격이 있도록 텍스트 라벨과 이미지 사이에 간격을 추가합니다(16dp).
  • 사용자가 이미지를 탭할 수 있음을 알 수 있도록 버튼에 강조 색상과 약간 둥근 모서리를 적용합니다.

한 단계 더 나아가고 싶다면 앱 사용 방법에 관한 설명에 따라 앱의 나머지 부분을 빌드해 보세요. 자세한 안내를 받고 싶다면 다음 섹션으로 넘어가세요.

4. 앱 빌드 방법 계획

앱을 빌드할 때는 일단 최소한으로 작동하는 버전의 앱을 만드는 것으로 시작하는 것이 좋습니다. 그런 다음 원하는 기능이 모두 적용될 때까지 조금씩 기능을 추가하세요. 먼저 빌드할 수 있는 작은 규모의 엔드 투 엔드 기능을 찾아보세요.

Lemonade 앱의 중요한 부문은 하나의 단계에서 또 다른 이미지와 텍스트 라벨을 갖는 다른 단계로 전환하는 것입니다. 압착이라는 기능은 앱의 기초를 먼저 만든 후 나중에 추가할 수 있으니 처음에는 이 동작을 무시합니다.

아래에서는 앱을 빌드할 때 사용하면 좋을 단계를 개략적으로 설명합니다.

  1. 레모네이드 만들기 1단계의 UI 레이아웃을 빌드합니다. 이 단계에서는 사용자에게 나무에서 레몬을 선택하라는 메시지가 표시됩니다. 이미지를 둘러싸는 테두리는 나중에 추가할 수 있는 시각 콘텐츠이니 여기서는 일단 건너뜁니다.

9504828af9af42b.png

  1. 사용자가 레몬 나무를 탭하면 레몬 이미지와 텍스트 라벨이 표시되도록 앱의 동작을 구현합니다. 이로써 레모네이드 만들기의 1단계와 2단계가 어느 정도 구현됩니다.

7f964f2605a2f36c.png

  1. 매번 이미지를 탭할 때마다 레모네이드 만들기의 나머지 단계가 표시되도록 코드를 추가합니다. 이 시점에서는 레몬을 한 번 탭하는 것만으로 레모네이드가 담긴 잔이 표시되도록 합니다.

녹색 테두리로 둘러싸인 네 개의 상자가 가로로 나열되어 있습니다. 각 상자에는 1부터 4 사이의 숫자가 들어 있습니다. 상자 1번에서 출발하여 상자 2번에서 끝나는 화살표, 상자 2번에서 출발하여 상자 3번에서 끝나는 화살표, 상자 3번에서 출발하여 상자 4번에서 끝나는 화살표, 상자 4번에서 출발하여 상자 1번에서 끝나는 화살표가 있습니다. 1번 상자 아래에는 'Tap the lemon tree to select a lemon'이라는 텍스트 라벨과 레몬 나무 이미지가 있습니다. 2번 상자 아래에는 'Keep tapping the lemon to squeeze it'이라는 텍스트 라벨과 레몬 이미지가 있습니다. 3번 상자 아래에는 'Tap the lemonade to drink it'이라는 텍스트 라벨과 레모네이드 한 잔 이미지가 있습니다. 4번 상자 아래에는 'Tap the empty glass to start again'이라는 텍스트 라벨과 빈 잔 이미지가 있습니다.

  1. 사용자가 2와 4 사이의 무작위로 생성된 숫자만큼 레몬을 '압착'(탭)해야 하도록 레몬 압착 단계를 위한 커스텀 동작을 추가합니다.

녹색 테두리로 둘러싸인 네 개의 상자가 가로로 나열되어 있습니다. 각 상자에는 1부터 4 사이의 숫자가 들어 있습니다. 상자 1번에서 출발하여 상자 2번에서 끝나는 화살표, 상자 2번에서 출발하여 상자 3번에서 끝나는 화살표, 상자 3번에서 출발하여 상자 4번에서 끝나는 화살표, 상자 4번에서 출발하여 상자 1번에서 끝나는 화살표가 있습니다. 2번 상자에서 출발하여 같은 상자에서 끝나는 화살표가 있고, 여기에 'Random number of times'라는 라벨이 있습니다. 1번 상자 아래에는 레몬 나무 이미지와 텍스트 라벨이 있습니다. 2번 상자 아래에는 레몬 이미지와 텍스트 라벨이 있습니다. 3번 상자 아래에는 레모네이드가 든 잔 이미지와 텍스트 라벨이 있습니다. 4번 상자 아래에는 빈 잔 이미지와 텍스트 라벨이 있습니다.

  1. 필요한 만큼 시각 콘텐츠를 다듬어서 앱을 마무리합니다. 예를 들어, 글꼴 크기를 변경하고 이미지 주변에 테두리를 추가하여 앱에 깔끔한 느낌을 줍니다. Kotlin 코딩 스타일 가이드라인을 준수하고 코드에 주석을 추가하는 등 바람직한 코딩 방식을 사용하세요.

위에서 설명한 개략적인 가이드를 참고하여 Lemonade 앱을 구현할 수 있다면 직접 앱을 빌드하세요. 위에서 설명한 5가지 단계에 관한 상세한 가이드가 필요하다면 다음 섹션으로 넘어가세요.

5. 앱 구현

UI 레이아웃 빌드하기

먼저 앱이 화면 중앙에 레몬 나무 이미지와 텍스트 라벨(Tap the lemon tree to select a lemon)을 표시하도록 앱을 수정합니다. 텍스트와 텍스트 아래의 이미지 사이에 16dp의 간격도 추가해야 합니다.

9504828af9af42b.png

필요하다면 MainActivity.kt 파일에서 아래의 시작 코드를 사용하세요.

package com.example.lemonade

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import com.example.lemonade.ui.theme.LemonadeTheme

class MainActivity : ComponentActivity() {
   override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)
       setContent {
           LemonadeTheme {
               LemonApp()
           }
       }
   }
}

@Composable
fun LemonApp() {
   // A surface container using the 'background' color from the theme
   Surface(
       modifier = Modifier.fillMaxSize(),
       color = MaterialTheme.colorScheme.background
   ) {
       Text(text = "Hello there!")
   }
}

@Preview(showBackground = true)
@Composable
fun DefaultPreview() {
   LemonadeTheme {
       LemonApp()
   }
}

이 코드는 Android 스튜디오에서 자동으로 생성된 코드와 비슷합니다. 단, Greeting() 컴포저블 대신 LemonApp() 컴포저블이 정의되어 있고 이 컴포저블은 매개변수를 받지 않습니다. DefaultPreview() 컴포저블 또한 개발자가 코드를 쉽게 미리 볼 수 있게끔 LemonApp() 컴포저블을 사용하도록 업데이트되어 있습니다.

Android 스튜디오에 이 코드를 입력한 다음 LemonApp() 컴포저블이 앱의 콘텐츠를 포함하도록 수정하세요. 사고 과정을 전개하는 데 도움을 줄 아래의 질문을 참고하세요.

  • 어떤 컴포저블을 사용할 건가요?
  • 여러 컴포저블을 원하는 위치로 구성하는 데 도움이 될 만한 표준 Compose 레이아웃 구성요소가 있나요?

앱이 실행되면 레몬 나무 이미지와 텍스트 라벨이 표시되도록 이 단계를 구현합니다. Android 스튜디오에서 컴포저블 미리보기를 통해 코드를 수정하는 과정에서 UI가 어떻게 보이는지 확인하세요. 앱을 실행하여 이 섹션의 앞부분에서 본 스크린샷과 똑같은 화면이 표시되는지 확인합니다.

작업을 마친 후, 이미지를 탭했을 때의 동작을 추가하는 방법을 알아보려면 다시 이 부분으로 돌아오세요.

클릭 동작 추가하기

다음으로, 사용자가 레몬 나무 이미지를 탭하면 레몬 이미지와 텍스트 라벨 Keep tapping the lemon to squeeze it이 표시되도록 코드를 추가합니다. 즉, 레몬 나무를 탭하면 텍스트와 이미지가 변경됩니다.

7f964f2605a2f36c.png

본 개발자 과정의 앞부분에서 클릭 가능한 버튼을 만드는 방법을 배웠습니다. Lemonade 앱에는 Button 컴포저블이 없지만, 컴포저블에서 clickable 수정자를 지정하면 버튼뿐 아니라 어떤 컴포저블도 클릭 가능하도록 만들 수 있습니다. 예시를 보려면 clickable 문서 페이지를 참고하세요.

이미지를 클릭하면 어떤 일이 일어나야 하나요? 이 동작을 구현하는 코드는 그리 간단하지 않으니, 이 시점에서 잠시 우리에게 익숙한 앱을 살펴보겠습니다.

Dice Roller 앱 살펴보기

Dice Roller 앱의 코드를 다시 살펴보며 주사위 굴리기의 결과 값에 따라 앱이 서로 다른 주사위 이미지를 표시하는 방식을 알아봅니다.

Dice Roller 앱의 MainActivity.kt

...

@Composable
fun DiceWithButtonAndImage(modifier: Modifier = Modifier) {
   var result by remember { mutableStateOf(1) }
   val imageResource = when(result) {
       1 -> R.drawable.dice_1
       2 -> R.drawable.dice_2
       3 -> R.drawable.dice_3
       4 -> R.drawable.dice_4
       5 -> R.drawable.dice_5
       else -> R.drawable.dice_6
   }
   Column(modifier = modifier, horizontalAlignment = Alignment.CenterHorizontally) {
       Image(painter = painterResource(id = imageResource), contentDescription = result.toString())
       Button(onClick = { result = (1..6).random() }) {
          Text(stringResource(id = R.string.roll))
       }
   }
}

...

Dice Roller 앱 코드에 관한 다음 질문에 답하세요.

  • 어떤 변수의 값으로 표시할 주사위 이미지가 정해지나요?
  • 이 변수가 변경되도록 트리거하는 사용자의 동작은 무엇인가요?

DiceWithButtonAndImage() 구성 가능한 함수는 가장 최근 주사위 굴리기의 값을 result 변수에 저장합니다. 이 변수는 다음 코드 줄에서 remember 컴포저블의 mutableStateOf() 함수에 정의되어 있습니다.

var result by remember { mutableStateOf(1) }

result 변수가 새 값으로 업데이트되면 Compose가 DiceWithButtonAndImage() 컴포저블의 리컴포지션을 트리거합니다. 즉, 컴포저블이 다시 실행됩니다. result 값은 리컴포지션이 반복되어도 계속 기억되므로 DiceWithButtonAndImage() 컴포저블이 다시 실행될 때 가장 최근의 result 값이 사용됩니다. 컴포저블은 result 변수의 값을 대상으로 when 문을 사용하여 표시할 새 드로어블 리소스 ID와 이를 표시할 Image 컴포저블을 확인합니다.

배운 내용을 Lemonade 앱에 적용하기

이번에는 Lemonade 앱에 관한 비슷한 질문에 답해 보세요.

  • 화면에 표시할 텍스트와 이미지를 확인하는 데 사용할 수 있는 변수가 있나요? 코드에서 이 변수를 정의하세요.
  • 앱이 이 변수의 값에 따라 서로 다른 동작을 실행하도록 Kotlin의 조건문을 사용할 수 있나요? 그렇다면 코드에서 이 조건문을 작성하세요.
  • 이 변수가 변경되도록 트리거하는 사용자의 동작은 무엇인가요? 코드에서 사용자의 동작으로 변수의 변경이 트리거될 적절한 위치를 찾은 다음 거기에 코드를 추가하여 변수를 업데이트하세요.

이 섹션은 구현하기가 까다로울 수 있으며 올바르게 작동하려면 코드의 여러 부분을 변경해야 합니다. 앱이 한 번에 예상대로 작동하지 않는다고 해서 낙담하지 마세요. 이 동작은 여러 가지 방법으로 올바르게 구현할 수 있다는 사실을 기억하세요.

여기까지 마쳤으면 앱을 실행하여 올바르게 작동하는지 확인합니다. 앱을 실행하면 레몬 나무 이미지와 텍스트 라벨이 표시되어야 합니다. 레몬 나무 이미지를 한 번 탭하면 텍스트 라벨이 업데이트되고 레몬 이미지가 표시되어야 합니다. 지금으로서는 레몬 이미지를 탭해도 아무것도 변경되지 않습니다.

나머지 단계 추가하기

이제 여러분의 앱에 레모네이드 만들기의 1단계와 2단계가 표시됩니다! 이 시점에서는 LemonApp() 컴포저블이 아래의 코드 스니펫과 비슷할 것입니다. 앱의 동작이 동일하다면 코드가 약간 다르더라도 괜찮습니다.

MainActivity.kt

...
@Composable
fun LemonApp() {
   // Current step the app is displaying (remember allows the state to be retained
   // across recompositions).
   var currentStep by remember { mutableStateOf(1) }

   // A surface container using the 'background' color from the theme
   Surface(
       modifier = Modifier.fillMaxSize(),
       color = MaterialTheme.colorScheme.background
   ) {
       when (currentStep) {
           1 -> {
               Column (
                   horizontalAlignment = Alignment.CenterHorizontally,
                   verticalArrangement = Arrangement.Center,
                   modifier = Modifier.fillMaxSize()
               ){
                   Text(text = stringResource(R.string.lemon_select))
                   Spacer(modifier = Modifier.height(32.dp))
                   Image(
                       painter = painterResource(R.drawable.lemon_tree),
                       contentDescription = stringResource(R.string.lemon_tree_content_description),
                       modifier = Modifier
                           .wrapContentSize()
                           .clickable {
                               currentStep = 2
                           }
                   )
               }
           }
           2 -> {
               Column (
                   horizontalAlignment = Alignment.CenterHorizontally,
                   verticalArrangement = Arrangement.Center,
                   modifier = Modifier.fillMaxSize()
               ){
                   Text(text = stringResource(R.string.lemon_squeeze))
                   Spacer(modifier = Modifier.height(32
                       .dp))
                   Image(
                       painter = painterResource(R.drawable.lemon_squeeze),
                       contentDescription = stringResource(R.string.lemon_content_description),
                       modifier = Modifier.wrapContentSize()
                   )
               }
           }
       }
   }
}
...

다음으로, 레모네이드 만들기의 나머지 단계를 추가합니다. 이미지를 한 번 탭하면 사용자가 레모네이드 만들기의 다음 단계로 전환되며 텍스트와 이미지가 둘 다 업데이트되어야 합니다. 코드가 앱의 처음 두 단계만이 아니라 모든 단계를 처리할 수 있도록 코드를 더 유연하게 변경해야 합니다.

녹색 테두리로 둘러싸인 네 개의 상자가 가로로 나열되어 있습니다. 각 상자에는 1부터 4 사이의 숫자가 들어 있습니다. 상자 1번에서 출발하여 상자 2번에서 끝나는 화살표, 상자 2번에서 출발하여 상자 3번에서 끝나는 화살표, 상자 3번에서 출발하여 상자 4번에서 끝나는 화살표, 상자 4번에서 출발하여 상자 1번에서 끝나는 화살표가 있습니다. 1번 상자 아래에는 'Tap the lemon tree to select a lemon'이라는 텍스트 라벨과 레몬 나무 이미지가 있습니다. 2번 상자 아래에는 'Keep tapping the lemon to squeeze it'이라는 텍스트 라벨과 레몬 이미지가 있습니다. 3번 상자 아래에는 'Tap the lemonade to drink it'이라는 텍스트 라벨과 레모네이드 한 잔 이미지가 있습니다. 4번 상자 아래에는 'Tap the empty glass to start again'이라는 텍스트 라벨과 빈 잔 이미지가 있습니다.

이미지를 클릭할 때마다 앱이 서로 다른 방식으로 동작하도록 하려면 clickable 동작을 맞춤설정해야 합니다. 구체적으로, 이미지를 클릭하면 실행되는 람다가 다음으로 어느 단계로 이동할지를 알아야 합니다.

앱에 레모네이드 만들기의 각 단계에 반복적으로 적용되는 코드가 있다는 사실을 눈치챘을 수 있을 텐데요. 앞에 나온 코드 스니펫의 when 문의 경우, 케이스 1의 코드는 약간의 차이를 제외하고는 케이스 2와 매우 비슷합니다. 필요하다면 UI에서 이미지 위에 텍스트를 표시하는 LemonTextAndImage()라는 새 구성 가능한 함수를 만듭니다. 입력 매개변수를 받는 새 구성 가능한 함수를 만들면 다른 입력값을 전달함으로써 복수의 시나리오에서 사용할 수 있는 재사용 가능한 함수를 갖게 됩니다. 여기서 입력 매개변수가 무엇이 될지는 여러분이 직접 알아내어야 합니다. 이 구성 가능한 함수를 만든 후에는 해당하는 지점에서 이 새 함수를 호출하도록 기존 코드를 수정하세요.

LemonTextAndImage()와 같은 별도의 컴포저블을 만드는 것의 또 다른 이점은 코드가 더 체계적이고 강력해진다는 것입니다. LemonTextAndImage()를 호출하면 텍스트와 이미지가 새 값으로 업데이트된다는 사실을 확실히 알 수 있습니다. 컴포저블을 사용하지 않았다면 업데이트된 텍스트 라벨이 잘못된 이미지와 함께 표시되는 케이스를 놓치는 경우가 있을 수 있습니다.

힌트를 하나 더 드리겠습니다. 바로 컴포저블에 람다 함수도 전달할 수 있다는 것인데요. 어떤 형식의 함수를 전달해야 하는지 명시하는 함수 형식 표기법을 사용하세요. 다음 예에서는 WelcomeScreen() 컴포저블이 정의되었고, 2개의 입력 매개변수를 받습니다. 2개의 입력값은 name 문자열과 () -> Unit 형식의 onStartClicked()함수인데, 이 함수는 입력값을 받지 않으며(화살표 앞의 빈 괄호) 반환 값이 없다는(화살표 뒤의 Unit) 사실을 알 수 있습니다. 함수 형식 () -> Unit과 일치하는 함수라면 무엇이든 이 ButtononClick 핸들러를 설정하는 데 사용할 수 있습니다. 버튼을 클릭하면 onStartClicked() 함수가 호출됩니다.

@Composable
fun WelcomeScreen(name: String, onStartClicked: () -> Unit) {
    Column {
        Text(text = "Welcome $name!")
        Button(
            onClick = onStartClicked
        ) {
            Text("Start")
        }
    }
}

컴포저블에 람다를 전달하는 것은 유용한 패턴입니다. WelcomeScreen() 컴포저블을 다양한 시나리오에서 재사용할 수 있게 되기 때문입니다. 사용자의 이름과 버튼의 onClick 동작은 인수로 전달되기 때문에 매번 다른 값을 가질 수 있습니다.

여기까지 알아보았으니 이제 코드로 돌아가서 앱에 레모네이드 만들기의 나머지 단계를 추가하세요.

레몬을 임의의 횟수만큼 압착하는 커스텀 로직을 추가하는 방법에 관한 가이드가 필요하면 다시 이 부분으로 돌아오세요.

압착 로직 추가하기

잘하셨습니다. 이미지를 탭하면 하나의 단계에서 다음 단계로 넘어가는 앱의 기본적인 기능이 구현되었습니다. 이제 레몬을 여러 번 압착하여 레모네이드를 만드는 동작을 추가할 차례입니다. 사용자가 레몬을 압착(탭)해야 하는 횟수는 2와 4 사이의 랜덤 숫자여야 합니다. 이 랜덤 숫자는 사용자가 나무에서 레몬을 새로 선택할 때마다 달라집니다.

녹색 테두리로 둘러싸인 네 개의 상자가 가로로 나열되어 있습니다. 각 상자에는 1부터 4 사이의 숫자가 들어 있습니다. 상자 1번에서 출발하여 상자 2번에서 끝나는 화살표, 상자 2번에서 출발하여 상자 3번에서 끝나는 화살표, 상자 3번에서 출발하여 상자 4번에서 끝나는 화살표, 상자 4번에서 출발하여 상자 1번에서 끝나는 화살표가 있습니다. 2번 상자에서 출발하여 같은 상자에서 끝나는 화살표가 있고, 여기에 'Random number of times'라는 라벨이 있습니다. 1번 상자 아래에는 레몬 나무 이미지와 텍스트 라벨이 있습니다. 2번 상자 아래에는 레몬 이미지와 텍스트 라벨이 있습니다. 3번 상자 아래에는 레모네이드가 든 잔 이미지와 텍스트 라벨이 있습니다. 4번 상자 아래에는 빈 잔 이미지와 텍스트 라벨이 있습니다.

사고 과정을 전개하는 데 도움을 줄 아래의 질문을 참고하세요.

  • Kotlin에서 랜덤 숫자를 생성하려면 어떻게 해야 하나요?
  • 코드의 어느 지점에서 랜덤 숫자를 생성해야 하나요?
  • 다음 단계로 넘어가기 전에 사용자가 필요한 횟수만큼 레몬을 탭했는지 어떻게 확인하면 좋을까요?
  • 화면이 다시 그려질 때마다 데이터가 재설정되지 않도록 remember 컴포저블과 함께 저장해야 하는 변수가 있나요?

이 변경사항을 구현한 후에 앱을 실행합니다. 레몬 이미지를 여러 번 탭해야 다음 단계로 넘어가고, 매번 탭해야 하는 횟수는 2와 4 사이의 랜덤 숫자인지 확인합니다. 레몬 이미지를 한 번 탭했는데 레모네이드 잔이 표시된다면 코드로 돌아가서 무엇이 빠졌는지 확인한 후 다시 시도하세요.

앱을 마무리하는 방법에 관한 가이드가 필요하면 다시 이 부분으로 돌아오세요.

앱 마무리하기

고지가 얼마 남지 않았습니다. 몇 가지 정보를 추가하여 앱을 다듬기만 하면 됩니다.

앱의 최종 스크린샷을 다시 한 번 살펴보세요.

  • 텍스트와 이미지를 화면에서 세로와 가로 방향의 중앙으로 배치합니다.
  • 텍스트의 글꼴 크기를 18sp로 설정합니다.
  • 텍스트와 이미지 사이에 16dp의 간격을 추가합니다.
  • 이미지 주변에 4dp만큼의 둥근 모서리를 갖는 2dp 두께의 얇은 테두리를 추가합니다. 테두리의 RGB 색상 값은 Red 105, Green 205, Blue 216입니다. 테두리를 추가하는 방법의 예시를 보려면 Google 검색을 이용하거나 테두리에 관한 문서를 참고하세요.

위와 같은 변경사항을 적용했으면 앱을 실행한 다음 최종 스크린샷과 비교하여 모두 동일한지 확인합니다.

코드를 읽는 모든 사람이 개발자의 사고 과정을 쉽게 이해할 수 있도록 코드에 주석을 추가하세요. 파일 상단에서 코드에서 사용하지 않은 가져오기 문을 모두 삭제하세요. 코드가 Kotlin 스타일 가이드를 따르는지 살펴보세요. 이렇게 하면 코드를 쉽게 유지 관리하고 다른 사람이 코드를 쉽게 읽을 수 있습니다.

잘하셨습니다. Lemonade 앱을 구현하느라 수고하셨습니다! 군데군데 고민할 부분이 있었던 까다로운 앱이었어요. 상쾌한 레모네이드 한 잔을 마시며 스스로를 칭찬해 주세요. 건배!

6. 솔루션 코드 가져오기

솔루션 코드를 다운로드합니다.

코드가 있는 GitHub 저장소를 클론하는 방법도 있습니다.

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

앱을 구현하는 방법에는 여러 가지가 있으니 솔루션 코드와 여러분의 코드가 정확하게 일치하지 않아도 된다는 사실을 기억하세요.

Lemonade 앱 GitHub 저장소에서 코드를 살펴볼 수도 있습니다.