Kotlin 03.2의 고급 Android: MotionLayout을 사용한 애니메이션

1. 시작하기 전에

이 Codelab은 Kotlin 기반 Android 고급 과정의 일부입니다. Codelab을 순서대로 살펴보면 이 과정을 최대한 활용할 수 있지만 필수는 아닙니다. 모든 과정 Codelab은 Kotlin 기반 Android 고급 Codelab 방문 페이지에 나열되어 있습니다.

MotionLayout는 Android 앱에 리치 모션을 추가할 수 있는 라이브러리입니다. ConstraintLayout,를 기반으로 하며 ConstraintLayout를 사용하여 빌드할 수 있는 모든 항목에 애니메이션을 적용할 수 있습니다.

MotionLayout를 사용하여 동시에 여러 뷰의 위치, 크기, 공개 상태, 알파, 색상, 고도, 회전, 기타 속성에 애니메이션을 적용할 수 있습니다. 선언적 XML을 사용하면 코드로는 실행하기 어려운 여러 뷰가 포함된 조정된 애니메이션을 만들 수 있습니다.

애니메이션은 앱 환경을 향상시키는 좋은 방법입니다. 애니메이션을 사용하여 다음을 수행할 수 있습니다.

  • 변경사항 표시: 상태 간에 애니메이션을 사용하면 사용자가 UI의 변경사항을 자연스럽게 추적할 수 있습니다.
  • 관심 유도: 애니메이션을 사용하여 중요한 UI 요소로 주의를 끕니다.
  • 아름다운 디자인 - 효과적인 모션 디자인으로 앱을 세련되게 만들 수 있습니다.

기본 요건

이 Codelab은 Android 개발 경험이 있는 개발자를 대상으로 합니다. 이 Codelab을 완료하기 전에 다음을 수행해야 합니다.

  • 활동, 기본 레이아웃으로 앱을 만들고 Android 스튜디오를 사용하여 기기 또는 에뮬레이터에서 앱을 실행하는 방법을 알아야 합니다. ConstraintLayout를 숙지합니다. 제약 조건 레이아웃 Codelab을 읽고 ConstraintLayout에 관해 자세히 알아보세요.

실행할 작업

  • ConstraintSetsMotionLayout를 사용하여 애니메이션 정의
  • 드래그 이벤트를 기반으로 애니메이션 처리
  • KeyPosition로 애니메이션 변경
  • KeyAttribute로 속성 변경
  • 코드로 애니메이션 실행
  • MotionLayout로 접을 수 있는 헤더에 애니메이션 적용

필요한 항목

2. 시작하기

샘플 앱을 다운로드하려면 다음 중 하나를 실행하세요.

또는 다음 명령어를 사용하여 명령줄에서 GitHub 저장소를 클론합니다.

$ git clone https://github.com/googlecodelabs/motionlayout.git

3. MotionLayout으로 애니메이션 만들기

먼저 사용자 클릭에 응답하여 화면의 상단 시작 부분에서 하단으로 뷰를 이동하는 애니메이션을 빌드합니다.

시작 코드에서 애니메이션을 만들려면 다음과 같은 주요 요소가 필요합니다.

  • ConstraintLayout의 서브클래스인 MotionLayout, MotionLayout 태그 내에서 애니메이션을 적용할 모든 뷰를 지정합니다.
  • MotionLayout.의 애니메이션을 설명하는 XML 파일인 MotionScene,
  • 애니메이션 지속 시간, 트리거, 뷰 이동 방법을 지정하는 MotionScene의 일부인 Transition,입니다.
  • 전환의 startend 제약 조건을 모두 지정하는 ConstraintSet입니다.

MotionLayout부터 순서대로 하나씩 살펴보겠습니다.

1단계: 기존 코드 살펴보기

MotionLayoutConstraintLayout의 서브클래스이므로 애니메이션을 추가하는 동안 동일한 모든 기능을 지원합니다. MotionLayout를 사용하려면 ConstraintLayout.를 사용할 MotionLayout 뷰를 추가합니다.

  1. 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단계: 모션 레이아웃으로 변환

MotionLayout,를 사용하여 애니메이션을 적용하려면 ConstraintLayoutMotionLayout로 변환해야 합니다.

레이아웃이 모션 장면을 사용하려면 모션 장면을 가리켜야 합니다.

  1. 이렇게 하려면 디자인 화면을 엽니다. Android 스튜디오 4.0에서는 레이아웃 XML 파일을 볼 때 오른쪽 상단의 분할 아이콘 또는 디자인 아이콘을 사용하여 디자인 화면을 엽니다.

a2beea710c2decb7.png

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

4fa936a98a8393b9.png

이렇게 하면 ConstraintLayout 태그가 MotionLayout 태그로 대체되고 @xml/activity_step1_scene.를 가리키는 MotionLayout 태그에 motion:layoutDescription가 추가됩니다.

activity_step1**.xml**

<!-- explore motion:layoutDescription="@xml/activity_step1_scene" -->
<androidx.constraintlayout.motion.widget.MotionLayout
       ...
       motion:layoutDescription="@xml/activity_step1_scene">

