0. 서론
모션레이아웃은 앱을 상당히 유연하게 하는 매력적인 기능이라고 생각합니다. 드래그나 클릭에 의해서 ConstraintLayout의 ConstraintSet, TransitionManager등과 같은 기능들을 잘 녹였다고 생각합니다. 물론, 구현을 하기까지 어려움을 많이 겪을 수 있겠지만, 어느 순간 자신의 기술이 되어있을 때, UI를 제공해주는 큰 무기를 얻을 수 있다고 생각합니다.
1. 목표
Lottie와 MotionLayout을 함께 결합해 애니메이션을 구현하면 최고의 효율을 낼 수 있다는 것을 목표로 프로젝트가 진행되겠습니다. 저는 프로젝트내에서 사용자들의 주목을 받기 위해서 MotionLayout을 사용하고 있습니다.
2. MotionLayout 구조
MotionLayout은 ConstraintLayout 라이브러리 2.0에서 모션과 애니메이션을 관리하기 위해서 사용하는 클래스라고 합니다. 그래서 ConstraintLayout을 기반으로 하고 있어서 ConstraintLayout을 통해 애니메이션을 만드는 개념을 알고 있다면 더 원활하게 해당 개념을 이해할 수 있다고 생각합니다. 프로젝트에서는 이와 같은 구조를 사용하고 있습니다.
이와 같이 ConstraintLayout은 일반적인 Layout 구조와 함께 MotionLayout에서 움직이는 Layout의 시작 구조와 마지막 구조 혹은 중간 구조, 마지막 구조를 만들어서 애니메이션을 만들어낼 수 있습니다. 다시 한번 정리해서 말하자면, Motion이 만들어지기 위한 Layout들을 만들어야하는 것을 의미합니다.
- Layout의 전체적인 구조: cardview_update_content.xml
- Layout의 시작 구조: motion_rocket_animation_start.xml
- Layout의 마지막 구조: motion_rocket_animation_end.xml
3. 코드
3.1. cardview_update_content..xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 | <?xml version="1.0" encoding="utf-8"?> <android.support.constraint.motion.MotionLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/ml_rocket" app:layoutDescription="@xml/scene_rocket_in_content"> <TextView android:layout_width="0dp" android:layout_height="0dp" android:layout_marginStart="8dp" android:layout_marginTop="8dp" android:layout_marginEnd="8dp" android:layout_marginBottom="8dp" android:id="@+id/tv_update_top_title" android:gravity="center" android:text="@string/congratulation_to_update" android:textSize="16sp" app:layout_constraintBottom_toTopOf="@+id/guideline_lottie_view" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <com.airbnb.lottie.LottieAnimationView android:id="@+id/lottieAnimationView" android:layout_width="128dp" android:layout_height="128dp" app:layout_constraintBottom_toTopOf="@id/guideline_rocket_content" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHeight_percent="0.3" app:layout_constraintHorizontal_bias="0.5" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:lottie_autoPlay="true" app:lottie_fileName="rocket.json" app:lottie_loop="true" /> <android.support.constraint.Guideline android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/guideline_rocket_content" app:layout_constraintGuide_percent="0.5" android:orientation="horizontal"/> <android.support.constraint.Guideline android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/guideline_lottie_view" app:layout_constraintGuide_percent="0.15" android:orientation="horizontal"/> <android.support.constraint.Guideline android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/guideline_text_content" app:layout_constraintGuide_percent="0.5" android:orientation="horizontal"/> <TextView android:id="@+id/tv_update_content1" android:layout_width="0dp" android:layout_height="0dp" android:layout_marginTop="8dp" android:gravity="center_horizontal" android:text="@string/update_content" android:textSize="14sp" app:layout_constraintBottom_toTopOf="@id/tv_update_content2" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/guideline_text_content" /> <TextView android:id="@+id/tv_update_content2" android:layout_width="0dp" android:layout_height="0dp" android:gravity="center_horizontal" android:text="@string/menu_change_app_settings_first_timetable_time" android:textSize="12sp" android:textStyle="bold" app:layout_constraintBottom_toTopOf="@id/button_ok" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/tv_update_content1" /> <Button android:id="@+id/button_ok" android:layout_width="0dp" android:layout_height="wrap_content" android:background="@color/splash_activity_background" android:text="@string/all_text_confirm" android:textColor="@color/white_color" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" /> </android.support.constraint.motion.MotionLayout> | cs |
모션레이아웃의 구조는 이와 같이 ConstraintLayout을 통해 만들어주는 것과 같은 구조로 레이아웃을 만들어줍니다. 해당 레이아웃은 LottieAnimationView를 중심으로 모션을 넣어줬습니다. 여기서 LottieAnimationView은 View의 메모리를 합리적으로 사용해 애니메이션을 제공해주는 것을 의미하고, 해당 파일은 json으로 해서 적은 용량과 비교적 적은 메모리로 애니메이션을 구현하도록 도와줍니다.
모션레이아웃에 scene을 등록시켜줘야 하는데, 여기에서 scene은 애니메이션이 움직이는 내용이 담겨져있습니다. 이것은 MotionLayout의 속성에서 app:layoutDescripton에 등록시켜줘야합니다.
자세한 내용은 https://airbnb.design/lottie/ 확인해보시면 쉽게 구현하실 수 있을 것이라고 생각합니다.
3.2. xml/scene_rocket_in_content.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | <MotionScene xmlns:motion="http://schemas.android.com/apk/res-auto" xmlns:android="http://schemas.android.com/apk/res/android"> <Transition motion:constraintSetStart="@layout/motion_rocket_animation_start" motion:constraintSetEnd="@layout/motion_rocket_animation_end" motion:duration="3000"> <OnClick motion:target="@id/button_ok" motion:clickAction="toggle"/> <KeyFrameSet> <KeyPosition motion:framePosition="50" motion:keyPositionType="parentRelative" motion:percentY="0.40" motion:target="@+id/lottieAnimationView" /> <KeyAttribute android:scaleX="2" android:scaleY="2" motion:framePosition="50" motion:target="@id/lottieAnimationView" /> <KeyAttribute android:rotationY="-50" motion:framePosition="40" motion:target="@id/lottieAnimationView" /> <KeyAttribute android:rotationY="50" motion:framePosition="60" motion:target="@id/lottieAnimationView" /> </KeyFrameSet> </Transition> </MotionScene> | cs |
3.3. motion_rocket_animation_start.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | <?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" xmlns:app="http://schemas.android.com/apk/res-auto"> <com.airbnb.lottie.LottieAnimationView android:id="@+id/lottieAnimationView" android:layout_width="128dp" android:layout_height="128dp" app:layout_constraintBottom_toTopOf="@id/guideline_rocket_content" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHeight_percent="0.3" app:layout_constraintHorizontal_bias="0.5" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:lottie_autoPlay="true" app:lottie_fileName="rocket.json" app:lottie_loop="true" /> <android.support.constraint.Guideline android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/guideline_rocket_content" app:layout_constraintGuide_percent="0.5" android:orientation="horizontal"/> </android.support.constraint.ConstraintLayout> | cs |
3.4. motion_rocket_animation_end.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | <?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" xmlns:app="http://schemas.android.com/apk/res-auto"> <com.airbnb.lottie.LottieAnimationView android:id="@+id/lottieAnimationView" android:layout_width="128dp" android:layout_height="128dp" app:layout_constraintBottom_toTopOf="@id/guideline_rocket_content" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHeight_percent="0.3" app:layout_constraintHorizontal_bias="0.5" app:layout_constraintStart_toStartOf="parent" app:lottie_autoPlay="true" app:lottie_fileName="rocket.json" app:lottie_loop="true" /> <android.support.constraint.Guideline android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/guideline_rocket_content" app:layout_constraintGuide_percent="0.0" android:layout_marginBottom="64dp" android:orientation="horizontal"/> </android.support.constraint.ConstraintLayout> | cs |
MotionLayout의 마지막 Layout으로 ConstraintLayout으로 구성시켜줘야 합니다. 해당 코드는 scene에서 constraintEnd에 들어가는 layout입니다. 해당 뷰에서는 시작시에 어떻게 레이아웃을 구성하겠냐는 정적인 뷰입니다. constraintStart뷰와 constraintEnd뷰를 활용해 정적인 뷰 2개를 활용해서 애니메이션이 만들어집니다.
4. 사용 후기
MotionLayout은 이전 '서울살이'프로젝트를 하면서 가장 삽질을 했던 라이브러리입니다. 해당 라이브러리를 사용하게 되면 앱의 각종 모션과 애니메이션을 만들어줄 수 있어서 이전과 다르게 굉장히 유연한 뷰를 만들 수 있게 됩니다.
많은 삽질 끝에 MotionLayout을 사용해야할 때와 사용하지 말아야할 때를 알 수 있게 되었습니다.
- MotionLayout을 사용할 때: 중복적이지 않은 View에 각종 애니메이션을 넣고 싶을 때, example) FloattingActionButton을 클릭시에 움직이게 하고 싶을 때.
- MotionLayout을 사용하지 말아야할 때 : RecyclerView 내부에 MotionLayout을 사용할 때, Swipe가 작용되는 MotionLayout에 RecyclerView를 사용해야할 때, 이미 구현되어진 뷰가 있다면 해당 뷰를 사용하는 것이 정신건강에 좋습니다.
'Android 공부 > Android UI' 카테고리의 다른 글
RecyclerView in SwipeRefreshLayout with CoordinatorLayout (0) | 2019.09.13 |
---|---|
NestedScrollView 없이 RecyclerView를 사용해보자. (1) | 2019.09.13 |
[안드로이드 UI 공부] Android Shared-Element Transitions - 2 (0) | 2019.04.12 |
[안드로이드 UI 공부] Android Shared-Element Transitions - 1 (0) | 2019.04.10 |
안드로이드 ConstraintLayout 사용법 (0) | 2018.10.24 |