Jetpack Compose를 사용한 간단한 애니메이션

1. 시작하기 전에

이 Codelab에서는 Android 앱에 간단한 애니메이션을 추가하는 방법을 알아봅니다. 애니메이션을 사용하면 사용자가 흥미를 느끼고 더 쉽게 해석할 수 있는 대화형 앱을 만들 수 있습니다. 정보가 가득 찬 화면에 개별 업데이트를 애니메이션으로 표시하면 사용자가 변경된 내용을 확인하는 데 도움이 됩니다.

앱의 사용자 인터페이스에 사용할 수 있는 애니메이션 유형은 다양합니다. 항목은 나타날 때 페이드 인하고 사라질 때 페이드 아웃할 수 있으며 화면 안팎으로 이동하거나 흥미로운 방식으로 변형될 수 있습니다. 이렇게 하면 앱의 UI를 표현력이 뛰어나고 사용하기 쉽게 만들 수 있습니다.

또한 애니메이션은 앱에 세련된 느낌을 더해주어 우아한 디자인과 분위기를 자아내는 동시에 사용자에게 도움을 줍니다.

기본 요건

  • 함수, 람다, 스테이트리스(Stateless) 컴포저블을 비롯한 Kotlin 지식
  • Jetpack Compose에서 레이아웃을 빌드하는 방법에 관한 기본 지식
  • Jetpack Compose에서 목록을 만드는 방법에 관한 기본 지식
  • Material Design 관련 기본 지식

학습할 내용

  • Jetpack Compose로 간단한 스프링 애니메이션을 빌드하는 방법

빌드할 항목

필요한 항목

  • Android 스튜디오의 최신 안정화 버전
  • 시작 코드를 다운로드하기 위한 인터넷 연결

2. 앱 개요

Jetpack Compose를 사용한 Material Theming Codelab에서 Material Design을 사용하여 반려견과 그 정보를 목록으로 표시하는 Woof 앱을 만들었습니다.

8de41607e8ff2c79.png

이 Codelab에서는 Woof 앱에 애니메이션과 목록 항목을 펼칠 때 표시되는 취미 정보를 추가합니다. 또한 목록 항목이 확장될 때 애니메이션을 적용하는 스프링 애니메이션을 추가합니다.

c0d0a52463332875.gif

시작 코드 가져오기

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

GitHub 저장소를 클론하여 코드를 가져와도 됩니다.

$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-woof.git
$ cd basic-android-kotlin-compose-training-woof
$ git checkout material

Woof app GitHub 저장소에서 코드를 둘러볼 수 있습니다.

3. 펼치기 아이콘 추가

이 섹션에서는 펼치기 30c384f00846e69b.png접기 3387908f9031c112.png 아이콘을 앱에 추가합니다.

def59d71015c0fbe.png

아이콘

아이콘은 의도한 기능을 시각적으로 전달하여 사용자가 사용자 인터페이스를 이해하는 데 도움을 주는 기호입니다. 사용자가 경험했을 것으로 기대되는 실제 세상의 사물에서 아이콘의 아이디어를 얻는 경우가 많습니다. 아이콘은 보통 사용자가 인식할 수 있을 정도로만 세부 표현을 덜어내어 디자인합니다. 예를 들어 실제 세상의 연필은 쓰기에 사용되므로 연필 아이콘은 일반적으로 만들기 또는 수정을 나타냅니다.

공책 위의 연필 사진: 안젤리나 리트빈(Unsplash 제공)

흑백 연필 아이콘

Material Design은 대부분의 요구에 부합하는 다수의 아이콘을 일반적인 카테고리로 정리하여 제공합니다.

머티리얼 아이콘 라이브러리

Gradle 종속 항목 추가

프로젝트에 material-icons-extended 라이브러리 종속 항목을 추가합니다. 이 라이브러리의 Icons.Filled.ExpandLess 30c384f00846e69b.pngIcons.Filled.ExpandMore 3387908f9031c112.png 아이콘을 사용합니다.

  1. Project 창에서 Gradle Scripts > build.gradle.kts (Module :app)을 엽니다.
  2. build.gradle.kts (Module :app) 파일 끝까지 스크롤합니다. dependencies{} 블록에 다음 줄을 추가합니다.
implementation("androidx.compose.material:material-icons-extended")

