MotionLayout의 Scene을 재사용해보자

Jong_Han·2022년 7월 12일
0

Android

목록 보기
9/9
💡 이 글의 모든 내용은 작성자의 개인적인 생각으로, 틀린 방법일 수 있습니다. 다른 의견을 가지신 분들은 댓글로 공유해주세요!

개요

최근 회사에서 MotionLayout을 아주 간~단하게 적용하고 있는데, 나는 비교적 최근에 MotionLayout을 공부하고 사용했었다.

하지만 동료들은 그렇지 않았고, 일정이 넉넉하지 않은 팀 사정 상 MotionLayout을 사용하는 것은 오직 나 혼자 뿐이었다.

사용하는 데에 다소 러닝커브가 있다면, 쉽게 사용할 수 있는 방법을 찾아보자

그렇다면 무엇을 하려고 하는가?

애니메이션을 구현하는 방법은 여러가지이며, 그 중 xml파일을 정의해서 사용하는 방식이 있다.

이 방법에서 정의된 애니메이션은 사용하고자 모든 곳에서 재사용된다.

따라서, MotionLayout에서도 애니메이션을 이루는 xml인 MotionScene을 단 1번만 정의하고 각각의 MotionLayout에서 정의된 Scene을 참조한다면 모든 MotionLayout에서 동일한 애니메이션을 얻을 수 있다고 생각했다.

이 방법이 유효할 경우, 한 번 정의된 Scene에 대해서는 그 뒤엔 매우 쉽게 적용이 가능할 것이다.

검증

2개의 액티비티에서 동일한 구조와 id를 가진 MotionLayout과 그 자식들을 작성한다.

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <data>

    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <androidx.constraintlayout.motion.widget.MotionLayout
            android:id="@+id/container"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_weight="5"
            app:layoutDescription="@xml/activity_main_scene"
            tools:context=".MainActivity">

            <TextView
                android:id="@+id/test"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Hello World!" />

        </androidx.constraintlayout.motion.widget.MotionLayout>

        <Button
            android:id="@+id/btn"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />

    </LinearLayout>
</layout>

activity_test.xml

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <data>

    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <androidx.constraintlayout.motion.widget.MotionLayout
            android:id="@+id/container"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_weight="5"
            app:layoutDescription="@xml/activity_main_scene">

            <TextView
                android:id="@+id/test"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="TEST!!!!!" />

        </androidx.constraintlayout.motion.widget.MotionLayout>

        <Button
            android:id="@+id/btn"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />

    </LinearLayout>
</layout>

각각의 MotionLayout에서 공동으로 참조할 Scene을 작성한다.

간단하게, 화면을 클릭했을 때 TextView가 화면 상단에서 하단으로 움직이는 애니메이션이다.

activity_main_scene.xml

<?xml version="1.0" encoding="utf-8"?>
<MotionScene 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:motion="http://schemas.android.com/apk/res-auto">

    <Transition
        motion:constraintSetEnd="@+id/end"
        motion:constraintSetStart="@id/start"
        motion:duration="1000">
       <KeyFrameSet>
       </KeyFrameSet>
        <OnClick motion:clickAction="toggle"/>
    </Transition>

    <ConstraintSet android:id="@+id/start">
        <Constraint
            android:id="@+id/test"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            motion:layout_constraintTop_toTopOf="parent"
            motion:layout_constraintStart_toStartOf="parent"
            motion:layout_constraintEnd_toEndOf="parent"/>
    </ConstraintSet>

    <ConstraintSet android:id="@+id/end">
        <Constraint
            android:id="@+id/test"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            motion:layout_constraintBottom_toBottomOf="parent"
            motion:layout_constraintStart_toStartOf="parent"
            motion:layout_constraintEnd_toEndOf="parent"/>
    </ConstraintSet>
</MotionScene>

하나의 Scene을 두 개의 MotionLayout이 참조하고 있는 상황에서 빌드하여 애니메이션이 정상 적용되는지 확인한다.

결과


두 곳 모두에서 동일한 애니메이션이 적용되었다!

제한 사항

하지만, 이 방법에는 크게 두 가지 제한이 있다.

  1. Scene을 공유하는 MotionLayout의 자식들은 모두 같은 id를 가져야 한다.

    위 예시 상황에서 MotionLayout의 자식으로 있는 TextView의 경우, 모두 같은 id를 가진다 (@id/test)

    이 이유는, 공유하는 SceneConstraintSet에 포함된 Constraint의 id와 MotionLayout의 자식의 id는 같아야 하기 때문이다.

  2. Scene을 공유하는 MotionLayout은 같은 구조로 자식을 가져야 한다.

    1번과 같은 이유에서, 동일한 애니메이션을 적용하기 위해서는 SceneConstraintSet에 포함된 Constraint의 id와 MotionLayout의 자식의 id는 같아야 한다.

    ex) SceneConstraint id가 header, body라면 MotionLayout은 header와 body만을 가진다.

    → 하지만, header와 body는 각각 어떤 자식을 가지는지는 상관이 없다.

  3. 한 레이아웃 파일에서 단 1개의 MotionLayout만이 Scene을 공유할 수 있다.

    2번과 마찬가지로 1번에서 이어지는 내용으로, 한 레이아웃에서 동일한 id를 사용할 수 없기 때문에 Scene과 같은 id를 가진 MotionLayout은 한 레이아웃 파일당 단 1개만 존재할 수 있다.

이미 작성된 Scene을 사용하는 방법

이미 작성된 Scene이 있고, 위 3가지 제한을 어기지 않았다면 아주 간단하게 애니메이션을 적용할 수 있다.

  1. 적용을 원하는 Parent Layout을 MotionLayout으로 Convert 한다.
  2. app:layoutDescription 을 이미 작성된 Scene을 참조하도록 한다.

마치며

이 방법이 틀린 방법일 수 있고, 위에 적어놓은 3가지의 제한이 있지만 그래도 꽤 괜찮은 방법이라고 생각된다.

추후에 저 3가지 제한을 초월하는 방법이 떠오르면 추가로 작성해보도록…….해보겠다.

profile
안드로이드 개.....발자?

0개의 댓글