MotionLayout으로 만드는 애니메이션
ConstraintLayout 2.0이 출시 되면서 MotionLayout이라는 새로운 레이아웃이 등장했습니다. MotionLayout은 ConstrainLayout을 상속한 레이아웃으로 새로운 방식으로 동적인 화면을 구성합니다.
기존에 애니메이션을 구현하기위해서는 다음과 같은 기술들을 적용했었습니다.
- AnimatedVectorDrawable( 모핑 애니메이션 버튼 만들기 )
- Property Animation
- LayoutTransition, (Animate layout changes using a transition)
- CoordinatorLayout(디자인 서포트 라이브러리)
MotionLayout은 기존의 방식과는 다르게 레이아웃 두개를 만든 뒤, 두 레이아웃 사이의 전환효과를 정의합니다. 기본적으로 애니메이션되는 프레임들을 탐색가능하며, 화면 터치 조작과 키프레임 및 각종 화면전환효과등을 지원합니다.
MotionLayout은 별도의 Java/Kotlin 코드 작성없이도 애니메이션을 만들어 낼수 있습니다. 원하는 애니메이션 효과를 xml만 선언해주면 됩니다. 현재는 공개되지 않았지만 추후에는 아래와 같은 툴도 쓸 수 있게 될 것입니다.
제약사항
- API Level 18 이상 지원 (전체 안드로이드 사용자의 95.9%)
- TransitionManager와는 달리, MotionLayout은 중첩 된 레이아웃 계층뿐만 아니라 Activity Transition에서도 작동 할 수 있는 직접적인 자식뷰에 대한 기능들도 제공합니다.
언제 쓰면 좋을까?
MotionLayout은 UI를 움직이거나 사이즈를 조절하고 싶을때 쓸 수 있습니다. 버튼, 이미지, 타이틀 영역 등 사용자의 조작과 직접적으로 상호작용 할 때 유용합니다. 아래의 샘플프로젝트를 다운받아 테스트 해보시기 바랍니다.
샘플 : https://github.com/googlesamples/android-ConstraintLayoutExamples
프로젝트에 MotionLayout 적용해보기
app모듈 레벨의 gradle에 다음과 같이 추가해주세요.
dependencies { implementation 'com.android.support.constraint:constraint-layout:2.0.0-alpha2' }
MotionLayout의 작동 원리
MotionLayout의 가장 큰 특징이자 장점은 애니메이션에 관련 내용을 별도의 xml로 분리(모듈화,재사용) 하여 사용한다는 점입니다.
MotionLayout은 ConstraintLayout을 사용할때처럼 화면에 표현하고 싶은 View들과 MotionHelper만을 포함 합니다. (MotionHelper는 샘플프로젝트의 21번 FadeIn 참조) 독립된 MotionScene으로 부터 화면 터치에 대한 정의, 키프레임에 대한 정의, 뷰 속성에 대한 정의등을 선언할 수 있으며 MotionLayout은 app:layoutDescription이라는 속성에 MotionScene에 대한 아이디만을 참조하여 사용하게 됩니다.
각 파츠가 어떤 역할을 하는지 하나씩 알아보도록 하겠습니다.
<MotionLayout>
MotionScene에서 정의된 ConstraintSet에 의해 레이아웃간 화면전환을 지원합니다.
그러므로 MotionScene파일 연결이 필수적으로 필요하며, 연결시에는 “layoutDescription”에 리소스 아이디로 연결합니다.
<MotionScene>
MotionScene은 ConstraintSet, 키프레임, 터치 조작, 화면전환 효과등의 애니메이션 정보를 포함한 xml 파일이며 res / xml 폴더에 저장되어야합니다.
<Transition>
MotionScene내에서 정의 되며, 화면을 어떤식으로 전환시킬지에 대한 정의를 합니다.
- constraintSetStart : 첫번째 프레임이 될 제약조건을 정의한 레이아웃 또는 ConstraintSet의 아이디를 참조합니다.
- constraintSetEnd : 마지막 프레임이 될 제약조건을 정의한 레이아웃 또는 ConstraintSet의 아이디를 참조합니다.
- interpolator : 주어진 시간내에서 어떤식으로 애니메이션속도를 조절할건지 정의합니다. (예 : easeInOut, linear)
- duration : 화면전환 애니메이션을 수행하는 시간에 대한 길이를 정의합니다.
- staggered : 0부터 1까지 float 값이 들어가며, duration내에서 staggered의 값의 비율만큼 애니메이션을 지연시켰다가 빠르게 남은 프레임들의 애니메이션을 실행시킵니다. 예를들어 duration의 값이 5이고 staggered의 값은 0.2라고 가정하면 1초정도 애니메이션을 지연시켰다가 4초만에 모든 프레임을 실행시킵니다.
<OnSwipe>
Transition내에서 정의 됩니다. 손끝의 움직임과 화면전환을 일치 시켜주는 핸들러입니다. 몇가지 설정해야하는 속성들이 있습니다.
- touchAnchorId : 추적하고 싶은 오브젝트 아이디
- touchAnchorSide : 추적하고 싶은 오브젝트의 측면쪽정의. right, left, top, bottom 중 택1
- dragDirection : 추적하고 싶은 모션의 방향 정의. dragRight, dragLeft, dragUp, dragDown 으로 정의 할 수 있다. MotionLayout의 progress가 0부터 1까지 어떻게 변화 할것인지에 대한 정의입니다.
- maxVelocity : touch up 될 때, 애니메이션의 최대 속력값 정의
- maxAcceleration : touch up 될 때, 얼마나 빨리 애니메이션이 가속하고 감속하는지에 대한 정의
- moveWhenScrollAtTop : true 또느 false대입. RecyclerView나 NestedScrollView같은 스크롤 가능한 뷰가 스크롤될 때 애니메이션을 실행시킬지에 대한 정의입니다.
<OnClick>
targetView를 클릭했을때 어떤 화면전환 애니메이션 효과를 보여줄지 정의합니다.
- target : 화면전환을 트리거할 뷰 아이디
- mode : 애니메이션을 실행하는 방법에 대한 정의입니다.
- transitionToEnd – 현재 프레임에서 애니메이션 마지막쪽으로 애니메이션을 실행합니다.
- transitionToStart – 현재 프레임에서 애니메이션의 시작점으로 애니메이션을 실행합니다.
- toggle – 현재 프레임에서 시작점 또는 끝점으로 애니메이션을 토글시키며 애니메이션을 실행합니다.
- jumpToEnd – 애니메이션을 실행시키지 않고 마지막 프레임으로 이동합니다.
- jumpToStart – 애니메이션을 실행시키지않고
<KeyFrameSet>
KeyFrame을 적용하면 자연스럽게 애니메이션에 커브를 적용하거나 모핑을 시도 할 수 있습니다. KeyFrameSet에 적용할 수 있는 태그는 다음과 같습니다
- KeyPosition
- KeyAttribute
- KeyCycle
아래의 예제를 확인해주세요
<Transition ...> <KeyFrameSet> <KeyPosition motion:keyPositionType="parentRelative" motion:percentY="0.25" motion:framePosition="50" motion:target="@+id/button"/> </KeyFrameSet> </Transition>
<KeyPosition>
키프레임에서의 뷰의 위치를 지정합니다.
- target : 타겟 뷰의 아이디
- framePosition : 전체 애니메이션의 프레임을 100이라고 가정할 때 키프레임이 될 프레임의 위치입니다. 0~100 입력
- transitionEasing : 애니메이션이 실행될때 완만한 곡선을 그리기 위한 값을 정의합니다.
- percentX, percentY : pathRelative내에서 X축 또는 Y축을 따라 비율적으로 거리를 나타냅니다.
- curveFit : 직선을 기반으로 하는 경로(linear) 또는 단조로운 스플라인 곡선을 기반으로 하는 경로를 선택합니다.(spline)
- drawPath : 디버깅을 위해 경로가 나타납니다.
- sizePecent : 뷰 사이즈를 조절합니다.
- keyPositionType : 선형 경로에 대한 키프레임의 편차를 계산하는 방법을 결정합니다.
- deltaRelative : 시작/ 끝 위치를 계산하며 백분율로 나타냅니다.
parentRelative와 마찬가지로 상대적으로 직관적인 좌표계이며 일반적으로는 좋은 결과를 나타냅니다. 뷰가 수평 또는 수직 동작으로 시작되거나 끝날때 유용합니다.
잠재적인 문제가 하나 있는데, 위젯의 시작 위치와 끝 위치의 차이에 따라 정의되므로 차이가 매우 작거나 0인 경우 영향을 받는 축에서 키 프레임의 위치가 변경되지 않습니다. 예를 들어 뷰가 화면의 왼쪽에서 오른쪽으로 이동하면서 동일한 높이에 머무르는 경우 위치 키프레임에 deltaRelative percentY를 사용해봐야 아무런 일이 일어나지 않습니다. - pathRelative : 시작 상태와 끝 상태 사이의 직선 경로를 기준으로 정의 됩니다. deltaRelative를 좌표계로 사용했을때 생기던 문제점을 해결할 수 있으며, 세로 축에서 이동하지 않는 위젯에서도 pathRelative를 사용하면 위치 키 프레임을 이탈 경로로 설정할 수 있습니다. -좌표도 지원합니다. 끝점이 변경 되더라도 일정하게 유지되는 곡선 모양(예 : “S”모양)을 얻을 수 있습니다.
- parentRelative : 좌표가 부모 컨테이너 기준으로 표현됩니다. 키프레임의 위치를 표현하는 매우 직관적인 방법입니다. 일반적으로 컨테이너에 상대적이여야 하는 계산을 할 때 이 방법을 이용합니다.
이 좌표계는 부모 위치에만 기반을 두고있기 때문에 , 결과적으로 키프레임의 위치가 차선적 위치(시작/끝 위치에 상대적)로 끝나는 상황이 발생할 수 있습니다. - pathMotionArc : 둥근 활모양을 어떻게 그릴지 정의합니다. (none, flip, startHorizontal, startVertical)
- deltaRelative : 시작/ 끝 위치를 계산하며 백분율로 나타냅니다.
<KeyAttribute>
KeyFrame내에서 KeyPosition과 비슷한 역할을 하지만 Position이 아닌 Attribute(속성)에 관여 한다.
<KeyFrameSet> <KeyAttribute android:scaleX="2" android:scaleY="2" android:rotation="-45" motion:framePosition="50" motion:target="@id/button" /> </KeyFrameSet>
- KeyFrame과 비슷한 속성을 가지고 있음( target, framePosition, curveFit, transitionEasing, transitionPathRotate, drawPath, progress)
- View에서 사용하는 attribute를 사용할 수 있음. (visibility, alpha, elevation, rotation, rotationX, rotationY, scaleX, scaleY, translationX, translationY, translationZ)
elevation과 translationZ는 API Level 21 이상에서 사용가능합니다.
<ConstraintSet>
ConstraintSet의 기본적인 개념은 모든 레이아웃 위치에 대해 캡슐화를 하는 것입니다. View를 재생성할 필요 없이 View에 대한 위치 및 수치만 바꿀 수 있습니다.
TransitionManager와 함께 동작하며 MotionLayout의 가장 기초적인 부분이 됩니다.
여러개의 <Constraint>를 포함할 수 있습니다.
<Constraint>
특정 뷰에 위치나 크기에 대한 대한 제약조건을 정의할 수 있습니다.
- View Attribute + ConstraintLayout Attribute 정의
- transitionEasing : 이 지점에 애니메이션을 적용할 때 사용할 완만한 곡선 정의
- transitionPathRotate : float 값을 대입. 오브젝트를 상대적으로 회전
- drawPath : 애니메이션이 작동될 위치의 path가 나타남. 테스트목적이며 실제 출시될 앱에는 나오지 않는것이 좋다.
- progress : 해당뷰의 setProgress(float)를 호출한다.
<CustomAttribute>
<Constraint>에 속하며 뷰에 대한 속성을 변경시킬 수 있다. start되는 constraint에서 정의했다면 end쪽도 정의해야합니다.
- attributeName : 속성에 대한 이름. Case senstive하다. BackgroundColor라고 적으면 setBackgroundColor메소드를 찾게 된다.
- 입력에 대한 값은 다음과 같은 타입으로 정의 될 수 있다.
- customColorValue
- customIntegerValue
- customFloatValue
- customStringValue
- customDimension
- customBoolean
Conclusion
아직 Alpha 단계의 라이브러리라 관련 자료가 턱없이 부족하며, 구글 안드로이드 개발자 Nicolas Roard의 포스팅을 참조한것이 9할입니다. (part1, part2, part3, part4 추후 더 포스팅 예정인듯 합니다.) MotionLayout으로 부터 나오는 산출물들이 Awesome인지라 자꾸자꾸 파보게 됩니다.
CoordinatorLayout 없이 만든 화면
직접 샘플 프로젝트 테스트해보고, 개인적으로 이런 저런 테스트해본 결과 아직 애니메이션 관련해서 매끄럽지 않은 부분이 많이 보이지만, 정식버전으로 릴리즈 될때는 많은것들이 수정되고 안정화 될것이라고 생각합니다.
3개의 댓글
굥이 · 2019년 6월 27일 5:39 오후
안녕하세요 구현 중에 궁금한게 하나 있습니다.
Activity가 [ ] 로 표현된다고 하면 아래처럼 구성하고 있습니다
– [Recycler 2 : Fragment 8]
Recycler를 터치해 오른쪽으로 드래그 하여 5 : 5 비율로 바꾸고 싶습니다.
한데 onSwipe의 touchAnchorId 를 Recycler로 잡고 dragDirection을 dragRight로 설정했는데
막상 Drag를 하면 작동하지 않습니다.
웃긴건 dragLeft로 설정하면 오른쪽으로 드래그 했을때 원하는대로 나옵니다.
왜이런걸까요?
굥이 · 2019년 6월 27일 5:40 오후
ㅇㅇㅇㅇ
Jason · 2019년 10월 4일 11:25 오전
감사합니다!