아이콘 컴포저블 추가

머티리얼 아이콘 라이브러리에서 펼치기 아이콘을 표시하고 버튼으로 사용할 함수를 추가합니다.

  1. MainActivity.kt에서 DogItem() 함수 뒤에 DogItemButton()이라는 구성 가능한 새 함수를 만듭니다.
  2. 다음과 같이 펼쳐진 상태의 Boolean, 버튼 onClick 핸들러의 람다 표현식 및 선택적 Modifier를 전달합니다.
@Composable
private fun DogItemButton(
   expanded: Boolean,
   onClick: () -> Unit,
   modifier: Modifier = Modifier
) {

}
  1. DogItemButton() 함수에서 이름이 onClick인 매개변수, 후행 람다 문법을 사용하는 람다(이 아이콘을 누르면 호출됨), 선택적 modifier를 허용하는 IconButton() 컴포저블을 추가합니다. IconButton's onClickmodifier value parametersDogItemButton에 전달된 것과 동일하게 설정합니다.
@Composable
private fun DogItemButton(
   expanded: Boolean,
   onClick: () -> Unit,
   modifier: Modifier = Modifier
){
   IconButton(
       onClick = onClick,
       modifier = modifier
   ) {

   }
}
  1. IconButton() 람다 블록 내에서 Icon 컴포저블을 추가하고 imageVector value-parameterIcons.Filled.ExpandMore로 설정합니다. 목록 항목의 끝에 다음과 같이 표시됩니다. 3387908f9031c112.png Android 스튜디오에서는 Icon() 컴포저블 매개변수에 관한 경고를 표시하며 이는 다음 단계에서 수정합니다.
import androidx.compose.material.icons.filled.ExpandMore
import androidx.compose.material.icons.Icons
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton

IconButton(
   onClick = onClick,
   modifier = modifier
) {
   Icon(
       imageVector = Icons.Filled.ExpandMore
   )
}
  1. 값 매개변수 tint를 추가하고 아이콘의 색상을 MaterialTheme.colorScheme.secondary로 설정합니다. 이름이 지정된 매개변수 contentDescription을 추가하고 문자열 리소스 R.string.expand_button_content_description으로 설정합니다.
IconButton(
   onClick = onClick,
   modifier = modifier
){
   Icon(
       imageVector = Icons.Filled.ExpandMore,
       contentDescription = stringResource(R.string.expand_button_content_description),
       tint = MaterialTheme.colorScheme.secondary
   )
}

아이콘 표시

DogItemButton() 컴포저블을 레이아웃에 추가하여 표시합니다.

  1. DogItem()의 시작 부분에 var을 추가하여 목록 항목의 펼쳐진 상태를 저장합니다. 초깃값을 false로 설정합니다.
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue

var expanded by remember { mutableStateOf(false) }
  1. 목록 항목 내에 아이콘 버튼을 표시합니다. DogItem() 컴포저블의 Row 블록 끝에 있는 DogInformation() 호출 뒤에 DogItemButton()을 추가합니다. expanded 상태와 콜백의 빈 람다를 전달합니다. 이후 단계에서 onClick 작업을 정의합니다.
Row(
   modifier = Modifier
       .fillMaxWidth()
       .padding(dimensionResource(R.dimen.padding_small))
) {
   DogIcon(dog.imageResourceId)
   DogInformation(dog.name, dog.age)
   DogItemButton(
       expanded = expanded,
       onClick = { /*TODO*/ }
   )
}
  1. Design 창에서 WoofPreview()를 확인합니다.

5bbf09cd2828b6.png

펼치기 버튼은 목록 항목의 끝에 정렬되지 않습니다. 다음 단계에서 이 문제를 해결합니다.

더보기 버튼 정렬

목록 항목의 끝부분에 펼치기 버튼을 정렬하려면 레이아웃에서 Modifier.weight() 속성을 사용하여 스페이서를 추가해야 합니다.

Woof 앱에서 각 목록 항목 행에는 반려견 이미지, 반려견 정보, 펼치기 버튼이 포함되어 있습니다. 가중치 1f를 사용하여 펼치기 버튼 앞에 Spacer 컴포저블을 추가하여 버튼 아이콘을 올바르게 정렬합니다. 스페이서는 행에서 가중치가 적용된 유일한 하위 요소이므로 가중치가 없는 다른 하위 요소의 너비를 측정한 후 행에 남아 있는 공간을 채웁니다.