모션 장면MotionLayout의 애니메이션을 설명하는 단일 XML 파일입니다.

MotionLayout로 변환하면 즉시 디자인 화면에 모션 편집기가 표시됩니다.

66d0e80d5ab4daf8.png

모션 편집기에는 다음과 같은 세 가지 새로운 UI 요소가 있습니다.

  1. 개요 – 애니메이션의 여러 부분을 선택할 수 있는 모달 선택입니다. 이 이미지에서는 start ConstraintSet가 선택되어 있습니다. startend 사이에 있는 화살표를 클릭하여 전환을 선택할 수도 있습니다.
  2. 섹션 – 개요 아래에는 현재 선택한 개요 항목에 따라 변경되는 섹션 창이 표시됩니다. 이 이미지에서는 start ConstraintSet 정보가 선택 창에 표시됩니다.
  3. 속성 – 속성 패널이 표시되며 개요 또는 선택 창에서 현재 선택한 항목의 속성을 수정할 수 있습니다. 이 이미지에서는 start ConstraintSet의 속성을 보여줍니다.

3단계: 시작 및 종료 제약조건 정의

모든 애니메이션은 시작과 끝의 측면에서 정의할 수 있습니다. 시작 부분은 애니메이션이 실행되기 전의 화면을 설명하고 끝은 애니메이션이 완료된 후의 화면을 설명합니다. MotionLayout는 시간 경과에 따라 시작 상태와 종료 상태 간에 애니메이션하는 방법을 파악합니다.

MotionSceneConstraintSet 태그를 사용하여 시작 상태와 종료 상태를 정의합니다. ConstraintSet은 말 그대로 뷰에 적용할 수 있는 제약 조건 세트입니다. 여기에는 너비, 높이, ConstraintLayout 제약 조건이 포함됩니다. 또한 alpha와 같은 속성도 포함됩니다. 여기에는 뷰 자체가 포함되어 있지 않으며 이러한 뷰의 제약 조건만 포함되어 있습니다.

ConstraintSet에 지정된 모든 제약 조건은 레이아웃 파일에 지정된 제약 조건을 재정의합니다. 레이아웃과 MotionScene 모두에서 제약 조건을 정의하면 MotionScene의 제약 조건만 적용됩니다.

이 단계에서는 화면의 맨 위 시작 부분에서 시작하여 화면 하단 끝에서 완료되도록 별표 보기를 제한합니다.

모션 편집기를 사용하거나 activity_step1_scene.xml의 텍스트를 직접 수정하여 이 단계를 완료할 수 있습니다.

  1. 개요 패널에서 start ConstraintSet 선택

6e57661ed358b860.png

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

f9564c574b86ea8.gif

  1. 개요 패널에서 start ConstraintSet가 선택되면 red_starstart 소스가 표시되는지 확인합니다.
  2. start ConstraintSet에서 red_star를 선택한 Attributes 패널의 상단에 제약 조건을 추가하고 파란색 + 버튼을 클릭하여 시작합니다.

2fce076cd7b04bd.png

  1. 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/startid가 있으며 MotionLayout의 모든 뷰에 적용할 모든 제약 조건을 지정합니다. 이 MotionLayout에는 뷰가 하나만 있으므로 Constraint가 하나만 필요합니다.

ConstraintSet 내부의 Constraint는 제약하는 뷰의 ID(activity_step1.xml에 정의된 @id/red_star)를 지정합니다. Constraint 태그는 제약 조건과 레이아웃 정보만 지정한다는 점에 유의해야 합니다. Constraint 태그는 태그가 ImageView에 적용되고 있다는 것을 모릅니다.

이 제약 조건은 높이, 너비, 그리고 red_star 뷰를 상위 요소의 상단 시작 부분으로 제한하는 데 필요한 두 가지 제약 조건을 지정합니다.

  1. 개요 패널에서 end ConstraintSet를 선택합니다.

346e1248639b6f1e.png

  1. 이전과 동일한 단계를 따라 end ConstraintSet에서 red_starConstraint를 추가합니다.
  2. 모션 편집기를 사용하여 이 단계를 완료하려면 파란색 + 버튼을 클릭하여 bottomend에 제약 조건을 추가합니다.

fd33c779ff83c80a.png

  1. 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에는 전환이 1개 이상 포함되어야 합니다. 전환은 시작부터 끝까지, 한 애니메이션의 모든 부분을 정의합니다.

전환에서는 전환의 시작 및 종료 ConstraintSet를 지정해야 합니다. 또한 전환은 애니메이션을 실행하는 시간 또는 보기를 드래그하여 애니메이션을 적용하는 방법과 같은 다른 방식으로 애니메이션을 수정하는 방법을 지정할 수도 있습니다.

  1. 모션 편집기에서는 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단계: 모션 편집기에서 애니메이션 미리보기

dff9ecdc1f4a0740.gif

애니메이션: 모션 편집기에서 전환 미리보기를 재생하는 동영상

  1. 모션 편집기를 열고 개요 패널에서 startend 사이에 있는 화살표를 클릭하여 전환을 선택합니다.

1dc541ae8c43b250.png

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

a0fd2593384dfb36.png

6단계: on click 핸들러 추가

