1. 시작하기 전에
이 Codelab은 Kotlin 기반 Android 고급 교육 과정의 일부입니다. Codelab을 순서대로 진행하는 경우 학습 효과를 극대화할 수 있지만 순서를 바꿔 진행해도 괜찮습니다. 모든 교육 과정 Codelab은 Kotlin 기반 고급 Android Codelab 방문 페이지에 나열되어 있습니다.
MotionLayout는 Android 앱에 풍부한 모션을 추가할 수 있는 라이브러리입니다. ConstraintLayout,을 기반으로 하며 ConstraintLayout을 사용하여 빌드할 수 있는 모든 항목을 애니메이션으로 만들 수 있습니다.
MotionLayout을 사용하여 동시에 여러 뷰의 위치, 크기, 공개 상태, 알파, 색상, 고도, 회전, 기타 속성에 애니메이션을 적용할 수 있습니다. 선언적 XML을 사용하면 코드로는 실행하기 어려운 여러 뷰가 함께 작동하는 애니메이션을 만들 수 있습니다.
애니메이션은 앱 환경을 개선하는 좋은 방법입니다. 애니메이션을 사용하여 다음 작업을 할 수 있습니다.
- 변경사항 표시: 상태 간 애니메이션을 사용하면 사용자가 UI의 변경사항을 자연스럽게 추적할 수 있습니다.
- 관심 유도: 애니메이션을 사용하여 중요한 UI 요소에 관심을 유도합니다.
- 멋진 디자인 빌드: 디자인의 효과적인 모션은 앱을 세련되게 보이도록 합니다.
기본 요건
이 Codelab은 Android 개발 경험이 있는 개발자를 대상으로 설계되었습니다. 이 Codelab을 완료하기 전에 다음을 수행해야 합니다.
- 활동과 기본 레이아웃으로 앱을 만들고 Android 스튜디오를 사용하여 기기나 에뮬레이터에서 실행하는 방법을 알아야 합니다.
ConstraintLayout에 익숙해야 합니다. 제약 조건 레이아웃 Codelab을 읽고ConstraintLayout에 관해 자세히 알아보세요.
실습할 내용
ConstraintSets및MotionLayout로 애니메이션 정의- 드래그 이벤트를 기반으로 애니메이션 적용
KeyPosition로 애니메이션 변경KeyAttribute로 속성 변경- 코드로 애니메이션 실행
MotionLayout로 접을 수 있는 헤더에 애니메이션 적용
필요한 항목
- Android 스튜디오 4.0 (
MotionLayout편집기는 이 버전의 Android 스튜디오에서만 작동함)
2. 시작하기
샘플 앱을 다운로드하려면 다음 중 하나를 실행하세요.
또는 다음 명령어를 사용하여 명령줄에서 GitHub 저장소를 클론합니다.
$ git clone https://github.com/googlecodelabs/motionlayout.git
3. MotionLayout으로 애니메이션 만들기
먼저 사용자가 클릭하면 화면의 상단 시작 부분에서 하단 끝으로 뷰를 이동하는 애니메이션을 빌드합니다.
시작 코드에서 애니메이션을 만들려면 다음 주요 요소가 필요합니다.
ConstraintLayout의 서브클래스인MotionLayout,MotionLayout태그 내에서 애니메이션을 적용할 모든 뷰를 지정합니다.MotionLayout.의 애니메이션을 설명하는 XML 파일인MotionScene,- 애니메이션 지속 시간, 트리거, 뷰 이동 방법을 지정하는
MotionScene의 일부인Transition, - 전환의 start 및 end 제약 조건을 모두 지정하는
ConstraintSet입니다.
MotionLayout부터 차례대로 살펴보겠습니다.
1단계: 기존 코드 살펴보기
MotionLayout은 ConstraintLayout의 서브클래스이므로 애니메이션을 추가하면서 동일한 기능을 모두 지원합니다. MotionLayout를 사용하려면 ConstraintLayout.를 사용할 위치에 MotionLayout 뷰를 추가합니다.
res/layout에서activity_step1.xml.을 엽니다. 여기에는 별의 단일ImageView이 있고 그 안에 색조가 적용된ConstraintLayout이 있습니다.
activity_step1.xml
<!-- initial code -->
<androidx.constraintlayout.widget.ConstraintLayout
...
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<ImageView
android:id="@+id/red_star"
...
/>
</androidx.constraintlayout.motion.widget.MotionLayout>
이 ConstraintLayout에는 제약 조건이 없으므로 지금 앱을 실행하면 별이 제약 조건 없이 표시되어 알 수 없는 위치에 배치됩니다. 제약 조건이 없으면 Android 스튜디오에 경고가 표시됩니다.
2단계: Motion Layout으로 변환
MotionLayout,를 사용하여 애니메이션을 적용하려면 ConstraintLayout를 MotionLayout로 변환해야 합니다.
레이아웃에서 모션 장면을 사용하려면 모션 장면을 가리켜야 합니다.
- 이렇게 하려면 디자인 화면을 엽니다. Android 스튜디오 4.0에서는 레이아웃 XML 파일을 볼 때 오른쪽 상단의 분할 또는 디자인 아이콘을 사용하여 디자인 화면을 엽니다.

- 디자인 화면을 연 후 미리보기를 마우스 오른쪽 버튼으로 클릭하고 Convert to MotionLayout을 선택합니다.

이렇게 하면 ConstraintLayout 태그가 MotionLayout 태그로 대체되고 @xml/activity_step1_scene.을 가리키는 motion:layoutDescription이 MotionLayout 태그에 추가됩니다.
activity_step1**.xml**
<!-- explore motion:layoutDescription="@xml/activity_step1_scene" -->
<androidx.constraintlayout.motion.widget.MotionLayout
...
motion:layoutDescription="@xml/activity_step1_scene">
모션 장면은 MotionLayout의 애니메이션을 설명하는 단일 XML 파일입니다.
MotionLayout로 변환하면 디자인 화면에 모션 편집기가 표시됩니다.

모션 편집기에는 세 가지 새로운 UI 요소가 있습니다.
- 개요: 애니메이션의 여러 부분을 선택할 수 있는 모달 선택입니다. 이 이미지에서는
startConstraintSet이 선택되어 있습니다.start과end사이의 화살표를 클릭하여 전환을 선택할 수도 있습니다. - 섹션 - 개요 아래에는 현재 선택된 개요 항목에 따라 변경되는 섹션 창이 있습니다. 이 이미지에서는 선택 창에
startConstraintSet정보가 표시됩니다. - 속성 – 속성 패널에는 개요 또는 선택 창에서 현재 선택된 항목의 속성이 표시되며, 이를 수정할 수 있습니다. 이 이미지에서는
startConstraintSet의 속성을 보여줍니다.
3단계: 시작 및 종료 제약 조건 정의하기
모든 애니메이션은 시작과 종료로 정의할 수 있습니다. 시작은 애니메이션 전 화면의 모습을 설명하고, 끝은 애니메이션이 완료된 후 화면의 모습을 설명합니다. MotionLayout은 시작 상태와 종료 상태 사이를 시간에 따라 애니메이션하는 방법을 파악합니다.
MotionScene는 ConstraintSet 태그를 사용하여 시작 및 종료 상태를 정의합니다. ConstraintSet는 뷰에 적용할 수 있는 제약 조건 집합입니다. 여기에는 너비, 높이, ConstraintLayout 제약 조건이 포함됩니다. alpha와 같은 속성도 포함됩니다. 뷰 자체는 포함되지 않고 뷰의 제약 조건만 포함됩니다.
ConstraintSet에 지정된 제약 조건은 레이아웃 파일에 지정된 제약 조건을 재정의합니다. 레이아웃과 MotionScene 모두에 제약 조건을 정의하면 MotionScene의 제약 조건만 적용됩니다.
이 단계에서는 별표 뷰가 화면의 상단 시작 부분에서 시작하고 화면의 하단 끝부분에서 끝나도록 제한합니다.
모션 편집기를 사용하거나 activity_step1_scene.xml 텍스트를 직접 수정하여 이 단계를 완료할 수 있습니다.
- 개요 패널에서
startConstraintSet을 선택합니다.