733f6d9ef2939ab5.png

목록 항목 행에 스페이서 추가

  1. DogItem()에서 DogInformation()DogItemButton() 사이에 Spacer를 추가합니다. weight(1f)를 사용하여 Modifier를 전달합니다. Modifier.weight()를 사용하면 스페이서가 행의 나머지 공간을 채웁니다.
import androidx.compose.foundation.layout.Spacer

Row(
   modifier = Modifier
       .fillMaxWidth()
       .padding(dimensionResource(R.dimen.padding_small))
) {
   DogIcon(dog.imageResourceId)
   DogInformation(dog.name, dog.age)
   Spacer(modifier = Modifier.weight(1f))
   DogItemButton(
       expanded = expanded,
       onClick = { /*TODO*/ }
   )
}
  1. Design 창에서 WoofPreview()를 확인합니다. 이제 펼치기 버튼이 목록 항목의 끝에 정렬되는 것을 확인할 수 있습니다.

8df42b9d85a5dbaa.png

4. 취미를 표시하기 위해 컴포저블 추가

이 작업에서는 반려견 취미 정보를 표시하는 Text 컴포저블을 추가합니다.

bba8146c6332cc37.png

  1. 새 구성 가능한 함수 DogHobby()를 만듭니다. 이 함수는 반려견의 취미 문자열 리소스 ID와 선택적 Modifier를 사용합니다.
@Composable
fun DogHobby(
   @StringRes dogHobby: Int,
   modifier: Modifier = Modifier
) {
}
  1. DogHobby() 함수 내에서 Column을 만들고 DogHobby()에 전달된 수정자를 전달합니다.
@Composable
fun DogHobby(
   @StringRes dogHobby: Int,
   modifier: Modifier = Modifier
){
   Column(
       modifier = modifier
   ) {

   }
}
  1. Column 블록 내에서 Text 컴포저블을 두 개 추가합니다. 하나는 취미 정보 위에 About 텍스트를 표시하고 다른 하나는 취미 정보를 표시합니다.

첫 번째 컴포저블의 textstrings.xml 파일에서 about으로 설정하고 stylelabelSmall로 설정합니다. 두 번째 컴포저블의 text를 전달된 dogHobby로 설정하고 stylebodyLarge로 설정합니다.

Column(
   modifier = modifier
) {
   Text(
       text = stringResource(R.string.about),
       style = MaterialTheme.typography.labelSmall
   )
   Text(
       text = stringResource(dogHobby),
       style = MaterialTheme.typography.bodyLarge
   )
}
  1. DogItem()에서 DogHobby() 컴포저블은 DogIcon(), DogInformation(), Spacer(), DogItemButton()이 포함된 Row 아래에 위치합니다. 이렇게 하려면 RowColumn으로 래핑하여 Row 아래에 취미가 추가될 수 있도록 합니다.
Column() {
   Row(
       modifier = Modifier
           .fillMaxWidth()
           .padding(dimensionResource(R.dimen.padding_small))
   ) {
       DogIcon(dog.imageResourceId)
       DogInformation(dog.name, dog.age)
       Spacer(modifier = Modifier.weight(1f))
       DogItemButton(
           expanded = expanded,
           onClick = { /*TODO*/ }
       )
   }
}
  1. Row 뒤에 DogHobby()Column의 두 번째 하위 요소로 추가합니다. 전달된 반려견의 고유한 취미가 포함된 dog.hobbiesDogHobby() 컴포저블의 패딩이 포함된 modifier를 전달합니다.
Column() {
   Row() {
      ...
   }
   DogHobby(
       dog.hobbies,
       modifier = Modifier.padding(
           start = dimensionResource(R.dimen.padding_medium),
           top = dimensionResource(R.dimen.padding_small),
           end = dimensionResource(R.dimen.padding_medium),
           bottom = dimensionResource(R.dimen.padding_medium)
       )
   )
}

전체 DogItem() 함수는 다음과 같습니다.