애니메이션을 시작할 방법이 필요합니다. 이렇게 하는 한 가지 방법은 MotionLayout@id/red_star의 클릭 이벤트에 응답하도록 하는 것입니다.

  1. 모션 편집기를 열고 개요 패널에서 시작과 끝 사이의 화살표를 클릭하여 전환을 선택합니다.

b6f94b344ce65290.png

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

ccf92d06335105fe.png

  1. View To Clickred_star로 변경합니다.

b0d3f0c970604f01.png

  1. Add(추가)를 클릭하면 모션 편집기에서 클릭 핸들러가 작은 점으로 표시됩니다.

cec3913e67fb4105.png

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

9af6fc60673d093d.png

  1. 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는 클릭을 감시하는 뷰입니다.
  • toggleclickAction는 클릭 시 시작 상태와 종료 상태 간에 전환합니다. clickAction의 다른 옵션은 문서에서 확인할 수 있습니다.
  1. 코드를 실행하고 Step 1을 클릭한 다음 빨간색 별을 클릭하여 애니메이션을 확인합니다.

5단계: 애니메이션 실행

앱을 실행합니다. 별표를 클릭하면 애니메이션이 실행되는 것을 확인할 수 있습니다.

7ba88af963fdfe10.gif

완성된 모션 장면 파일은 시작 및 끝 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단계: 초기 코드 검사하기

  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 정의

  1. 모션 장면 xml/step2.xml을 열어 애니메이션을 정의합니다.
  2. 시작 제약조건 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에는 모두 표시되지 않으며 애니메이션 시작 시 적용되는 추가 알파 값이 있습니다.

startend 제약 조건 집합은 애니메이션의 시작과 끝을 정의합니다. 시작 시 제약 조건(예: motion:layout_constraintStart_toStartOf)은 뷰의 시작을 다른 뷰의 시작으로 제한합니다. 처음에는 혼란스러울 수 있습니다. start라는 이름이 모두 제약 조건 컨텍스트에서 사용되기 그리고 둘 다 사용되기 때문입니다. 구분을 돕기 위해 layout_constraintStartstart는 '시작'을 나타냅니다. 즉, 왼쪽에서 오른쪽으로 쓰는 언어로, 오른쪽에서 왼쪽으로 쓰는 언어로 된 모습입니다. start 제약 조건 집합은 애니메이션의 시작을 나타냅니다.

최종 ConstraintSet 정의

  1. 체인을 사용하여 @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 태그를 사용하여 추가할 수 있습니다.

  1. 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 떨어지도록 유지합니다.

사용해 보기

  1. 앱을 다시 실행하고 2단계 화면을 엽니다. 애니메이션이 표시됩니다.
  2. '플링'을 사용해 보세요. 또는 애니메이션 중간에 손가락을 놓으면 MotionLayout가 유체 물리학 기반 애니메이션을 표시하는 방식을 살펴볼 수 있습니다.

fefcdd690a0dcaec.gif

MotionLayoutConstraintLayout의 기능을 사용하여 풍부한 효과를 만들어 매우 다양한 디자인 간에 애니메이션을 적용할 수 있습니다.

이 애니메이션에서는 세 개의 뷰가 모두 화면 하단에 상위 요소를 기준으로 배치되어 시작됩니다. 마지막에 3개의 뷰는 체인의 @id/credits를 기준으로 배치됩니다.

이러한 매우 다른 레이아웃에도 불구하고 MotionLayout는 시작과 끝 사이에 유동적인 애니메이션을 만듭니다.

5. 경로 수정

이 단계에서는 애니메이션 도중 복잡한 경로를 따라가면서 모션 중에 크레딧에 애니메이션을 적용하는 애니메이션을 빌드합니다. MotionLayoutKeyPosition를 사용하여 시작과 끝 사이에 뷰가 사용할 경로를 수정할 수 있습니다.

1단계: 기존 코드 살펴보기

  1. layout/activity_step3.xmlxml/step3.xml를 열어 기존 레이아웃과 모션 장면을 확인합니다. ImageViewTextView에는 달과 크레딧 텍스트가 표시됩니다.
  2. 모션 장면 파일 (xml/step3.xml)을 엽니다. @id/start에서 @id/end까지의 Transition이 정의된 것을 볼 수 있습니다. 애니메이션은 두 개의 ConstraintSets를 사용하여 화면 왼쪽 하단에서 화면 오른쪽 하단으로 달 이미지를 이동합니다. 달이 움직이면 크레딧 텍스트가 alpha="0.0"에서 alpha="1.0"으로 페이드 인됩니다.
  3. 이제 앱을 실행하고 3단계를 선택합니다. 달을 클릭하면 달이 시작부터 끝까지 선형 경로 (또는 직선)를 따라 움직이는 것을 볼 수 있습니다.

2단계: 경로 디버깅 사용 설정

달의 움직임에 호를 추가하기 전에 MotionLayout에서 경로 디버깅을 사용 설정하면 유용합니다.