- 선택 패널에서
red_star를 선택합니다. 현재는layout의 소스가 표시됩니다. 즉, 이ConstraintSet에서 제한되지 않습니다. 오른쪽 상단의 연필 아이콘을 사용하여 제약 조건 만들기

- 개요 패널에서
startConstraintSet를 선택하면red_star에start소스가 표시되는지 확인합니다. - 속성 패널에서
startConstraintSet에red_star가 선택된 상태로 상단에 제약 조건을 추가하고 파란색 + 버튼을 클릭하여 시작합니다.

xml/activity_step1_scene.xml를 열어 동작 편집기에서 이 제약 조건에 대해 생성한 코드를 확인합니다.
activity_step1_scene.xml
<!-- Constraints to apply at the start of the animation -->
<ConstraintSet android:id="@+id/start">
<Constraint
android:id="@+id/red_star"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toTopOf="parent" />
</ConstraintSet>
ConstraintSet에는 @id/start의 id이 있으며 MotionLayout의 모든 뷰에 적용할 모든 제약 조건을 지정합니다. 이 MotionLayout에는 보기가 하나만 있으므로 Constraint 하나만 있으면 됩니다.
ConstraintSet 내의 Constraint는 제한하는 뷰의 ID인 @id/red_star를 지정합니다(activity_step1.xml에 정의됨). Constraint 태그는 제약 조건과 레이아웃 정보만 지정합니다. Constraint 태그는 ImageView에 적용되고 있음을 알지 못합니다.
이 제약 조건은 red_star 뷰를 상위 요소의 상단 시작으로 제한하는 데 필요한 높이, 너비, 기타 두 개의 제약 조건을 지정합니다.
- 개요 패널에서
endConstraintSet을 선택합니다.

- 이전과 동일한 단계에 따라
endConstraintSet에red_star의Constraint을 추가합니다. - 동작 편집기를 사용하여 이 단계를 완료하려면 파란색 + 버튼을 클릭하여
bottom및end에 제약 조건을 추가합니다.

- XML의 코드는 다음과 같습니다.
activitiy_step1_scene.xml
<!-- Constraints to apply at the end of the animation -->
<ConstraintSet android:id="@+id/end">
<Constraint
android:id="@+id/red_star"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintBottom_toBottomOf="parent" />
</ConstraintSet>
@id/start와 마찬가지로 이 ConstraintSet에는 @id/red_star에 단일 Constraint가 있습니다. 이번에는 화면 하단에만 표시되도록 제한합니다.
@id/start 및 @id/end로 이름을 지정하지 않아도 되지만 이렇게 하면 편리합니다.
4단계: 전환 정의하기
모든 MotionScene에는 하나 이상의 전환도 포함되어야 합니다. 전환은 시작부터 끝까지 하나의 애니메이션의 모든 부분을 정의합니다.
전환은 전환의 시작 및 종료 ConstraintSet를 지정해야 합니다. 전환은 애니메이션을 실행할 시간이나 뷰를 드래그하여 애니메이션을 적용하는 방법과 같은 다른 방식으로 애니메이션을 수정하는 방법을 지정할 수도 있습니다.
- Motion Editor는 MotionScene 파일을 만들 때 기본적으로 전환을 만들었습니다.
activity_step1_scene.xml를 열어 생성된 전환을 확인합니다.
activity_step1_scene.xml
<!-- A transition describes an animation via start and end state -->
<Transition
motion:constraintSetEnd="@+id/end"
motion:constraintSetStart="@id/start"
motion:duration="1000">
<KeyFrameSet>
</KeyFrameSet>
</Transition>
이것이 MotionLayout가 애니메이션을 빌드하는 데 필요한 모든 것입니다. 각 속성을 살펴보겠습니다.
- 애니메이션이 시작되면
constraintSetStart가 뷰에 적용됩니다. - 애니메이션이 끝날 때
constraintSetEnd가 뷰에 적용됩니다. duration은 애니메이션의 재생 시간을 밀리초 단위로 지정합니다.
그러면 MotionLayout이 시작 제약 조건과 종료 제약 조건 사이의 경로를 파악하고 지정된 기간 동안 애니메이션을 적용합니다.
5단계: 모션 편집기에서 애니메이션 미리보기

애니메이션: 모션 편집기에서 전환 미리보기를 재생하는 동영상
- 동작 편집기를 열고 개요 패널에서
start와end사이의 화살표를 클릭하여 전환을 선택합니다.

- 전환을 선택하면 선택 패널에 재생 컨트롤과 스크럽 막대가 표시됩니다. 재생을 클릭하거나 현재 위치를 드래그하여 애니메이션을 미리 봅니다.

6단계: 클릭 핸들러 추가
애니메이션을 시작하는 방법이 필요합니다. 이를 위한 한 가지 방법은 MotionLayout가 @id/red_star의 클릭 이벤트에 응답하도록 하는 것입니다.
- 동작 편집기를 열고 개요 패널의 시작과 끝 사이에 있는 화살표를 클릭하여 전환을 선택합니다.

- 개요 패널의 툴바에서
클릭 또는 스와이프 핸들러 만들기를 클릭합니다 . 이렇게 하면 전환을 시작하는 핸들러가 추가됩니다. - 팝업에서 클릭 핸들러를 선택합니다.

- 클릭할 때까지 표시를
red_star로 변경합니다.

- 추가를 클릭합니다. 클릭 핸들러는 Motion Editor의 전환에 작은 점으로 표시됩니다.

- 개요 패널에서 전환을 선택한 상태로 속성 패널에서 방금 추가한 OnClick 핸들러에
toggle의clickAction속성을 추가합니다.

activity_step1_scene.xml를 열어 모션 편집기에서 생성한 코드를 확인합니다.
activity_step1_scene.xml
<!-- A transition describes an animation via start and end state -->
<Transition
motion:constraintSetStart="@+id/start"
motion:constraintSetEnd="@+id/end"
motion:duration="1000">
<!-- MotionLayout will handle clicks on @id/red_star to "toggle" the animation between the start and end -->
<OnClick
motion:targetId="@id/red_star"
motion:clickAction="toggle" />
</Transition>
Transition는 <OnClick> 태그를 사용하여 클릭 이벤트에 대한 응답으로 애니메이션을 실행하도록 MotionLayout에 지시합니다. 각 속성을 살펴보겠습니다.
targetId은 클릭을 감시할 뷰입니다.clickAction/toggle은 클릭 시 시작 상태와 종료 상태 간에 전환됩니다.clickAction의 다른 옵션은 문서를 참고하세요.
- 코드를 실행하고 1단계를 클릭한 다음 빨간색 별을 클릭하여 애니메이션을 확인하세요.
5단계: 애니메이션 작동
앱을 실행합니다. 별을 클릭하면 애니메이션이 실행됩니다.