@Composable
fun DogItem(
   dog: Dog,
   modifier: Modifier = Modifier
) {
   var expanded by remember { mutableStateOf(false) }
   Card(
       modifier = modifier
   ) {
       Column() {
           Row(
               modifier = Modifier
                   .fillMaxWidth()
                   .padding(dimensionResource(R.dimen.padding_small))
           ) {
               DogIcon(dog.imageResourceId)
               DogInformation(dog.name, dog.age)
               Spacer(Modifier.weight(1f))
               DogItemButton(
                   expanded = expanded,
                   onClick = { /*TODO*/ },
               )
           }
           DogHobby(
               dog.hobbies,
               modifier = Modifier.padding(
                   start = dimensionResource(R.dimen.padding_medium),
                   top = dimensionResource(R.dimen.padding_small),
                   end = dimensionResource(R.dimen.padding_medium),
                   bottom = dimensionResource(R.dimen.padding_medium)
               )
           )
       }
   }
}
  1. Design 창에서 WoofPreview()를 확인합니다. 반려견 취미가 표시됩니다.

펼쳐진 목록 항목이 포함된 Woof 미리보기

5. 버튼 클릭 시 취미 표시 또는 숨기기

앱에는 모든 목록 항목에 펼치기 버튼이 있지만 이 버튼은 아직 아무런 기능을 하지 않습니다. 이 섹션에서는 사용자가 펼치기 버튼을 클릭할 때 취미 정보를 숨기거나 표시하는 옵션을 추가합니다.

  1. 구성 가능한 DogItem() 함수의 DogItemButton() 함수 호출에서 onClick() 람다 표현식을 정의하고 버튼을 클릭할 때 expanded 불리언 상태 값을 true로 변경합니다. 버튼을 다시 클릭하면 false로 다시 변경합니다.
DogItemButton(
   expanded = expanded,
   onClick = { expanded = !expanded }
)
  1. DogItem() 함수에서 expanded 불리언을 확인하는 if 검사로 DogHobby() 함수 호출을 래핑합니다.
@Composable
fun DogItem(
   dog: Dog,
   modifier: Modifier = Modifier
) {
   var expanded by remember { mutableStateOf(false) }
   Card(
       ...
   ) {
       Column(
           ...
       ) {
           Row(
               ...
           ) {
               ...
           }
           if (expanded) {
               DogHobby(
                   dog.hobbies, modifier = Modifier.padding(
                       start = dimensionResource(R.dimen.padding_medium),
                       top = dimensionResource(R.dimen.padding_small),
                       end = dimensionResource(R.dimen.padding_medium),
                       bottom = dimensionResource(R.dimen.padding_medium)
                   )
               )
           }
       }
   }
}

이제 반려견의 취미 정보가 expanded 값이 true인 경우에만 표시됩니다.

  1. 미리보기를 통해 UI의 모양을 확인할 수 있으며 UI와 상호작용할 수도 있습니다. UI 미리보기와 상호작용하려면 Design 창에 있는 WoofPreview 텍스트 위로 마우스를 가져간 후 Design 창 오른쪽 상단에 있는 Interactive Mode 버튼 42379dbe94a7a497.png을 클릭합니다. 그러면 미리보기가 대화형 모드로 시작됩니다.

578d665f081e638e.png

  1. 펼치기 버튼을 클릭하여 미리보기와 상호작용합니다. 펼치기 버튼을 클릭하면 반려견 취미 정보가 숨겨지고 표시됩니다.

Woof 목록 항목이 펼쳐지고 접히는 애니메이션

목록 항목을 펼치면 펼치기 버튼 아이콘은 동일하게 유지됩니다. 더 나은 사용자 환경을 위해 ExpandMore에 아래쪽 화살표 c761ef298c2aea5a.png를 표시하고 ExpandLess에 위쪽 화살표 b380f933be0b6ff4.png를 표시하도록 아이콘을 변경합니다.

  1. DogItemButton() 함수에서 다음과 같이 expanded 상태에 따라 imageVector 값을 업데이트하는 if 문을 추가합니다.
import androidx.compose.material.icons.filled.ExpandLess

@Composable
private fun DogItemButton(
   ...
) {
   IconButton(onClick = onClick) {
       Icon(
           imageVector = if (expanded) Icons.Filled.ExpandLess else Icons.Filled.ExpandMore,
           ...
       )
   }
}

이전 코드 스니펫에서 if-else를 작성한 방법을 확인합니다.