MotionLayout로 복잡한 애니메이션을 개발하는 데 도움이 되도록 모든 뷰의 애니메이션 경로를 그릴 수 있습니다. 애니메이션을 시각화하거나 모션의 작은 부분을 세밀하게 조정할 때 유용합니다.

  1. 디버깅 경로를 사용 설정하려면 layout/activity_step3.xml를 열고 motion:motionDebug="SHOW_PATH"MotionLayout 태그에 추가합니다.

activity_step3.xml

<!-- Add motion:motionDebug="SHOW_PATH" -->

<androidx.constraintlayout.motion.widget.MotionLayout
       ...
       motion:motionDebug="SHOW_PATH" >

경로 디버깅을 사용 설정한 후 앱을 다시 실행하면 모든 뷰의 경로가 점선으로 시각화됩니다.

23bbb604f456f65c.png

  • 은 한 뷰의 시작 또는 종료 위치를 나타냅니다.
  • 은 하나의 뷰의 경로를 나타냅니다.
  • 다이아몬드는 경로를 수정하는 KeyPosition를 나타냅니다.

예를 들어 이 애니메이션에서 가운데 원은 크레딧 텍스트의 위치입니다.

3단계: 경로 수정하기

MotionLayout의 모든 애니메이션은 애니메이션이 시작되기 전과 애니메이션이 완료된 후의 화면 모양을 정의하는 시작 및 종료 ConstraintSet로 정의됩니다. 기본적으로 MotionLayout는 위치가 변경되는 각 뷰의 시작 위치와 종료 위치 사이에 선형 경로 (직선)를 표시합니다.

이 예에서 달의 원호와 같은 복잡한 경로를 빌드하기 위해 MotionLayoutKeyPosition를 사용하여 뷰가 시작과 끝 사이에 이동하는 경로를 수정합니다.

  1. xml/step3.xml를 열고 장면에 KeyPosition를 추가합니다. KeyPosition 태그는 Transition 태그 내에 배치됩니다.

eae4dae9a12d0410.png

step3.xml

<!-- TODO: Add KeyFrameSet and KeyPosition -->
<KeyFrameSet>
   <KeyPosition
           motion:framePosition="50"
           motion:motionTarget="@id/moon"
           motion:keyPositionType="parentRelative"
           motion:percentY="0.5"
   />
</KeyFrameSet>

KeyFrameSetTransition의 하위 요소이며 전환 중에 적용되어야 하는 모든 KeyFrames(예: KeyPosition)의 집합입니다.

MotionLayout는 시작과 끝 사이의 달 경로를 계산하므로 KeyFrameSet에 지정된 KeyPosition에 따라 경로를 수정합니다. 앱을 다시 실행하여 경로가 어떻게 수정되는지 확인할 수 있습니다.

KeyPosition에는 경로를 수정하는 방법을 설명하는 여러 속성이 있습니다. 가장 중요한 기능은 다음과 같습니다.

  • framePosition는 0~100 사이의 숫자입니다. 애니메이션에서 이 KeyPosition를 적용해야 하는 시점을 정의합니다. 1은 애니메이션을 통해 1%, 99%는 애니메이션을 통해 99%를 전달합니다. 값이 50이면 중간에 바로 적용합니다.
  • motionTarget는 이 KeyPosition가 경로를 수정하는 뷰입니다.
  • keyPositionType는 이 KeyPosition가 경로를 수정하는 방법입니다. parentRelative, pathRelative 또는 deltaRelative일 수 있습니다 (다음 단계에서 설명).
  • percentX | percentYframePosition에서 경로를 수정할 정도입니다 (0.0~1.0 사이의 값, 음수 값 및 1보다 큰 값이 허용됨).

다음과 같이 해석할 수 있습니다. framePosition 에서 keyPositionType에 의해 결정된 좌표에 따라 percentX 또는 percentY 만큼 이동하여 motionTarget 의 경로를 수정합니다.

기본적으로 MotionLayout는 경로를 수정하여 도입되는 모든 모서리를 둥글게 만듭니다. 방금 만든 애니메이션을 보면 구부러진 곳에서 달이 곡선 경로를 따라가는 것을 확인할 수 있습니다. 대부분의 애니메이션에서 이 기능을 사용하면 되며, 그렇지 않은 경우 curveFit 속성을 지정하여 맞춤설정할 수 있습니다.

사용해 보기

앱을 다시 실행하면 이 단계의 애니메이션이 표시됩니다.

46b179c01801f19e.gif

달이 호를 따라가는 이유는 Transition에 지정된 KeyPosition를 통과하기 때문입니다.

<KeyPosition
       motion:framePosition="50"
       motion:motionTarget="@id/moon"
       motion:keyPositionType="parentRelative"
       motion:percentY="0.5"
/>

KeyPosition는 다음과 같이 읽을 수 있습니다. 'framePosition 50 (애니메이션이 중간 지점)에서 parentRelative (전체 MotionLayout)에 의해 결정된 좌표에 따라 50% Y만큼 (화면 아래쪽의) 50% Y만큼 이동하여 motionTarget @id/moon의 경로를 수정합니다.'

따라서 애니메이션이 끝날 때 달이 화면에서 50% 아래에 있는 KeyPosition를 통과해야 합니다. 이 KeyPosition은 X 모션을 전혀 수정하지 않으므로 달은 계속 수평으로 시작부터 끝까지 이동합니다. MotionLayout는 시작과 끝 사이를 이동하는 동안 이 KeyPosition를 통과하는 매끄러운 경로를 파악합니다.