완료된 모션 장면 파일은 시작 및 종료 ConstraintSet를 가리키는 하나의 Transition를 정의합니다.
애니메이션이 시작될 때 (@id/start) 별 아이콘은 화면의 상단 시작 부분으로 제한됩니다. 애니메이션이 끝나면 (@id/end) 별 아이콘이 화면 하단에 고정됩니다.
<?xml version="1.0" encoding="utf-8"?>
<!-- Describe the animation for activity_step1.xml -->
<MotionScene xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android">
<!-- A transition describes an animation via start and end state -->
<Transition
motion:constraintSetStart="@+id/start"
motion:constraintSetEnd="@+id/end"
motion:duration="1000">
<!-- MotionLayout will handle clicks on @id/star to "toggle" the animation between the start and end -->
<OnClick
motion:targetId="@id/red_star"
motion:clickAction="toggle" />
</Transition>
<!-- Constraints to apply at the end of the animation -->
<ConstraintSet android:id="@+id/start">
<Constraint
android:id="@+id/red_star"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toTopOf="parent" />
</ConstraintSet>
<!-- Constraints to apply at the end of the animation -->
<ConstraintSet android:id="@+id/end">
<Constraint
android:id="@+id/red_star"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintBottom_toBottomOf="parent" />
</ConstraintSet>
</MotionScene>
4. 드래그 이벤트를 기반으로 애니메이션 적용
이 단계에서는 사용자가 화면을 스와이프할 때 애니메이션을 실행하는 사용자 드래그 이벤트에 응답하는 애니메이션을 빌드합니다. MotionLayout는 뷰를 이동하기 위한 터치 이벤트 추적과 모션을 유연하게 만들기 위한 물리 기반 플링 동작을 지원합니다.
1단계: 초기 코드 검사하기
- 시작하려면 기존
MotionLayout이 있는 레이아웃 파일activity_step2.xml를 엽니다. 코드를 살펴보세요.
activity_step2.xml
<!-- initial code -->
<androidx.constraintlayout.motion.widget.MotionLayout
...
motion:layoutDescription="@xml/step2" >
<ImageView
android:id="@+id/left_star"
...
/>
<ImageView
android:id="@+id/right_star"
...
/>
<ImageView
android:id="@+id/red_star"
...
/>
<TextView
android:id="@+id/credits"
...
motion:layout_constraintTop_toTopOf="parent"
motion:layout_constraintEnd_toEndOf="parent"/>
</androidx.constraintlayout.motion.widget.MotionLayout>
이 레이아웃은 애니메이션의 모든 뷰를 정의합니다. 별 아이콘 3개는 모션 장면에서 애니메이션 처리되므로 레이아웃에서 제한되지 않습니다.
TextView 크레딧은 애니메이션 전체에서 동일한 위치에 유지되고 속성을 수정하지 않으므로 제약 조건이 적용됩니다.
2단계: 장면 애니메이션 적용하기
마지막 애니메이션과 마찬가지로 애니메이션은 시작 및 종료 ConstraintSet,와 Transition로 정의됩니다.
시작 ConstraintSet 정의
- 모션 장면
xml/step2.xml을 열어 애니메이션을 정의합니다. - 시작 제약 조건
start의 제약 조건을 추가합니다. 처음에는 세 별이 모두 화면 하단 중앙에 있습니다. 오른쪽과 왼쪽 별의alpha값은0.0입니다. 즉, 완전히 투명하고 숨겨져 있습니다.
step2.xml
<!-- TODO apply starting constraints -->
<!-- Constraints to apply at the start of the animation -->
<ConstraintSet android:id="@+id/start">
<Constraint
android:id="@+id/red_star"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintBottom_toBottomOf="parent" />
<Constraint
android:id="@+id/left_star"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:alpha="0.0"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintBottom_toBottomOf="parent" />
<Constraint
android:id="@+id/right_star"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:alpha="0.0"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintBottom_toBottomOf="parent" />
</ConstraintSet>
이 ConstraintSet에서는 별마다 하나의 Constraint을 지정합니다. 각 제약 조건은 애니메이션이 시작될 때 MotionLayout에 의해 적용됩니다.
각 별 뷰는 시작, 종료, 하단 제약 조건을 사용하여 화면 하단에 중앙에 배치됩니다. 두 별 @id/left_star와 @id/right_star 모두 애니메이션 시작 시 적용되어 보이지 않게 만드는 알파 값이 추가로 있습니다.
start 및 end 제약 조건 세트는 애니메이션의 시작과 끝을 정의합니다. motion:layout_constraintStart_toStartOf와 같은 시작 제약 조건은 뷰의 시작을 다른 뷰의 시작으로 제한합니다. start라는 이름이 두 가지 모두에 사용되고 및 두 가지 모두 제약 조건의 컨텍스트에서 사용되므로 처음에는 혼동될 수 있습니다. 구분을 명확하게 하기 위해 layout_constraintStart의 start는 뷰의 '시작'을 나타냅니다. 이는 왼쪽에서 오른쪽으로 쓰는 언어의 경우 왼쪽이고 오른쪽에서 왼쪽으로 쓰는 언어의 경우 오른쪽입니다. start 제약 조건 세트는 애니메이션의 시작을 나타냅니다.
종료 ConstraintSet 정의
@id/credits아래에 세 별을 모두 함께 배치하는 체인을 사용하도록 끝 제약 조건을 정의합니다. 또한 왼쪽 및 오른쪽 별의alpha의 끝 값을1.0로 설정합니다.
step2.xml
<!-- TODO apply ending constraints -->
<!-- Constraints to apply at the end of the animation -->
<ConstraintSet android:id="@+id/end">
<Constraint
android:id="@+id/left_star"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:alpha="1.0"
motion:layout_constraintHorizontal_chainStyle="packed"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintEnd_toStartOf="@id/red_star"
motion:layout_constraintTop_toBottomOf="@id/credits" />
<Constraint
android:id="@+id/red_star"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
motion:layout_constraintStart_toEndOf="@id/left_star"
motion:layout_constraintEnd_toStartOf="@id/right_star"
motion:layout_constraintTop_toBottomOf="@id/credits" />
<Constraint
android:id="@+id/right_star"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:alpha="1.0"
motion:layout_constraintStart_toEndOf="@id/red_star"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintTop_toBottomOf="@id/credits" />
</ConstraintSet>
결과적으로 뷰가 애니메이션으로 표시될 때 중앙에서 위로 퍼져 나갑니다.
또한 alpha 속성이 ConstraintSets의 @id/right_start 및 @id/left_star에 모두 설정되어 있으므로 애니메이션이 진행됨에 따라 두 뷰가 모두 페이드 인됩니다.
사용자 스와이프에 따라 애니메이션 적용
MotionLayout은 사용자 드래그 이벤트나 스와이프를 추적하여 물리 기반 '플링' 애니메이션을 만들 수 있습니다. 즉, 사용자가 뷰를 빠르게 움직이면 뷰가 계속 움직이고 표면을 가로질러 굴러갈 때 실제 물체처럼 속도가 느려집니다. Transition에서 OnSwipe 태그를 사용하여 이러한 유형의 애니메이션을 추가할 수 있습니다.
OnSwipe태그를 추가하는 TODO를<OnSwipe motion:touchAnchorId="@id/red_star" />로 바꿉니다.
step2.xml
<!-- TODO add OnSwipe tag -->
<!-- A transition describes an animation via start and end state -->
<Transition
motion:constraintSetStart="@+id/start"
motion:constraintSetEnd="@+id/end">
<!-- MotionLayout will track swipes relative to this view -->
<OnSwipe motion:touchAnchorId="@id/red_star" />
</Transition>
OnSwipe에는 몇 가지 속성이 포함되어 있으며 그중 가장 중요한 속성은 touchAnchorId입니다.
touchAnchorId는 터치에 따라 이동하는 추적된 뷰입니다.MotionLayout는 스와이프하는 손가락에서 이 뷰를 동일한 거리로 유지합니다.touchAnchorSide는 뷰의 어느 쪽을 추적해야 하는지 결정합니다. 크기가 조절되거나 복잡한 경로를 따르거나 한쪽이 다른 쪽보다 빠르게 이동하는 뷰의 경우 특히 중요합니다.dragDirection는 이 애니메이션에 중요한 방향 (위, 아래, 왼쪽 또는 오른쪽)을 결정합니다.
MotionLayout가 드래그 이벤트를 수신 대기하면 리스너는 touchAnchorId에 의해 지정된 뷰가 아닌 MotionLayout 뷰에 등록됩니다. 사용자가 화면의 아무 곳에서나 동작을 시작하면 MotionLayout은 손가락과 touchAnchorId 뷰의 touchAnchorSide 사이의 거리를 일정하게 유지합니다. 예를 들어 앵커 측면에서 100dp 떨어진 곳을 터치하면 MotionLayout는 애니메이션 전체에서 해당 측면을 손가락에서 100dp 떨어진 곳에 유지합니다.
사용해 보기
- 앱을 다시 실행하고 2단계 화면을 엽니다. 애니메이션이 표시됩니다.
- 애니메이션 중간에 손가락을 떼거나 '던져'서
MotionLayout가 유연한 물리 기반 애니메이션을 어떻게 표시하는지 살펴보세요.