if (expanded) Icons.Filled.ExpandLess else Icons.Filled.ExpandMore

이는 다음 코드에서 중괄호 { }를 사용하는 것과 같습니다.

if (expanded) {

`Icons.Filled.ExpandLess`

} else {

`Icons.Filled.ExpandMore`

}

if-else 문에 한 줄의 코드가 있는 경우 중괄호는 선택사항입니다.

  1. 기기 또는 에뮬레이터에서 앱을 실행하거나 미리보기에서 대화형 모드를 다시 사용합니다. 아이콘은 ExpandMore c761ef298c2aea5a.pngExpandLess b380f933be0b6ff4.png 간에 번갈아 표시됩니다.

de5dc4a953f11e65.gif

아이콘을 업데이트했습니다.

목록 항목을 펼칠 때 높이가 급격히 변화하는 것을 확인할 수 있나요? 갑작스러운 높이 변경이 세련된 앱처럼 보이지는 않습니다. 이 문제를 해결하려면 다음으로 애니메이션을 추가하세요.

6. 애니메이션 추가

애니메이션을 사용하면 앱에 일어나고 있는 일을 사용자에게 알려주는 시각적 단서를 추가할 수 있습니다. 새 콘텐츠가 로드되거나 새 작업이 제공되는 경우와 같이 UI에서 상태가 변경되는 경우 특히 유용합니다. 또한 애니메이션을 사용하여 앱에 세련된 느낌을 줄 수도 있습니다.

이 섹션에서는 목록 항목의 높이 변화에 애니메이션을 적용하는 스프링 애니메이션을 추가합니다.

스프링 애니메이션

스프링 애니메이션스프링력에 기반한 물리학 기반 애니메이션입니다. 스프링 애니메이션에서는 적용된 스프링 포력을 기준으로 이동의 값과 속도가 계산됩니다.

예를 들어 화면 주위에서 앱 아이콘을 드래그한 다음 손가락을 떼면 아이콘이 보이지 않는 힘으로 원래 위치로 돌아갑니다.

다음 애니메이션은 스프링 효과를 보여줍니다. 아이콘에서 손가락을 떼면 아이콘이 스프링처럼 뒤로 튕기듯 이동합니다.

스프링 해제 효과

스프링 효과

스프링력은 다음 두 가지 속성을 기준으로 합니다.

  • 감쇠비: 스프링의 탄성입니다.
  • 강도 수준: 스프링의 강성 즉, 스프링이 끝까지 이동하는 속도입니다.

다음은 감쇠비와 강성 수준이 다른 애니메이션의 예입니다.

스프링 효과높은 반동력

스프링 효과반동력 없음

높은 강성

낮은 강성 매우 낮은 강성

구성 가능한 DogItem() 함수에서 DogHobby() 함수 호출을 살펴보세요. 반려견 취미 정보는 expanded 불리언 값에 따라 컴포지션에 포함됩니다. 목록 항목의 높이는 취미 정보의 표시 여부에 따라 달라집니다. 현재 전환이 원활하지 않습니다. 이 섹션에서는 animateContentSize 수정자를 사용하여 펼쳐진 상태와 펼쳐지지 않은 상태 간에 더 원활한 전환을 추가합니다.

// No need to copy over
@Composable
fun DogItem(...) {
  ...
    if (expanded) {
       DogHobby(
          dog.hobbies,
          modifier = Modifier.padding(
              start = dimensionResource(R.dimen.padding_medium),
              top = dimensionResource(R.dimen.padding_small),
              end = dimensionResource(R.dimen.padding_medium),
              bottom = dimensionResource(R.dimen.padding_medium)
          )
      )
   }
}
  1. MainActivity.ktDogItem()에서 Column 레이아웃에 modifier 매개변수를 추가합니다.
@Composable
fun DogItem(
   dog: Dog,
   modifier: Modifier = Modifier
) {
   ...
   Card(
       ...
   ) {
       Column(
          modifier = Modifier
       ){
           ...
       }
   }
}
  1. 수정자를 animateContentSize 수정자로 연결하여 크기(목록 항목 높이) 변경을 애니메이션 처리합니다.
import androidx.compose.animation.animateContentSize

Column(
   modifier = Modifier
       .animateContentSize()
)