자세히 보면 크레딧 텍스트는 달의 위치의 제약을 받습니다. 세로로도 이동하지 않는 이유는 무엇인가요?

1c7cf779931e45cc.gif

<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 이해

이전 단계에서는 keyPosition 유형의 parentRelative를 사용하여 경로를 화면의 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의 세 가지 유형이 있습니다. 유형을 지정하면 percentXpercentY이 계산되는 좌표계가 변경됩니다.

좌표계란 무엇인가요?

좌표계를 사용하면 공간의 점을 지정할 수 있습니다. 화면상의 위치를 설명하는 데도 유용합니다.

MotionLayout 좌표계는 데카르트 좌표계입니다. 즉, 두 개의 수직선으로 정의된 X축과 Y축이 있습니다. 두 지점 간의 주요 차이점은 화면에서 X축이 이동하는 위치입니다 (Y축은 항상 X축과 수직을 이룹니다).

MotionLayout의 모든 좌표계는 X축과 Y축에서 모두 0.01.0 사이의 값을 사용합니다. 음수 및 1.0보다 큰 값을 허용합니다. 예를 들어 percentX 값이 -2.0이면 X축의 반대 방향으로 두 번 이동합니다.

모두 대수학 수업처럼 들리면 아래 사진을 확인해 보세요!

parentrelative 좌표

a7b7568d46d9dec7.png

parentRelativekeyPositionType는 화면과 동일한 좌표계를 사용합니다. 전체 MotionLayout의 왼쪽 상단으로 (0, 0)를 정의하고 오른쪽 하단에 (1, 1)를 정의합니다.

이 예에서 달의 호와 같이 전체 MotionLayout를 이동하는 애니메이션을 만들고 싶을 때마다 parentRelative를 사용할 수 있습니다.

그러나 모션을 기준으로 경로를 수정하려는 경우(예: 약간 곡선으로 만드는 경우) 다른 두 좌표계를 선택하는 것이 더 좋습니다.

deltarelative 좌표

5680bf553627416c.png

델타는 변화를 나타내는 수학 용어이므로 deltaRelative은 '상대적으로 변화'를 나타내는 방법입니다. deltaRelative 좌표에서 (0,0)는 뷰의 시작 위치가고 (1,1)는 종료 위치입니다. X축 및 Y축은 화면에 맞게 정렬됩니다.

X축은 화면에서 항상 가로이고 Y축은 화면에서 항상 세로입니다. parentRelative와 비교할 때 주요 차이점은 좌표는 뷰가 이동할 화면의 일부분만 설명한다는 것입니다.

deltaRelative는 수평 또는 수직 모션을 개별적으로 제어하는 데 유용한 좌표계입니다. 예를 들어, 수직 (Y) 이동을 50%에서 완료하고 수평 (X)으로 계속 움직이는 애니메이션을 만들 수 있습니다.

path상대 좌표

f3aaadaac8b4a93f.png

MotionLayout의 마지막 좌표계는 pathRelative입니다. X축이 시작부터 끝까지 모션 경로를 따라가기 때문에 다른 두 축과는 상당히 다릅니다. 따라서 (0,0)는 시작 위치이고 (1,0)는 종료 위치입니다.

왜 이렇게 해야 할까요? 언뜻 보기에는 상당히 놀랍습니다. 특히 이 좌표계는 화면 좌표계와 정렬되어 있지 않기 때문입니다.

몇 가지 측면에서 pathRelative가 정말로 유용한 것으로 확인되었습니다.

  • 애니메이션이 재생되는 동안 뷰 속도를 높이거나 낮추거나 중지합니다. X 측정기준은 항상 보기에서 이동하는 경로와 정확하게 일치하므로 pathRelative KeyPosition를 사용하여 해당 경로에서 특정 지점에 도달하는 framePosition를 변경할 수 있습니다. 따라서 percentX="0.1"가 있는 framePosition="50"KeyPosition가 있으면 애니메이션이 모션의 처음 10% 를 이동하는 데 시간의 50% 가 걸립니다.
  • 경로에 미묘한 호를 추가합니다. Y 차원은 항상 모션과 수직을 이루므로 Y를 변경하면 전체 모션을 기준으로 경로가 변경됩니다.
  • 두 번째 측정기준을 추가하면 deltaRelative 이 작동하지 않습니다. 완전한 수평 및 수직 모션의 경우 deltaRelative은 하나의 유용한 측정기준만 생성합니다. 그러나 pathRelative는 항상 사용 가능한 X 및 Y 좌표를 생성합니다.

다음 단계에서는 둘 이상의 KeyPosition를 사용하여 훨씬 더 복잡한 경로를 빌드하는 방법을 알아봅니다.

7. 복잡한 경로 빌드

이전 단계에서 빌드한 애니메이션을 보면 부드러운 곡선이 만들어지지만 모양은 '달처럼' 보일 수 있습니다.

여러 KeyPosition 요소로 경로 수정