MotionLayout는 ConstraintLayout의 기능을 사용하여 풍부한 효과를 만들어 매우 다른 디자인 간에 애니메이션을 적용할 수 있습니다.
이 애니메이션에서는 세 뷰가 모두 화면 하단의 상위 요소에 상대적으로 배치되어 시작합니다. 결과적으로 세 개의 뷰가 @id/credits을 기준으로 체인에 배치됩니다.
레이아웃이 매우 다르지만 MotionLayout는 시작과 끝 사이에 부드러운 애니메이션을 만듭니다.
5. 경로 수정
이 단계에서는 애니메이션 중에 복잡한 경로를 따르고 움직이는 동안 크레딧에 애니메이션을 적용하는 애니메이션을 빌드합니다. MotionLayout는 KeyPosition를 사용하여 시작과 끝 사이에서 뷰가 이동하는 경로를 수정할 수 있습니다.
1단계: 기존 코드 살펴보기
layout/activity_step3.xml및xml/step3.xml을 열어 기존 레이아웃과 모션 장면을 확인합니다.ImageView및TextView는 달과 크레딧 텍스트를 표시합니다.- 모션 장면 파일 (
xml/step3.xml)을 엽니다.@id/start에서@id/end로의Transition이 정의되어 있습니다. 이 애니메이션은 두 개의ConstraintSets를 사용하여 달 이미지를 화면 왼쪽 하단에서 화면 오른쪽 하단으로 이동합니다. 달이 움직일 때 크레딧 텍스트가alpha="0.0"에서alpha="1.0"으로 페이드 인됩니다. - 지금 앱을 실행하고 3단계를 선택합니다. 달을 클릭하면 달이 시작부터 끝까지 선형 경로 (또는 직선)를 따라 이동하는 것을 확인할 수 있습니다.
2단계: 경로 디버깅 사용 설정하기
달의 움직임에 호를 추가하기 전에 MotionLayout에서 경로 디버깅을 사용 설정하는 것이 좋습니다.
MotionLayout로 복잡한 애니메이션을 개발하는 데 도움이 되도록 각 뷰의 애니메이션 경로를 그릴 수 있습니다. 애니메이션을 시각화하고 동작의 세부사항을 미세 조정하려는 경우에 유용합니다.
- 디버깅 경로를 사용 설정하려면
layout/activity_step3.xml를 열고MotionLayout태그에motion:motionDebug="SHOW_PATH"를 추가합니다.
activity_step3.xml
<!-- Add motion:motionDebug="SHOW_PATH" -->
<androidx.constraintlayout.motion.widget.MotionLayout
...
motion:motionDebug="SHOW_PATH" >
경로 디버깅을 사용 설정한 후 앱을 다시 실행하면 모든 뷰의 경로가 점선으로 시각화됩니다.

- 원은 한 뷰의 시작 또는 끝 위치를 나타냅니다.
- 선은 하나의 뷰의 경로를 나타냅니다.
- 다이아몬드는 경로를 수정하는
KeyPosition를 나타냅니다.
예를 들어 이 애니메이션에서 가운데 원은 크레딧 텍스트의 위치입니다.
3단계: 경로 수정하기
MotionLayout의 모든 애니메이션은 애니메이션이 시작되기 전과 애니메이션이 완료된 후 화면이 어떻게 표시되는지 정의하는 시작 및 종료 ConstraintSet에 의해 정의됩니다. 기본적으로 MotionLayout는 위치가 변경되는 각 뷰의 시작 위치와 종료 위치 사이에 선형 경로 (직선)를 그립니다.
이 예의 달의 호와 같은 복잡한 경로를 빌드하기 위해 MotionLayout는 KeyPosition를 사용하여 뷰가 시작과 끝 사이에서 취하는 경로를 수정합니다.
xml/step3.xml를 열고 장면에KeyPosition를 추가합니다.KeyPosition태그는Transition태그 내에 배치됩니다.