현재 구현에서는 앱의 목록 항목 높이를 애니메이션으로 보여줍니다. 그러나 애니메이션이 매우 미묘하여 앱을 실행할 때 파악하기 어렵습니다. 이 문제를 해결하려면 애니메이션 맞춤설정이 가능한 animationSpec 매개변수(선택사항)를 사용하세요.

  1. Woof의 경우 애니메이션은 반동력 없이 이즈 인, 이즈 아웃됩니다. 이를 위해서는 animateContentSize() 함수 호출에 animationSpec 매개변수를 추가합니다. DampingRatioNoBouncy를 사용하여 스프링 애니메이션으로 설정되므로 반동력이 없고 StiffnessMedium 매개변수는 스프링의 강성을 약간 더 높입니다.
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.spring

Column(
   modifier = Modifier
       .animateContentSize(
           animationSpec = spring(
               dampingRatio = Spring.DampingRatioNoBouncy,
               stiffness = Spring.StiffnessMedium
           )
       )
)
  1. Design 창에서 WoofPreview()를 확인하고 대화형 모드를 사용하거나 에뮬레이터 또는 기기에서 앱을 실행하여 작동하는 스프링 애니메이션을 확인합니다.

c0d0a52463332875.gif

축하합니다. 애니메이션으로 멋진 앱을 만들었습니다.

7. (선택사항) 다른 애니메이션 실험

animate*AsState

animate*AsState() 함수는 Compose에서 단일 값을 애니메이션 처리하는 가장 간단한 애니메이션 API 중 하나입니다. 최종 값(또는 타겟 값)만 제공하면 API가 현재 값에서 지정된 값으로 애니메이션을 시작합니다.

Compose는 Float, Color, Dp, Size, Offset, Intanimate*AsState() 함수를 제공합니다. 포괄적인 유형을 취하는 animateValueAsState()를 사용하여 손쉽게 다른 데이터 유형 지원을 추가할 수 있습니다.

목록 항목을 펼칠 때 animateColorAsState() 함수를 사용하여 색상을 변경해 보세요.

  1. DogItem()에서 색상을 선언하고 초기화를 animateColorAsState() 함수에 위임합니다.
import androidx.compose.animation.animateColorAsState

@Composable
fun DogItem(
   dog: Dog,
   modifier: Modifier = Modifier
) {
   var expanded by remember { mutableStateOf(false) }
   val color by animateColorAsState()
   ...
}
  1. expanded 불리언 값에 따라 이름이 지정된 targetValue 매개변수를 설정합니다. 목록 항목이 펼쳐져 있는 경우 목록 항목을 tertiaryContainer 색상으로 설정합니다. 펼쳐져 있지 않으면 primaryContainer 색상으로 설정합니다.
import androidx.compose.animation.animateColorAsState

@Composable
fun DogItem(
   dog: Dog,
   modifier: Modifier = Modifier
) {
   var expanded by remember { mutableStateOf(false) }
   val color by animateColorAsState(
       targetValue = if (expanded) MaterialTheme.colorScheme.tertiaryContainer
       else MaterialTheme.colorScheme.primaryContainer,
   )
   ...
}
  1. color를 백그라운드 수정자로 Column으로 설정합니다.
@Composable
fun DogItem(
   dog: Dog,
   modifier: Modifier = Modifier
) {
   ...
   Card(
       ...
   ) {
       Column(
           modifier = Modifier
               .animateContentSize(
                   ...
                   )
               )
               .background(color = color)
       ) {...}
}
  1. 목록 항목을 펼치면 색상이 어떻게 변경되는지 확인합니다. 펼쳐지지 않은 목록 항목은 primaryContainer 색상이고 펼쳐진 목록 항목은 tertiaryContainer 색상입니다.

animateAsState 애니메이션

8. 솔루션 코드 가져오기

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

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

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

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

9. 마무리

축하합니다. 반려견에 관한 정보를 숨기고 표시하는 버튼을 추가했습니다. 스프링 애니메이션을 사용하여 사용자 환경을 개선했습니다. Design 창에서 대화형 모드를 사용하는 방법도 배웠습니다.

다른 유형의 Jetpack Compose 애니메이션을 사용해 볼 수도 있습니다. #AndroidBasics를 사용해 작업한 결과물을 소셜 미디어로 공유해 보세요.

자세히 알아보기