MotionLayout는 모션을 얻는 데 필요한 만큼의 KeyPosition를 정의하여 경로를 추가로 수정할 수 있습니다. 이 애니메이션에서는 호를 만들지만 원하는 경우 화면 중앙에서 달이 오르락내리락하게 만들 수도 있습니다.

  1. xml/step4.xml를 엽니다. 이전 단계에서 추가한 것과 동일한 뷰와 KeyFrame가 있는 것을 확인할 수 있습니다.
  2. 곡선 상단을 둥글게 만들려면 @id/moon의 경로에 KeyPositions을 두 개 더 추가합니다. 하나는 상단에 도달하기 직전에, 다른 하나는 뒤에 추가합니다.

500b5ac2db48ef87.png

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을 통과하는 부드러운 모션을 만드는 방법을 파악합니다.

사용해 보기

  1. 앱을 다시 실행합니다. 4단계로 이동하여 애니메이션의 실제 동작을 확인합니다. 달을 클릭하면 달은 시작부터 끝까지 경로를 따라 KeyFrameSet에 지정된 각 KeyPosition를 통과합니다.

직접 탐색하기

다른 유형의 KeyFrame로 이동하기 전에 KeyFrameSetKeyPositions를 더 추가하여 KeyPosition를 사용하여 어떤 종류의 효과를 만들 수 있는지 확인해 보세요.

다음 한 가지 예는 애니메이션 중에 앞뒤로 이동하는 복잡한 경로를 만드는 방법을 보여줍니다.

cd9faaffde3dfef.png

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를 사용하여 크기 조절 및 회전

  1. 이전 단계에서 빌드한 것과 동일한 애니메이션이 포함된 xml/step5.xml를 엽니다. 이 화면은 다양한 공간 사진을 배경으로 사용합니다.
  2. 달의 크기가 커지고 회전하게 하려면 KeyFrameSetkeyFrame="50"keyFrame="100"KeyAttribute 태그 두 개를 추가합니다.

bbae524a2898569.png

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도 (완전히 2개의 원)로 마무리하고 scaleXscaleY 값의 기본값이 1.0이므로 크기를 다시 일반으로 축소합니다.

KeyPosition와 마찬가지로 KeyAttributeframePositionmotionTarget를 사용하여 KeyFrame를 적용할 시점과 수정할 뷰를 지정합니다. MotionLayoutKeyPositions 사이에 보간하여 유동적인 애니메이션을 만듭니다.

KeyAttributes는 모든 보기에 적용할 수 있는 속성을 지원합니다. visibility, alpha 또는 elevation와 같은 기본 속성 변경을 지원합니다. 여기서와 같이 회전을 변경하거나, rotateXrotateY를 사용하여 3차원으로 회전하거나, scaleXscaleY로 크기를 조정하거나, X, Y, Z로 뷰의 위치를 변환할 수도 있습니다.

2단계: 크레딧 표시 지연

이 단계의 목표 중 하나는 애니메이션이 거의 완료될 때까지 크레딧 텍스트가 표시되지 않도록 애니메이션을 업데이트하는 것입니다.

  1. 크레딧 표시를 지연하려면 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/creditsalpha를 0.0으로 유지합니다. 알파 0에서 시작하므로 애니메이션의 처음 85% 에는 표시되지 않습니다.

KeyAttribute의 최종 효과는 애니메이션이 끝날 때 크레딧이 표시되는 것입니다. 이렇게 하면 화면의 오른쪽 모서리에 달이 지면에 맞춰 지면과 조화를 이루는 모습이 됩니다.

다른 뷰가 이렇게 이동하는 동안 한 뷰에서 애니메이션을 지연시키면 사용자에게 역동적으로 느껴지는 인상적인 애니메이션을 빌드할 수 있습니다.

사용해 보기

  1. 앱을 다시 실행하고 5단계로 이동하여 애니메이션이 실제로 작동하는 것을 확인합니다. 달을 클릭하면 달은 시작부터 끝까지 경로를 따라 KeyFrameSet에 지정된 각 KeyAttribute를 통과합니다.

2f4bfdd681c1fa98.gif

달을 두 개의 완만한 원을 회전시켰기 때문에 이제 달이 두 번 뒤로 젖히고 애니메이션이 거의 끝날 때까지 크레딧이 나타나기 지연됩니다.

직접 탐색하기

최종 유형의 KeyFrame로 이동하기 전에 KeyAttributes에서 다른 표준 속성을 수정해 보세요. 예를 들어 rotationrotationX로 변경하여 생성되는 애니메이션을 확인합니다.

다음은 시도할 수 있는 표준 속성 목록입니다.

  • android:visibility
  • android:alpha
  • android:elevation
  • android:rotation
  • android:rotationX
  • android:rotationY
  • android:scaleX
  • android:scaleY
  • android:translationX
  • android:translationY
  • android:translationZ

9. 맞춤 속성 변경

리치 애니메이션에는 뷰의 색상 또는 기타 속성을 변경하는 작업이 포함됩니다. MotionLayoutKeyAttribute를 사용하여 이전 작업에 나열된 표준 속성을 변경할 수 있지만 CustomAttribute를 사용하여 다른 속성을 지정합니다.