step3.xml
<!-- TODO: Add KeyFrameSet and KeyPosition -->
<KeyFrameSet>
<KeyPosition
motion:framePosition="50"
motion:motionTarget="@id/moon"
motion:keyPositionType="parentRelative"
motion:percentY="0.5"
/>
</KeyFrameSet>
KeyFrameSet는 Transition의 하위 요소이며 전환 중에 적용해야 하는 모든 KeyFrames(예: KeyPosition)의 집합입니다.
MotionLayout는 시작과 끝 사이의 달 경로를 계산하므로 KeyFrameSet에 지정된 KeyPosition에 따라 경로를 수정합니다. 앱을 다시 실행하여 경로가 어떻게 수정되는지 확인할 수 있습니다.
KeyPosition에는 경로를 수정하는 방법을 설명하는 여러 속성이 있습니다. 가장 중요한 것은 다음과 같습니다.
framePosition은 0~100 사이의 숫자입니다. 애니메이션에서 이KeyPosition를 적용해야 하는 시점을 정의합니다. 1은 애니메이션의 1% 이고 99는 애니메이션의 99% 입니다. 따라서 값이 50이면 중앙에 적용합니다.motionTarget는 이KeyPosition이 경로를 수정하는 뷰입니다.keyPositionType는 이KeyPosition가 경로를 수정하는 방식입니다.parentRelative,pathRelative또는deltaRelative일 수 있습니다 (다음 단계에서 설명).percentX | percentY는framePosition에서 경로를 수정할 양입니다 (0.0~1.0 사이의 값, 음수 값 및 1보다 큰 값 허용).
다음과 같이 생각하면 됩니다. 'framePosition 에서 motionTarget 의 경로를 percentX 또는 percentY 에 따라 keyPositionType에서 결정된 좌표로 이동하여 수정합니다.'
기본적으로 MotionLayout는 경로를 수정하여 도입된 모든 모서리를 둥글게 처리합니다. 방금 만든 애니메이션을 보면 달이 굽은 부분에서 곡선 경로를 따라가는 것을 확인할 수 있습니다. 대부분의 애니메이션에서 이 동작이 바람직하며, 그렇지 않은 경우 curveFit 속성을 지정하여 맞춤설정할 수 있습니다.
사용해 보기
앱을 다시 실행하면 이 단계의 애니메이션이 표시됩니다.

달은 Transition에 지정된 KeyPosition을 통과하므로 호를 따라 이동합니다.
<KeyPosition
motion:framePosition="50"
motion:motionTarget="@id/moon"
motion:keyPositionType="parentRelative"
motion:percentY="0.5"
/>
이 KeyPosition는 'framePosition 50 (애니메이션 중간)에서 parentRelative (전체 MotionLayout)에 의해 결정된 좌표에 따라 50% Y (화면 아래쪽 중간)만큼 이동하여 motionTarget @id/moon의 경로를 수정합니다'로 읽을 수 있습니다.
따라서 애니메이션 중간에 달이 화면 아래쪽 50% 에 있는 KeyPosition를 통과해야 합니다. 이 KeyPosition은 X 동작을 전혀 수정하지 않으므로 달은 여전히 시작부터 끝까지 수평으로 이동합니다. MotionLayout는 시작과 끝 사이를 이동할 때 이 KeyPosition를 통과하는 부드러운 경로를 파악합니다.
자세히 보면 크레딧 텍스트가 달의 위치에 의해 제한됩니다. 왜 세로로도 움직이지 않나요?

<Constraint
android:id="@id/credits"
...
motion:layout_constraintBottom_toBottomOf="@id/moon"
motion:layout_constraintTop_toTopOf="@id/moon"
/>
달이 이동하는 경로를 수정하더라도 달의 시작 및 종료 위치는 수직으로 전혀 이동하지 않습니다. KeyPosition는 시작 또는 종료 위치를 수정하지 않으므로 크레딧 텍스트는 달의 최종 종료 위치로 제한됩니다.
크레딧이 달과 함께 이동하도록 하려면 크레딧에 KeyPosition을 추가하거나 @id/credits의 시작 제약 조건을 수정하면 됩니다.
다음 섹션에서는 MotionLayout의 다양한 keyPositionType 유형을 살펴봅니다.
6. keyPositionType 이해
마지막 단계에서는 parentRelative의 keyPosition 유형을 사용하여 화면의 50% 만큼 경로를 오프셋했습니다. keyPositionType 속성은 MotionLayout이 percentX 또는 percentY에 따라 경로를 어떻게 수정할지 결정합니다.
<KeyFrameSet>
<KeyPosition
motion:framePosition="50"
motion:motionTarget="@id/moon"
motion:keyPositionType="parentRelative"
motion:percentY="0.5"
/>
</KeyFrameSet>
keyPosition에는 parentRelative, pathRelative, deltaRelative의 세 가지 유형이 있습니다. 유형을 지정하면 percentX 및 percentY이 계산되는 좌표계가 변경됩니다.
좌표계란 무엇인가요?
좌표계는 공간의 점을 지정하는 방법을 제공합니다. 화면의 위치를 설명하는 데도 유용합니다.
MotionLayout 좌표계는 직교 좌표계입니다. 즉, 두 개의 수직선으로 정의된 X축과 Y축이 있습니다. 두 좌표계의 주요 차이점은 화면에서 X축이 어디로 향하는지입니다 (Y축은 항상 X축에 수직임).
MotionLayout의 모든 좌표 시스템은 X축과 Y축 모두에서 0.0과 1.0 사이의 값을 사용합니다. 음수 값과 1.0보다 큰 값을 허용합니다. 예를 들어 percentX 값이 -2.0이면 X축의 반대 방향으로 두 번 이동합니다.
이 모든 것이 대수학 수업과 너무 비슷하게 들린다면 아래 그림을 확인하세요.
parentRelative 좌표

parentRelative의 keyPositionType는 화면과 동일한 좌표계를 사용합니다. (0, 0)는 전체 MotionLayout의 왼쪽 상단에 정의되고 (1, 1)는 오른쪽 하단에 정의됩니다.
이 예의 달 호와 같이 전체 MotionLayout을 통과하는 애니메이션을 만들고 싶을 때마다 parentRelative을 사용할 수 있습니다.
하지만 동작과 관련된 경로를 수정하려는 경우(예: 약간의 곡선 만들기) 다른 두 좌표계가 더 적합합니다.
deltaRelative 좌표

델타는 변화를 나타내는 수학 용어이므로 deltaRelative는 '상대적 변화'를 나타내는 방법입니다. deltaRelative 좌표에서 (0,0)은 뷰의 시작 위치이고 (1,1)은 종료 위치입니다. X축과 Y축은 화면에 맞춰 정렬됩니다.
X축은 항상 화면에서 가로이고 Y축은 항상 화면에서 세로입니다. parentRelative와 비교했을 때 주요 차이점은 좌표가 뷰가 이동할 화면의 일부만 설명한다는 점입니다.
deltaRelative는 수평 또는 수직 모션을 격리하여 제어하는 데 적합한 좌표계입니다. 예를 들어 50%에서 세로 (Y) 이동만 완료되고 가로 (X)로 계속 애니메이션이 적용되는 애니메이션을 만들 수 있습니다.
pathRelative 좌표

