1. 시작하기 전에
이 Codelab에서는 Android 앱에 간단한 애니메이션을 추가하는 방법을 알아봅니다. 애니메이션을 통해 보다 흥미롭고 사용자가 더 쉽게 해석할 수 있는 대화형 앱을 만들 수 있습니다. 정보가 가득 찬 화면에 개별 업데이트를 애니메이션으로 표시하면 사용자가 변경된 내용을 확인하는 데 도움이 됩니다.
앱 사용자 인터페이스에 사용할 수 있는 다양한 유형의 애니메이션이 있습니다. 항목은 나타났다가 사라지거나, 화면 안팎으로 이동하거나, 흥미로운 방식으로 변환할 수 있습니다. 이렇게 하면 앱의 UI를 표현력이 뛰어나고 사용하기 쉽게 만들 수 있습니다.
또한 애니메이션은 앱에 세련된 느낌을 더해주어 우아한 디자인과 분위기를 주면서 동시에 사용자에게 도움을 줍니다.
사용자에게 작업에 대한 보상을 제공하는 애니메이션은 사용자 경험의 중요한 부분을 더 의미 있게 만들 수 있습니다. | |
키패드 입력에 응답하는 애니메이션 요소는 피드백을 제공하여 작업의 성공 여부를 표시합니다. | |
애니메이션 목록 항목은 콘텐츠가 로드 중임을 전달하는 자리표시자입니다. | |
스와이프하여 열기 작업을 보여주는 애니메이션을 통해 필요한 동작을 유도합니다. | |
애니메이션 아이콘을 사용하면 아이콘을 재미있게 보완하거나 아이콘의 의미를 추가할 수 있습니다. |
기본 요건
- 함수, 람다, 스테이트리스(Stateless) 컴포저블을 비롯한 Kotlin 지식
- Jetpack Compose에서 레이아웃을 빌드하는 방법에 관한 기본 지식
- Jetpack Compose에서 목록을 만드는 방법에 관한 기본 지식
- Material Design 관련 기본 지식
학습할 내용
- Jetpack Compose로 간단한 스프링 애니메이션을 빌드하는 방법
빌드할 항목
- Jetpack Compose를 사용한 Material Theming Codelab의 Woof 앱을 기반으로 빌드하고 간단한 애니메이션을 추가해 사용자의 작업을 확인합니다.
필요한 항목
- Android 스튜디오 최신 버전
- 시작 코드를 다운로드하기 위한 인터넷 연결
2. code-along 동영상 시청(선택사항)
교육 과정 강사가 Codelab을 완료하는 모습을 보려면 아래 동영상을 재생하세요.
동영상을 전체 화면으로 펼쳐(동영상 하단의 오른쪽 모서리에 있는 아이콘 사용) Android 스튜디오와 코드를 더 선명하게 보는 것이 좋습니다.
이 단계는 선택사항입니다. 이 동영상을 건너뛰고 Codelab 안내를 바로 시작할 수도 있습니다.
3. 앱 개요
Jetpack Compose를 사용한 Material Theming Codelab에서 Material Design을 사용하여 반려견과 그 정보를 목록으로 표시하는 Woof 앱을 만들었습니다.
이 Codelab에서는 Woof 앱에 애니메이션을 추가합니다. 취미 정보를 추가하면 목록 항목을 펼칠 때 표시됩니다. 또한 확장되는 목록 항목에 애니메이션을 적용하는 스프링 애니메이션을 추가합니다.
시작 코드 가져오기
시작하려면 시작 코드를 다운로드하세요.
또는 코드에 관한 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 저장소에서 코드를 찾아볼 수 있습니다.
4. 펼치기 아이콘 추가
스프링 애니메이션을 빌드하는 첫 번째 단계는 더 펼치기 아이콘을 추가하는 것입니다. 펼치기 버튼 아이콘은 사용자가 목록 항목을 펼칠 수 있는 버튼을 제공합니다.
아이콘
아이콘은 의도한 기능을 시각적으로 전달하여 사용자가 사용자 인터페이스를 이해하는 데 도움을 주는 기호입니다. 사용자가 경험했을 것으로 기대되는 실제 세상의 사물에서 아이콘의 아이디어를 얻는 경우가 많습니다. 아이콘 디자인은 종종 필요한 최소한의 수준으로 세부 표현을 줄여서 사용자에게 인식되도록 만듭니다. 예를 들어 실제 세상의 연필은 쓰기에 사용되므로 그 아이콘은 일반적으로 만들기 또는 수정을 나타냅니다.
Material Design은 대부분의 요구에 부합하는 다수의 아이콘을 일반적인 카테고리로 정리하여 제공합니다.
Gradle 종속 항목 추가
프로젝트에 material-icons-extended
라이브러리 종속 항목을 추가합니다. 이 라이브러리의 Icons.Filled.ExpandLess
및
Icons.Filled.ExpandMore
아이콘을 사용합니다.
- Project 창에서 Gradle Scripts > build.gradle (Module: Woof.app)을 엽니다.
build.gradle (Module: Woof.app)
파일 끝까지 스크롤합니다.dependencies{}
블록에 다음 줄을 추가합니다.
implementation "androidx.compose.material:material-icons-extended"
아이콘 컴포저블 추가
머티리얼 아이콘 라이브러리에서 펼치기 아이콘을 표시하고 버튼으로 사용할 함수를 추가합니다.
MainActivity.kt
에서DogItem()
함수 뒤에DogItemButton()
이라는 구성 가능한 새 함수를 만듭니다.- 펼쳐진 상태의
Boolean
, 버튼 클릭 이벤트의 람다 표현식 및 다음과 같이 선택적Modifier
을 전달합니다.
@Composable
private fun DogItemButton(
expanded: Boolean,
onClick: () -> Unit,
modifier: Modifier = Modifier
) {
}
DogItemButton()
함수에서 이 아이콘을 누르면 호출되는onClick
이라는 매개변수(후행 람다 구문을 사용하는 람다)를 허용하는IconButton()
컴포저블을 추가합니다.onClick
인수에 전달되었습니다.
@Composable
private fun DogItemButton(
// ...
) {
IconButton(onClick = onClick) {
}
}
IconButton()
람다 블록 내에서imageVector
라는 이름이 지정된 매개변수를 사용하여Icon
컴포저블을 추가하고Icons.Filled.ExpandMore
로 설정합니다. 목록 항목 끝에 표시되는 아이콘 버튼입니다. Android 스튜디오에서는 구성 가능한
Icon()
매개변수에 관한 경고를 표시하며, 이후 단계에서 수정합니다.- 이름이 지정된 매개변수
tint
를 추가하고 아이콘의 색상을MaterialTheme.colors.secondary
로 설정합니다. 이름이 지정된 매개변수contentDescription
을 추가하고 문자열 리소스R.string.expand_button_content_description
으로 설정합니다.
import androidx.compose.material.Icon
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ExpandMore
IconButton(onClick = onClick) {
Icon(
imageVector = Icons.Filled.ExpandMore,
tint = MaterialTheme.colors.secondary,
contentDescription = stringResource(R.string.expand_button_content_description)
)
}
아이콘 표시
DogItemButton()
컴포저블을 레이아웃에 추가하여 표시합니다.
DogItem()
구성 가능한 함수의 시작 부분에var
를 추가하여 목록 항목의 확장된 상태를 저장합니다. 초깃값을false
으로 설정합니다.
var expanded by remember { mutableStateOf(false) }
- 목록 항목 내에 아이콘 버튼을 표시하려면
Row
블록 끝에 있는 구성 가능한DogItem()
함수에서DogInformation()
호출 후DogItemButton()
을 호출합니다.expanded
상태와 콜백의 빈 람다를 전달합니다. 이 람다 함수는 이후 단계에서 정의합니다.
Row(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp)
) {
DogIcon(dog.imageResourceId)
DogInformation(dog.name, dog.age)
DogItemButton(
expanded = expanded,
onClick = { }
)
}
- Design 창에서 미리보기를 빌드하고 새로고침합니다.
'펼치기' 버튼은 목록 항목의 끝에 정렬되지 않습니다. 다음 단계에서 이 문제를 해결합니다.
더보기 버튼 정렬
목록 항목의 끝부분에 펼치기 버튼을 정렬하려면 레이아웃에서 Modifier.weight()
속성을 사용하여 스페이서를 추가해야 합니다.
Woof 앱에서 각 목록 항목 행에는 반려견 이미지, 반려견 정보, 펼치기 버튼이 포함되어 있습니다. 가중치 1f
를 사용하여 펼치기 버튼 앞에 Spacer
컴포저블을 추가하여 버튼 아이콘을 올바르게 정렬합니다. 스페이서는 행에서 가중치가 적용된 유일한 하위 요소이므로 가중치가 없는 다른 하위 요소의 길이를 측정한 후 행에 남아 있는 공간을 채웁니다.
목록 항목 행에 스페이서 추가하기
DogItem()
구성 가능한 함수의Row
블록 끝에Spacer
를 추가합니다.weight(1f)
를 사용하여Modifier
를 전달합니다.Modifier.weight()
를 사용하면 스페이서가 행의 나머지 공간을 채웁니다.
Row(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp)
) {
DogIcon(dog.imageResourceId)
DogInformation(dog.name, dog.age)
Spacer(Modifier.weight(1f))
DogItemButton(
expanded = expanded,
onClick = { }
)
}
- Design 창에서 미리보기를 빌드하고 새로고침합니다. 이제 펼치기 버튼이 목록 항목의 끝에 정렬되는 것을 확인할 수 있습니다.
5. 취미를 표시하기 위해 컴포저블 추가
이 작업에서는 반려견 취미 정보를 표시하는 Text
컴포저블을 추가합니다.
- 새 구성 가능한 함수 만들기
DogHobby()
반려견 취미 문자열 리소스 ID(선택사항)를 사용하는Modifier
가 있는지 진단합니다. DogHobby()
함수 내에서 다음 패딩 속성이 있는 열을 만들어 열과 하위 컴포저블 사이에 공간을 추가합니다.
import androidx.annotation.StringRes
@Composable
fun DogHobby(@StringRes dogHobby: Int, modifier: Modifier = Modifier) {
Column(
modifier = modifier.padding(
start = 16.dp,
top = 8.dp,
bottom = 16.dp,
end = 16.dp
)
) { }
}
- 열 블록 내부에
Text
컴포저블을 추가합니다. 하나는 취미 정보 위에 About 텍스트를 표시하고 다른 하나는 취미 정보를 표시합니다.
- 정보 텍스트의 경우 스타일을
h3
(제목 3)으로 설정하고 색상을onBackground
로 설정합니다. 취미 정보의 경우 스타일을body1
로 설정합니다.
Column(
modifier = modifier.padding(
//..
)
) {
Text(
text = stringResource(R.string.about),
style = MaterialTheme.typography.h3,
)
Text(
text = stringResource(dogHobby),
style = MaterialTheme.typography.body1,
)
}
- 완성된
DogHobby()
구성 가능한 함수는 다음과 같습니다.
@Composable
fun DogHobby(@StringRes dogHobby: Int, modifier: Modifier = Modifier) {
Column(
modifier = modifier.padding(
start = 16.dp,
top = 8.dp,
bottom = 16.dp,
end = 16.dp
)
) {
Text(
text = stringResource(R.string.about),
style = MaterialTheme.typography.h3
)
Text(
text = stringResource(dogHobby),
style = MaterialTheme.typography.body1
)
}
}
DogHobby()
컴포저블을 표시하려면DogItem()
에서Row
를Column
으로 래핑합니다.DogHobby()
함수를 호출하여dog.hobbies
를 매개변수로 전달하고Row
뒤에 두 번째 하위 요소로 전달합니다.
Column() {
Row(
//..
) {
//..
}
DogHobby(dog.hobbies)
}
전체 DogItem()
함수는 다음과 같습니다.
@Composable
fun DogItem(dog: Dog, modifier: Modifier = Modifier) {
var expanded by remember { mutableStateOf(false) }
Card(
elevation = 4.dp,
modifier = modifier.padding(8.dp)
) {
Column() {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp)
) {
DogIcon(dog.imageResourceId)
DogInformation(dog.name, dog.age)
Spacer(Modifier.weight(1f))
DogItemButton(
expanded = expanded,
onClick = { expanded = !expanded },
)
}
DogHobby(dog.hobbies)
}
}
}
- Design 창에서 미리보기를 빌드하고 새로고침합니다. 반려견 취미가 표시됩니다.
6. 버튼 클릭 시 취미 표시 또는 숨기기
앱에는 모든 목록 항목에 펼치기 버튼이 있지만 이 버튼은 아직 아무런 기능을 하지 않습니다. 이 섹션에서는 사용자가 펼치기 버튼을 클릭할 때 취미 정보를 숨기거나 표시하는 옵션을 추가합니다.
- 구성 가능한
DogItem()
함수의DogItemButton()
함수 호출에서onClick()
람다 표현식을 정의하고 버튼을 클릭할 때expanded
부울 상태 값을true
로 변경합니다. 버튼을 다시 클릭하면false
로 다시 변경합니다.
DogItemButton(
expanded = expanded,
onClick = { expanded = !expanded }
)
DogItem()
함수에서expanded
부울을 확인하는if
검사로DogHobby()
함수 호출을 래핑합니다.
// No need to copy over
@Composable
fun DogItem(dog: Dog, modifier: Modifier = Modifier) {
var expanded by remember { mutableStateOf(false) }
Card(
//..
) {
Column() {
Row(
//..
) {
//..
}
if (expanded) {
DogHobby(dog.hobbies)
}
}
}
}
위 코드에서 반려견의 취미 정보는 expanded
값이 true
인 경우에만 표시됩니다.
- 미리보기를 통해 UI의 모양을 확인할 수 있으며 UI와 상호작용할 수도 있습니다. UI 미리보기와 상호작용하려면 Design 창의 오른쪽 상단에 있는 대화형 모드 버튼
을 클릭합니다. 그러면 미리보기가 대화형 모드로 시작됩니다.
- '펼치기' 버튼을 클릭하여 미리보기와 상호작용하기 '더보기' 버튼을 클릭하면 반려견 취미 정보가 숨겨지고 표시됩니다.
목록 항목을 펼치면 더 펼치기 버튼 아이콘은 동일하게 유지됩니다. 더 나은 사용자 환경을 위해 ExpandMore
에 아래쪽 화살표 를 표시하고
ExpandLess
에 위쪽 화살표 를 표시하도록 아이콘을 변경합니다.
DogItemButton()
함수에서 다음과 같이expanded
상태를 기반으로imageVector
값을 업데이트합니다.
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,
//..
)
}
}
- 기기 또는 에뮬레이터에서 앱을 실행하거나 미리보기에서 대화형 모드를 다시 사용합니다. 아이콘은
ExpandMore
과
ExpandLess
간에 번갈아 표시됩니다.
아이콘을 업데이트했습니다.
목록 항목을 펼치면 급격한 높이 변화를 확인하나요? 갑작스러운 높이 변경이 세련된 앱처럼 보이지는 않습니다. 이 문제를 해결하려면 다음으로 애니메이션을 추가하세요.
7. 애니메이션 추가
애니메이션을 사용하면 앱에 일어나고 있는 일을 사용자에게 알려주는 시각적 단서를 추가할 수 있습니다. 새 콘텐츠가 로드되거나 새 작업이 제공되는 경우와 같이 UI에서 상태가 변경되는 경우 특히 유용합니다. 또한 애니메이션을 사용하여 앱에 세련된 느낌을 줄 수도 있습니다.
이 섹션에서는 목록 항목의 높이 변화에 애니메이션을 적용하는 스프링 애니메이션을 추가합니다.
스프링 애니메이션
스프링 애니메이션은 스프링력에 기반한 물리학 기반 애니메이션입니다. 스프링 애니메이션에서는 적용된 스프링 포력을 기준으로 이동의 값과 속도가 계산됩니다.
예를 들어 화면 주위에서 앱 아이콘을 드래그한 다음 손가락을 떼면 아이콘이 보이지 않는 힘으로 원래 위치로 돌아갑니다.
다음 애니메이션은 스프링 효과를 보여줍니다. 아이콘에서 손가락을 떼면 아이콘이 스프링을 모방하며 뒤로 이동합니다.
스프링 효과
스프링력은 다음 두 가지 속성을 기준으로 합니다.
- 감쇠비: 스프링의 탄성입니다.
- 강도 수준: 스프링의 강수 즉, 스프링이 끝까지 이동하는 속도입니다.
다음은 감쇠비와 강도 수준이 다른 애니메이션의 예입니다.
|
|
|
|
이제 앱에 스프링 애니메이션을 추가합니다.
MainActivity.kt
의DogItem()
에서Column
레이아웃에modifier
매개변수를 추가합니다.
@Composable
fun DogItem(dog: Dog, modifier: Modifier = Modifier) {
//..
Card(
//..
) {
Column(
modifier = Modifier
){
//..
}
}
}
DogItem()
구성 가능한 함수에서 DogHobby()
함수 호출을 관찰합니다. 반려견 취미 정보는 expanded
부울 값에 따라 컴포지션에 포함됩니다. 목록 항목의 높이는 취미 정보의 표시 여부에 따라 달라집니다. animateContentSize
수정자를 사용하여 새 높이와 이전 높이 사이에 전환을 추가합니다.
// No need to copy over
@Composable
fun DogItem(...) {
//..
if (expanded) {
DogHobby(dog.hobbies)
}
}
- 수정자를
animateContentSize
수정자로 연결하여 크기 (목록 항목 높이) 변경을 애니메이션 처리합니다.
import androidx.compose.animation.animateContentSize
Column(
modifier = Modifier
.animateContentSize()
) {
//..
}
현재 구현에서는 앱의 목록 항목 높이를 애니메이션으로 보여줍니다. 그러나 애니메이션이 매우 미묘하여 앱을 실행할 때 파악하기 어렵습니다. 이 문제를 해결하려면 애니메이션 맞춤설정이 가능한 animationSpec
매개변수(선택사항)를 사용합니다.
animationSpec
매개변수를animateContentSize()
함수 호출에 추가합니다.DampingRatioMediumBouncy
및StiffnessLow
매개변수를 사용하여 스프링 애니메이션으로 설정합니다.
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.spring
Column(
modifier = Modifier
.animateContentSize(
animationSpec = spring(
dampingRatio = Spring.DampingRatioMediumBouncy,
stiffness = Spring.StiffnessLow
)
)
)
- Design 창에서 미리보기를 빌드 및 새로고침하고, 대화형 모드를 사용하거나 에뮬레이터 또는 기기에서 앱을 실행하여 스프링 애니메이션을 실제로 확인합니다.
에뮬레이터나 기기에서 앱을 다시 실행하고 애니메이션으로 멋진 앱을 즐기세요.
8. (선택사항) 다른 애니메이션 실험
animate*AsState
animate*AsState()
함수는 Compose에서 단일 값을 애니메이션 처리하는 가장 간단한 애니메이션 API 중 하나입니다. 최종 값(또는 타겟 값)만 제공하면 API가 현재 값에서 지정된 값으로 애니메이션을 시작합니다.
Compose는 Float
, Color
, Dp
, Size
, Offset
, Int
의 animate*AsState()
함수를 제공합니다. 일반 유형을 취하는 animateValueAsState()
를 사용하여 다른 데이터 유형의 지원 기능을 쉽게 추가할 수 있습니다.
목록 항목을 펼칠 때 animateColorAsState()
함수를 사용하여 색상에 애니메이션을 적용합니다.
힌트:
- 색상을 선언하고 초기화를
animateColorAsState()
함수에 위임합니다. expanded
부울 값에 따라 이름이 지정된targetValue
매개변수를 설정합니다.
@Composable
fun DogItem(dog: Dog, modifier: Modifier = Modifier) {
//..
val color by animateColorAsState(
targetValue = if (expanded) Green25 else MaterialTheme.colors.surface,
)
Card(
//..
) {...}
}
- 위에서 백그라운드 수정자로 선언한
color
를Column
로 설정합니다.
@Composable
fun DogItem(dog: Dog, modifier: Modifier = Modifier) {
//..
Card(
//..
) {
Column(
modifier = Modifier
.animateContentSize(
//..
)
)
.background(color = color)
) {...}
}
9. 솔루션 코드 가져오기
완료된 Codelab의 코드를 다운로드하려면 이 git 명령어를 사용하면 됩니다.
$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-woof.git
또는 ZIP 파일로 저장소를 다운로드한 다음 압축을 풀고 Android 스튜디오에서 열어도 됩니다.
솔루션 코드를 보려면 GitHub에서 확인하세요.
10. 결론
축하합니다. 반려견에 관한 정보를 숨기고 표시하는 버튼을 추가했습니다. 스프링 애니메이션을 사용하여 사용자 환경을 개선했습니다. Design 창에서 대화형 모드를 사용하는 방법도 배웠습니다.
다른 유형의 Jetpack Compose 애니메이션을 사용해 볼 수도 있습니다. #AndroidBasics를 사용해 작업한 결과물을 소셜 미디어로 공유해 보세요.