CustomAttribute는 setter가 있는 모든 값을 설정하는 데 사용할 수 있습니다. 예를 들어 CustomAttribute를 사용하여 뷰에서 backgroundColor를 설정할 수 있습니다. MotionLayout리플렉션을 사용하여 setter를 찾은 다음 반복적으로 호출하여 뷰에 애니메이션을 적용합니다.

이 단계에서는 CustomAttribute를 사용하여 달에 colorFilter 속성을 설정하여 아래와 같은 애니메이션을 빌드합니다.

5fb6792126a09fda.gif

맞춤 속성 정의

  1. 시작하려면 이전 단계에서 빌드한 애니메이션이 포함된 xml/step6.xml를 엽니다.
  2. 달의 색상을 변경하려면 KeyFrameSetkeyFrame="0", keyFrame="50", keyFrame="100".CustomAttribute이 있는 두 개의 KeyAttribute를 추가합니다.

214699d5fdd956da.png

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를 추가합니다. CustomAttributeKeyAttribute로 지정된 framePosition에 적용됩니다.

CustomAttribute 내에서 attributeName와 설정할 값 하나를 지정해야 합니다.

  • motion:attributeName는 이 맞춤 속성에서 호출할 setter의 이름입니다. 이 예에서는 DrawablesetColorFilter가 호출됩니다.
  • motion:custom*Value는 이름에 명시된 유형의 맞춤 값입니다. 이 예에서 맞춤 값은 지정된 색상입니다.

커스텀 값의 유형은 다음과 같습니다.

  • 색상
  • 정수
  • 부동 소수점 수
  • 문자열
  • 측정기준
  • 불리언

이 API를 사용하면 MotionLayout는 모든 뷰에서 setter를 제공하는 모든 항목에 애니메이션을 적용할 수 있습니다.

사용해 보기

  1. 앱을 다시 실행하고 6단계로 이동하여 애니메이션이 실제로 작동하는 것을 확인합니다. 달을 클릭하면 달은 시작부터 끝까지 경로를 따라 KeyFrameSet에 지정된 각 KeyAttribute를 통과합니다.

5fb6792126a09fda.gif

KeyFrames를 더 추가하면 MotionLayout는 달의 경로를 직선에서 복잡한 곡선으로 변경하여 이중 뒤로 뒤집기, 크기 조절, 애니메이션 중간에 색상이 변경됩니다.

실제 애니메이션에서는 종종 다른 경로와 속도에 따라 모션을 제어하는 여러 뷰를 동시에 애니메이션으로 만들 수 있습니다. 각 뷰에 서로 다른 KeyFrame를 지정하면 MotionLayout로 여러 뷰에 애니메이션을 적용하는 풍부한 애니메이션을 구성할 수 있습니다.

10. 드래그 이벤트 및 복잡한 경로

이 단계에서는 OnSwipe를 복잡한 경로와 함께 사용하는 방법을 살펴봅니다. 지금까지 달의 애니메이션은 OnClick 리스너에 의해 트리거되었으며 고정된 시간 동안 실행됩니다.

지난 몇 단계에서 빌드한 달 애니메이션과 같이 OnSwipe를 사용하여 경로가 복잡한 애니메이션을 제어하려면 OnSwipe의 작동 방식을 이해해야 합니다.

1단계: OnSwipe 동작 살펴보기

  1. xml/step7.xml를 열고 기존 OnSwipe 선언을 찾습니다.

step7.xml

<!-- Fix OnSwipe by changing touchAnchorSide 

<OnSwipe
       motion:touchAnchorId="@id/moon"
       motion:touchAnchorSide="bottom"
/>
  1. 기기에서 앱을 실행하고 7단계로 이동합니다. 호의 경로를 따라 달을 드래그하여 부드러운 애니메이션을 만들 수 있는지 확인해 보세요.

이 애니메이션을 실행하면 잘 보이지 않습니다. 달이 원호의 정상에 도달하면 달이 뛰어다니기 시작합니다.

ed96e3674854a548.gif

버그를 이해하려면 사용자가 원호의 상단을 터치할 때 어떤 일이 일어나는지 생각해 보세요. OnSwipe 태그에 motion:touchAnchorSide="bottom"가 있기 때문에 MotionLayout는 애니메이션이 진행되는 동안 손가락과 뷰 하단 사이의 거리를 일정하게 하려고 합니다.

그러나 달의 하단이 항상 같은 방향으로 움직이지는 않으므로 MotionLayout는 사용자가 원호의 꼭대기를 방금 지나갔을 때 어떻게 해야 할지 알 수 없습니다. 이를 생각해보면 달의 하단을 추적하고 있으므로 사용자가 달의 하단을 터치할 때 어느 위치에 배치해야 할까요?

56cd575c5c77eddd.png

2단계: 오른쪽 사용하기

이러한 버그를 방지하려면 항상 전체 애니메이션 재생 시간 동안 항상 한 방향으로 진행되는 touchAnchorIdtouchAnchorSide를 선택하는 것이 중요합니다.

이 애니메이션에서는 달의 right 면과 left 쪽이 화면에서 모두 한 방향으로 진행됩니다.