MotionLayout의 마지막 좌표계는 pathRelative입니다. X축이 시작부터 끝까지 모션 경로를 따르므로 다른 두 축과는 상당히 다릅니다. 따라서 (0,0)는 시작 위치이고 (1,0)는 종료 위치입니다.
이 기능을 사용하면 어떤 이점이 있나요? 특히 이 좌표계가 화면 좌표계와 정렬되지 않기 때문에 처음에는 매우 놀랍습니다.
pathRelative은 몇 가지 용도로 매우 유용합니다.
- 애니메이션의 일부 동안 뷰의 속도를 높이거나 낮추거나 중지합니다. X 차원은 항상 뷰가 정확히 취하는 경로와 일치하므로
pathRelativeKeyPosition를 사용하여 해당 경로의 특정 지점에 도달하는framePosition를 변경할 수 있습니다. 따라서framePosition="50"에KeyPosition가 있고percentX="0.1"가 있으면 애니메이션이 동작의 처음 10% 를 이동하는 데 시간의 50% 가 걸립니다. - 경로에 미묘한 호 추가 Y 치수는 항상 동작에 수직이므로 Y를 변경하면 전체 동작에 대한 경로가 곡선으로 변경됩니다.
deltaRelative가 작동하지 않는 경우 두 번째 측정기준을 추가할 수 없습니다. 완전히 수평 및 수직 동작의 경우deltaRelative는 유용한 측정기준을 하나만 만듭니다. 하지만pathRelative는 항상 사용할 수 있는 X 및 Y 좌표를 만듭니다.
다음 단계에서는 두 개 이상의 KeyPosition를 사용하여 훨씬 더 복잡한 경로를 빌드하는 방법을 알아봅니다.
7. 복잡한 경로 빌드
이전 단계에서 만든 애니메이션을 보면 부드러운 곡선이 만들어지지만 모양이 '달'과 같지 않습니다.
여러 KeyPosition 요소로 경로 수정
MotionLayout는 움직임을 얻기 위해 필요한 만큼 KeyPosition를 정의하여 경로를 추가로 수정할 수 있습니다. 이 애니메이션에서는 호를 만들지만 원하는 경우 화면 중앙에서 달이 위아래로 점프하도록 만들 수도 있습니다.
xml/step4.xml를 엽니다. 마지막 단계에서 추가한KeyFrame와 동일한 뷰가 표시됩니다.- 곡선의 상단을 둥글게 하려면
@id/moon경로에KeyPositions를 두 개 더 추가합니다. 하나는 상단에 도달하기 직전에, 하나는 상단에 도달한 후에 추가합니다.

step4.xml
<!-- TODO: Add two more KeyPositions to the KeyFrameSet here -->
<KeyPosition
motion:framePosition="25"
motion:motionTarget="@id/moon"
motion:keyPositionType="parentRelative"
motion:percentY="0.6"
/>
<KeyPosition
motion:framePosition="75"
motion:motionTarget="@id/moon"
motion:keyPositionType="parentRelative"
motion:percentY="0.6"
/>
이러한 KeyPositions는 애니메이션의 25% 와 75% 에 적용되며 @id/moon이 화면 상단에서 60% 떨어진 경로를 따라 이동하도록 합니다. 기존 KeyPosition 50%와 결합하면 달이 따라갈 수 있는 부드러운 호가 만들어집니다.
MotionLayout에서는 원하는 동작 경로를 얻기 위해 필요한 만큼 KeyPositions를 추가할 수 있습니다. MotionLayout는 지정된 framePosition에 각 KeyPosition을 적용하고 모든 KeyPositions을 통과하는 부드러운 동작을 만드는 방법을 파악합니다.
사용해 보기
- 앱을 다시 실행합니다. 4단계로 이동하여 애니메이션이 작동하는지 확인합니다. 달을 클릭하면 시작부터 끝까지의 경로를 따라
KeyFrameSet에 지정된 각KeyPosition를 통과합니다.
직접 탐색하기
다른 유형의 KeyFrame로 이동하기 전에 KeyFrameSet에 KeyPositions를 더 추가하여 KeyPosition만 사용하여 어떤 효과를 만들 수 있는지 확인해 보세요.
다음은 애니메이션 중에 앞뒤로 움직이는 복잡한 경로를 만드는 방법을 보여주는 예입니다.

step4.xml
<!-- Complex paths example: Dancing moon -->
<KeyFrameSet>
<KeyPosition
motion:framePosition="25"
motion:motionTarget="@id/moon"
motion:keyPositionType="parentRelative"
motion:percentY="0.6"
motion:percentX="0.1"
/>
<KeyPosition
motion:framePosition="50"
motion:motionTarget="@id/moon"
motion:keyPositionType="parentRelative"
motion:percentY="0.5"
motion:percentX="0.3"
/>
<KeyPosition
motion:framePosition="75"
motion:motionTarget="@id/moon"
motion:keyPositionType="parentRelative"
motion:percentY="0.6"
motion:percentX="0.1"
/>
</KeyFrameSet>
KeyPosition를 모두 살펴본 후 다음 단계에서는 다른 유형의 KeyFrames로 이동합니다.
8. 동작 중 속성 변경
동적 애니메이션을 빌드하는 것은 애니메이션이 진행됨에 따라 뷰의 size, rotation 또는 alpha를 변경하는 것을 의미합니다. MotionLayout는 KeyAttribute를 사용하여 뷰의 여러 속성을 애니메이션으로 표시하는 것을 지원합니다.
이 단계에서는 KeyAttribute를 사용하여 달의 크기를 조정하고 회전합니다. 또한 KeyAttribute를 사용하여 달이 거의 이동을 완료할 때까지 텍스트의 표시를 지연합니다.
1단계: KeyAttribute로 크기 조절 및 회전
- 이전 단계에서 빌드한 것과 동일한 애니메이션이 포함된
xml/step5.xml을 엽니다. 다양성을 위해 이 화면에서는 다른 우주 사진을 배경으로 사용합니다. - 달이 크기가 커지고 회전하도록 하려면
keyFrame="50"및keyFrame="100"의KeyFrameSet에KeyAttribute태그 두 개를 추가합니다.

step5.xml
<!-- TODO: Add KeyAttributes to rotate and resize @id/moon -->
<KeyAttribute
motion:framePosition="50"
motion:motionTarget="@id/moon"
android:scaleY="2.0"
android:scaleX="2.0"
android:rotation="-360"
/>
<KeyAttribute
motion:framePosition="100"
motion:motionTarget="@id/moon"
android:rotation="-720"
/>
이러한 KeyAttributes는 애니메이션의 50% 및 100% 에 적용됩니다. 50% 에서의 첫 번째 KeyAttribute는 호의 상단에서 발생하며 뷰의 크기가 두 배로 늘어나고 -360도 (또는 한 바퀴) 회전합니다. 두 번째 KeyAttribute는 두 번째 회전을 -720도 (두 바퀴)로 완료하고 scaleX 및 scaleY 값이 기본적으로 1.0이므로 크기를 다시 일반 크기로 축소합니다.
KeyPosition와 마찬가지로 KeyAttribute는 framePosition 및 motionTarget를 사용하여 KeyFrame를 적용할 시점과 수정할 뷰를 지정합니다. MotionLayout은 KeyPositions 사이를 보간하여 유연한 애니메이션을 만듭니다.
모든 뷰에 적용할 수 있는 KeyAttributes 지원 속성 visibility, alpha, elevation와 같은 기본 속성의 변경을 지원합니다. 여기에서와 같이 회전을 변경하거나, rotateX 및 rotateY로 3차원으로 회전하거나, scaleX 및 scaleY로 크기를 조정하거나, X, Y, Z에서 뷰의 위치를 변환할 수도 있습니다.
2단계: 크레딧 표시 지연하기
이 단계의 목표 중 하나는 애니메이션이 거의 완료될 때까지 크레딧 텍스트가 표시되지 않도록 애니메이션을 업데이트하는 것입니다.
- 크레딧 표시를 지연하려면
keyPosition="85"까지alpha이 0으로 유지되도록 하는KeyAttribute을 하나 더 정의합니다.MotionLayout는 여전히 0에서 100 알파로 부드럽게 전환되지만 애니메이션의 마지막 15% 에 걸쳐 전환됩니다.
step5.xml
<!-- TODO: Add KeyAttribute to delay the appearance of @id/credits -->
<KeyAttribute
motion:framePosition="85"
motion:motionTarget="@id/credits"
android:alpha="0.0"
/>
이 KeyAttribute는 애니메이션의 처음 85% 동안 @id/credits의 alpha을 0.0으로 유지합니다. 알파가 0에서 시작하므로 애니메이션의 처음 85% 동안은 표시되지 않습니다.
이 KeyAttribute의 최종 효과는 크레딧이 애니메이션이 끝날 무렵에 표시된다는 것입니다. 이렇게 하면 달이 화면 오른쪽 하단에 자리 잡은 것처럼 보입니다.
이와 같이 다른 뷰가 이동하는 동안 한 뷰의 애니메이션을 지연하면 사용자에게 동적으로 느껴지는 인상적인 애니메이션을 빌드할 수 있습니다.
사용해 보기
- 앱을 다시 실행하고 5단계로 이동하여 애니메이션이 작동하는지 확인합니다. 달을 클릭하면 시작부터 끝까지의 경로를 따라
KeyFrameSet에 지정된 각KeyAttribute를 통과합니다.

달을 두 바퀴 돌리므로 이제 달이 두 번 뒤로 회전하고 애니메이션이 거의 끝날 때까지 크레딧이 표시되지 않습니다.
직접 살펴보기
KeyFrame의 최종 유형으로 이동하기 전에 KeyAttributes에서 다른 표준 속성을 수정해 보세요. 예를 들어 rotation를 rotationX로 변경하여 어떤 애니메이션이 생성되는지 확인해 보세요.
다음은 시도해 볼 수 있는 표준 속성 목록입니다.
android:visibilityandroid:alphaandroid:elevationandroid:rotationandroid:rotationXandroid:rotationYandroid:scaleXandroid:scaleYandroid:translationXandroid:translationYandroid:translationZ
9. 맞춤 속성 변경
리치 애니메이션은 뷰의 색상이나 기타 속성을 변경하는 것을 말합니다. MotionLayout는 KeyAttribute를 사용하여 이전 작업에 나열된 표준 속성을 변경할 수 있지만 CustomAttribute를 사용하여 다른 속성을 지정합니다.
CustomAttribute를 사용하여 설정자가 있는 값을 설정할 수 있습니다. 예를 들어 CustomAttribute를 사용하여 View의 backgroundColor를 설정할 수 있습니다. MotionLayout는 리플렉션을 사용하여 설정자를 찾은 다음 이를 반복적으로 호출하여 뷰를 애니메이션화합니다.
이 단계에서는 CustomAttribute를 사용하여 달의 colorFilter 속성을 설정하여 아래와 같은 애니메이션을 빌드합니다.

맞춤 속성 정의
- 시작하려면 마지막 단계에서 빌드한 것과 동일한 애니메이션이 포함된
xml/step6.xml을 엽니다. - 달이 색상을 변경하도록 하려면
keyFrame="0",keyFrame="50",keyFrame="100".의KeyFrameSet에CustomAttribute이 있는KeyAttribute두 개를 추가합니다.

step6.xml
<!-- TODO: Add Custom attributes here -->
<KeyAttribute
motion:framePosition="0"
motion:motionTarget="@id/moon">
<CustomAttribute
motion:attributeName="colorFilter"
motion:customColorValue="#FFFFFF"
/>
</KeyAttribute>
<KeyAttribute
motion:framePosition="50"
motion:motionTarget="@id/moon">
<CustomAttribute
motion:attributeName="colorFilter"
motion:customColorValue="#FFB612"
/>
</KeyAttribute>
<KeyAttribute
motion:framePosition="100"
motion:motionTarget="@id/moon">
<CustomAttribute
motion:attributeName="colorFilter"
motion:customColorValue="#FFFFFF"
/>
</KeyAttribute>
KeyAttribute 내부에 CustomAttribute를 추가합니다. CustomAttribute은 KeyAttribute에 의해 지정된 framePosition에 적용됩니다.
CustomAttribute 내에서 attributeName과 설정할 값을 하나 지정해야 합니다.
motion:attributeName은 이 맞춤 속성에서 호출할 setter의 이름입니다. 이 예시에서는Drawable에서setColorFilter이 호출됩니다.motion:custom*Value은 이름에 표시된 유형의 맞춤 값입니다. 이 예에서는 맞춤 값이 지정된 색상입니다.
맞춤 값은 다음 유형 중 하나일 수 있습니다.
- 색상
- 정수
- 부동 소수점 수
- 문자열
- 측정기준
- 불리언
이 API를 사용하면 MotionLayout가 모든 뷰에서 설정자를 제공하는 항목을 애니메이션으로 만들 수 있습니다.
사용해 보기
- 앱을 다시 실행하고 6단계로 이동하여 애니메이션이 작동하는지 확인합니다. 달을 클릭하면 시작부터 끝까지의 경로를 따라
KeyFrameSet에 지정된 각KeyAttribute를 통과합니다.

KeyFrames를 추가하면 MotionLayout가 달의 경로를 직선에서 복잡한 곡선으로 변경하여 애니메이션 중간에 더블 백플립, 크기 조절, 색상 변경이 추가됩니다.
실제 애니메이션에서는 여러 뷰를 동시에 애니메이션 처리하여 다양한 경로와 속도로 움직임을 제어하는 경우가 많습니다. 각 뷰에 다른 KeyFrame를 지정하면 MotionLayout로 여러 뷰를 애니메이션 처리하는 풍부한 애니메이션을 구성할 수 있습니다.
10. 드래그 이벤트 및 복잡한 경로
이 단계에서는 복잡한 경로와 함께 OnSwipe를 사용하는 방법을 알아봅니다. 지금까지 달의 애니메이션은 OnClick 리스너에 의해 트리거되었으며 고정된 시간 동안 실행되었습니다.
OnSwipe를 사용하여 복잡한 경로가 있는 애니메이션(예: 지난 몇 단계에서 빌드한 달 애니메이션)을 제어하려면 OnSwipe의 작동 방식을 이해해야 합니다.
1단계: OnSwipe 동작 살펴보기
xml/step7.xml를 열고 기존OnSwipe선언을 찾습니다.
step7.xml
<!-- Fix OnSwipe by changing touchAnchorSide →
<OnSwipe
motion:touchAnchorId="@id/moon"
motion:touchAnchorSide="bottom"
/>
- 기기에서 앱을 실행하고 7단계로 이동합니다. 호의 경로를 따라 달을 드래그하여 부드러운 애니메이션을 만들 수 있는지 확인합니다.
이 애니메이션을 실행하면 보기에 좋지 않습니다. 달이 호의 꼭대기에 도달하면 달이 이리저리 움직이기 시작합니다.

버그를 이해하려면 사용자가 호의 상단 바로 아래를 터치할 때 어떤 일이 발생하는지 생각해 보세요. OnSwipe 태그에 motion:touchAnchorSide="bottom"이 있으므로 MotionLayout는 애니메이션 전반에 걸쳐 손가락과 뷰 하단 사이의 거리를 일정하게 유지하려고 합니다.
하지만 달의 하단이 항상 같은 방향으로 이동하는 것은 아니므로 위로 올라갔다가 다시 내려오기 때문에 사용자가 호의 상단을 지나면 MotionLayout는 어떻게 해야 할지 알 수 없습니다. 이를 고려할 때 사용자가 여기를 터치하면 달의 하단이 어디에 배치되어야 할까요?