그러나 bottomtop는 모두 방향을 반대로 합니다. OnSwipe에서 이들을 추적하려고 하면 방향이 바뀌면 혼란스러워집니다.

  1. 이 애니메이션이 터치 이벤트를 따르도록 하려면 touchAnchorSideright로 변경합니다.

step7.xml

<!-- Fix OnSwipe by changing touchAnchorSide 

<OnSwipe
       motion:touchAnchorId="@id/moon"
       motion:touchAnchorSide="right"
/>

3단계: DragDirection 사용

dragDirectiontouchAnchorSide를 결합하여 평소와 다른 사이드 트랙을 만들 수도 있습니다. touchAnchorSide가 한 방향으로만 진행되는 것이 중요하지만, MotionLayout에 추적할 방향을 지정할 수 있습니다. 예를 들어 touchAnchorSide="bottom"를 유지하되 dragDirection="dragRight"를 추가할 수 있습니다. 이렇게 하면 MotionLayout에서 뷰 하단의 위치를 추적하지만 오른쪽으로 이동할 때 위치만 고려합니다 (세로 모션은 무시됨). 따라서 하단이 위아래로 이동하더라도 OnSwipe를 사용하여 올바르게 애니메이션 처리됩니다.

  1. 달의 움직임을 올바르게 추적하려면 OnSwipe를 업데이트하세요.

step7.xml

<!-- Using dragDirection to control the direction of drag tracking 

<OnSwipe
       motion:touchAnchorId="@id/moon"
       motion:touchAnchorSide="bottom"
       motion:dragDirection="dragRight"
/>

사용해 보기

  1. 앱을 다시 실행하고 달을 전체 경로를 통해 드래그해 보세요. 복잡한 호를 따르지만 MotionLayout는 스와이프 이벤트에 대한 응답으로 애니메이션을 진행할 수 있습니다.

5458dff382261427.gif

11. 코드를 사용한 모션 실행

MotionLayoutCoordinatorLayout와 함께 사용할 때 풍부한 애니메이션을 빌드하는 데 사용할 수 있습니다. 이 단계에서는 MotionLayout를 사용하여 접을 수 있는 헤더를 빌드합니다.

1단계: 기존 코드 살펴보기

  1. 시작하려면 layout/activity_step8.xml을 엽니다.
  2. layout/activity_step8.xml에서 작동하는 CoordinatorLayoutAppBarLayout가 이미 빌드되었음을 확인할 수 있습니다.

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를 사용하여 NestedScrollViewAppBarLayout 간에 스크롤 정보를 공유합니다. 따라서 NestedScrollView가 위로 스크롤되면 AppBarLayout에 변경사항을 알립니다. 이런 방식으로 Android에서 이와 같이 접히는 툴바를 구현할 수 있습니다. 텍스트의 스크롤이 '조정'됩니다. 있습니다.

@id/motion_layout가 가리키는 모션 장면이 이전 단계의 모션 장면과 유사합니다. 그러나 CoordinatorLayout와 호환되도록 OnSwipe 선언은 삭제되었습니다.

  1. 앱을 실행하고 8단계로 이동합니다. 텍스트를 스크롤할 때 달이 움직이지 않습니다.

2단계: MotionLayout 스크롤 만들기

  1. NestedScrollView가 스크롤되는 즉시 MotionLayout 뷰가 스크롤되도록 하려면 motion:minHeightmotion:layout_scrollFlagsMotionLayout에 추가합니다.

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"  >
  1. 앱을 다시 실행하고 8단계로 이동합니다. 위로 스크롤하면 MotionLayout가 접힙니다. 하지만 애니메이션은 아직 스크롤 동작에 따라 진행되지 않습니다.

3단계: 코드로 모션 이동

  1. Step8Activity.kt을 엽니다 . coordinateMotion() 함수를 수정하여 MotionLayout에 스크롤 위치 변경사항을 알립니다.

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 및 백분율 진행률 간에 변환하려면 총 스크롤 범위로 나눕니다.

사용해 보기

  1. 앱을 다시 배포하고 8단계 애니메이션을 실행합니다. MotionLayout는 스크롤 위치에 따라 애니메이션을 진행합니다.

ee5ce4d9e33a59ca.gif

MotionLayout를 사용하여 맞춤 동적 축소 툴바 애니메이션을 빌드할 수 있습니다. KeyFrames 시퀀스를 사용하여 매우 과감한 효과를 얻을 수 있습니다.

12. 축하합니다

이 Codelab에서는 MotionLayout의 기본 API를 다루었습니다.

MotionLayout의 실제 예를 더 보려면 공식 샘플을 확인하세요. 문서도 확인해 보세요.

자세히 알아보기

MotionLayout는 반복되는 주기로 경로나 속성을 제어할 수 있는 KeyCycle,와 시계 시간에 따라 애니메이션을 적용할 수 있는 KeyTimeCycle,와 같이 이 Codelab에서 다루지 않은 더 많은 기능을 지원합니다. 각 예시의 샘플을 확인하세요.

이 과정의 다른 Codelab 링크는 Kotlin 기반 Android 고급 Codelab 방문 페이지를 참고하세요.