2단계: 오른쪽 사용하기
이러한 버그를 방지하려면 전체 애니메이션 기간 동안 항상 한 방향으로 진행되는 touchAnchorId 및 touchAnchorSide를 선택하는 것이 중요합니다.
이 애니메이션에서는 달의 right 쪽과 left 쪽이 모두 한 방향으로 화면을 가로질러 진행됩니다.
하지만 bottom와 top의 방향은 반대로 바뀝니다. OnSwipe가 이를 추적하려고 하면 방향이 바뀔 때 혼동됩니다.
- 이 애니메이션이 터치 이벤트를 따르도록 하려면
touchAnchorSide를right로 변경합니다.
step7.xml
<!-- Fix OnSwipe by changing touchAnchorSide →
<OnSwipe
motion:touchAnchorId="@id/moon"
motion:touchAnchorSide="right"
/>
3단계: dragDirection 사용
dragDirection를 touchAnchorSide와 결합하여 사이드 트랙이 평소와 다른 방향으로 이동하도록 할 수도 있습니다. touchAnchorSide이 한 방향으로만 진행되는 것은 여전히 중요하지만 MotionLayout에 추적할 방향을 알려줄 수 있습니다. 예를 들어 touchAnchorSide="bottom"는 유지하되 dragDirection="dragRight"를 추가할 수 있습니다. 이렇게 하면 MotionLayout가 뷰 하단의 위치를 추적하지만 오른쪽으로 이동할 때만 위치를 고려합니다 (세로 모션은 무시됨). 따라서 하단이 위아래로 움직이더라도 OnSwipe를 사용하면 올바르게 애니메이션이 적용됩니다.
- 달의 움직임을 올바르게 추적하도록
OnSwipe업데이트
step7.xml
<!-- Using dragDirection to control the direction of drag tracking →
<OnSwipe
motion:touchAnchorId="@id/moon"
motion:touchAnchorSide="bottom"
motion:dragDirection="dragRight"
/>
사용해 보기
- 앱을 다시 실행하고 달을 전체 경로로 드래그해 보세요. 복잡한 호를 따르더라도
MotionLayout는 스와이프 이벤트에 대한 응답으로 애니메이션을 진행할 수 있습니다.

11. 코드로 동작 실행
MotionLayout는 CoordinatorLayout와 함께 사용하면 풍부한 애니메이션을 빌드하는 데 사용할 수 있습니다. 이 단계에서는 MotionLayout를 사용하여 접을 수 있는 헤더를 빌드합니다.
1단계: 기존 코드 살펴보기
- 시작하려면
layout/activity_step8.xml을 엽니다. layout/activity_step8.xml에서 작동하는CoordinatorLayout및AppBarLayout가 이미 빌드되어 있습니다.
activity_step8.xml
<androidx.coordinatorlayout.widget.CoordinatorLayout
...>
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar_layout"
android:layout_width="match_parent"
android:layout_height="180dp">
<androidx.constraintlayout.motion.widget.MotionLayout
android:id="@+id/motion_layout"
... >
...
</androidx.constraintlayout.motion.widget.MotionLayout>
</com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.NestedScrollView
...
motion:layout_behavior="@string/appbar_scrolling_view_behavior" >
...
</androidx.core.widget.NestedScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
이 레이아웃은 CoordinatorLayout을 사용하여 NestedScrollView와 AppBarLayout 간에 스크롤 정보를 공유합니다. 따라서 NestedScrollView이 위로 스크롤되면 AppBarLayout에 변경사항을 알립니다. 이와 같은 접히는 툴바를 Android에서 구현하는 방법은 텍스트의 스크롤이 접히는 헤더와 '조정'되도록 하는 것입니다.
@id/motion_layout가 가리키는 동작 장면은 마지막 단계의 동작 장면과 유사합니다. 하지만 CoordinatorLayout와 함께 작동할 수 있도록 OnSwipe 선언이 삭제되었습니다.
- 앱을 실행하고 8단계로 이동합니다. 텍스트를 스크롤해도 달이 움직이지 않습니다.
2단계: MotionLayout 스크롤하기
NestedScrollView이 스크롤되는 즉시MotionLayout뷰가 스크롤되도록 하려면MotionLayout에motion:minHeight와motion:layout_scrollFlags를 추가합니다.
activity_step8.xml
<!-- Add minHeight and layout_scrollFlags to the MotionLayout -->
<androidx.constraintlayout.motion.widget.MotionLayout
android:id="@+id/motion_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
motion:layoutDescription="@xml/step8"
motion:motionDebug="SHOW_PATH"
android:minHeight="80dp"
motion:layout_scrollFlags="scroll|enterAlways|snap|exitUntilCollapsed" >
- 앱을 다시 실행하고 8단계로 이동합니다. 위로 스크롤하면
MotionLayout가 축소됩니다. 하지만 아직 스크롤 동작에 따라 애니메이션이 진행되지는 않습니다.
3단계: 코드로 동작 이동하기
Step8Activity.kt을 엽니다 . 스크롤 위치의 변경사항을MotionLayout에 알리도록coordinateMotion()함수를 수정합니다.
Step8Activity.kt
// TODO: set progress of MotionLayout based on an AppBarLayout.OnOffsetChangedListener
private fun coordinateMotion() {
val appBarLayout: AppBarLayout = findViewById(R.id.appbar_layout)
val motionLayout: MotionLayout = findViewById(R.id.motion_layout)
val listener = AppBarLayout.OnOffsetChangedListener { unused, verticalOffset ->
val seekPosition = -verticalOffset / appBarLayout.totalScrollRange.toFloat()
motionLayout.progress = seekPosition
}
appBarLayout.addOnOffsetChangedListener(listener)
}
이 코드는 사용자가 현재 스크롤 오프셋으로 스크롤할 때마다 호출되는 OnOffsetChangedListener를 등록합니다.
MotionLayout는 진행률 속성을 설정하여 전환을 검색하는 것을 지원합니다. verticalOffset와 백분율 진행률 간에 변환하려면 총 스크롤 범위로 나눕니다.
사용해 보기
- 앱을 다시 배포하고 8단계 애니메이션을 실행합니다.
MotionLayout이 스크롤 위치에 따라 애니메이션을 진행합니다.

MotionLayout를 사용하여 맞춤 동적 접기 툴바 애니메이션을 빌드할 수 있습니다. KeyFrames 시퀀스를 사용하면 매우 대담한 효과를 얻을 수 있습니다.
12. 축하합니다
이 Codelab에서는 MotionLayout의 기본 API를 다루었습니다.
MotionLayout의 실제 사용 사례를 더 보려면 공식 샘플을 확인하세요. 문서도 확인해 보세요.
자세히 알아보기
MotionLayout는 이 Codelab에서 다루지 않는 더 많은 기능을 지원합니다. 예를 들어 반복되는 주기로 경로나 속성을 제어할 수 있는 KeyCycle,와 시계 시간을 기반으로 애니메이션을 적용할 수 있는 KeyTimeCycle,가 있습니다. 각각의 예는 샘플을 참고하세요.
이 과정의 다른 Codelab 링크는 Kotlin 기반 Android 고급 Codelab 방문 페이지를 참고하세요.