AnimatedDrawable
여러 개의 Drawable을 차례로 로드하여 애니메이션을 만드는 기본 애니메이션
몇 프레임이 안 되는 경우에는 그저 나열하는 것만으로 애니메이션을 만들 수 있지만...
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
android:oneshot="true">
<item android:drawable="@drawable/rocket_thrust1" android:duration="200" />
<item android:drawable="@drawable/rocket_thrust2" android:duration="200" />
<item android:drawable="@drawable/rocket_thrust3" android:duration="200" />
</animation-list>
아래와 같이 30 프레임 정도만 되어도 30개나 되는 Drawable이 존재할 뿐더러 animation-list 도 끔찍한 모습을 하게 된다.
단순 예시가 아니라 과거에 진짜 만들었던 애니메이션이다...
<?xml version="1.0" encoding="utf-8"?>
<animation-list
xmlns:android="http://schemas.android.com/apk/res/android"
android:oneshot="false">
<item android:drawable="@drawable/frame1" android:duration="30"/>
<item android:drawable="@drawable/frame2" android:duration="30"/>
<item android:drawable="@drawable/frame3" android:duration="30"/>
<item android:drawable="@drawable/frame4" android:duration="30"/>
<item android:drawable="@drawable/frame5" android:duration="30"/>
<item android:drawable="@drawable/frame6" android:duration="30"/>
<item android:drawable="@drawable/frame7" android:duration="30"/>
<item android:drawable="@drawable/frame8" android:duration="30"/>
<item android:drawable="@drawable/frame9" android:duration="30"/>
<item android:drawable="@drawable/frame10" android:duration="30"/>
<item android:drawable="@drawable/frame11" android:duration="30"/>
<item android:drawable="@drawable/frame12" android:duration="30"/>
<item android:drawable="@drawable/frame13" android:duration="30"/>
<item android:drawable="@drawable/frame14" android:duration="30"/>
<item android:drawable="@drawable/frame15" android:duration="30"/>
<item android:drawable="@drawable/frame16" android:duration="30"/>
<item android:drawable="@drawable/frame17" android:duration="30"/>
<item android:drawable="@drawable/frame18" android:duration="30"/>
<item android:drawable="@drawable/frame19" android:duration="30"/>
<item android:drawable="@drawable/frame20" android:duration="30"/>
<item android:drawable="@drawable/frame21" android:duration="30"/>
<item android:drawable="@drawable/frame22" android:duration="30"/>
<item android:drawable="@drawable/frame23" android:duration="30"/>
<item android:drawable="@drawable/frame24" android:duration="30"/>
<item android:drawable="@drawable/frame25" android:duration="30"/>
<item android:drawable="@drawable/frame26" android:duration="30"/>
<item android:drawable="@drawable/frame27" android:duration="30"/>
<item android:drawable="@drawable/frame28" android:duration="30"/>
<item android:drawable="@drawable/frame29" android:duration="30"/>
<item android:drawable="@drawable/frame30" android:duration="30"/>
</animation-list>
AnimatedVectorDrawable
Drawable은 여러 장을 빠르게 보여주면서 애니메이션을 만들었다면,
VectorDrawable은 속성 값을 변경시키면서 애니메이션을 만들 수 있다!
Path
svg에서 도형을 그리는 요소이다. 아래의 24dp*24dp 크기의 svg의 path는 아래와 같다.
M 12 2 C 6.47 2 2 6.47 2 12 C 2 17.53 6.47 22 12 22 C 17.53 22 22 17.53 22 12 C 22 6.47 17.53 2 12 2 Z
... 영문을 알 수 없는 숫자와 알파벳의 나열처럼 보일 수 있지만, 이 나열이 도형을 그리고 있다.
- M = moveto
- L = lineto
- C = curveto
- Z = closepath
M x, y | 새로운 path를 (x, y) 에서 시작 |
L x, y | 현재 위치에서 (x, y)로 직선 그리기 |
C x1, y1, x2, y2, x, y | (x, y) 까지 cubic bezier curve를 그린다. (x1, y1) (x2, y2)를 control point로 사용한다. |
Z | M 으로 설정한 시작 위치로 돌아오면 path를 끝냄. |
cubic bezier curve는 아래 사이트에서 움직여보면 control point가 대충 무슨 역할을 하는지 어렴풋이 알 수 있을 것이다.
지난 면접 중 베지에 곡선이 뭐냐는 질문을 받은 적이 있었는데, 여유가 생기면 한 번 공부해봐야겠다.
https://cubic-bezier.com/#0,.47,.47,0
path 명령을 하나씩 따라간다면 아래 그림에서 무지개색을 따라가듯 반시계 반향으로 원이 그려진다.
path 속에는 pathData 외에도 stroke, fill과 관련된 다양한 속성이 있다. 이런 속성에 ObjectAnimator를 붙여 조절함으로서 애니메이션을 생성할 수 있다.
AnimatedVectorDrawable 만들어보기
이 안드로이드가 한바퀴 빙글 도는 애니메이션을 만들어볼 것이다.
ic_android.xml
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="#20EE50"
android:viewportWidth="24"
android:viewportHeight="24">
<group
android:name="rotationGroup"
android:pivotX="12"
android:pivotY="12"
android:rotation="0.0">
<path
android:name="path"
android:fillColor="#FFFFFFFF"
android:pathData="M17.6,11.48 L19.44,8.3a0.63,0.63 0,0 0,-1.09 -0.63l-1.88,3.24a11.43,11.43 0,0 0,-8.94 0L5.65,7.67a0.63,0.63 0,0 0,-1.09 0.63L6.4,11.48A10.81,10.81 0,0 0,1 20L23,20A10.81,10.81 0,0 0,17.6 11.48ZM7,17.25A1.25,1.25 0,1 1,8.25 16,1.25 1.25,0 0,1 7,17.25ZM17,17.25A1.25,1.25 0,1 1,18.25 16,1.25 1.25,0 0,1 17,17.25Z" />
</group>
</vector>
24dp * 24dp의 중심인 (12, 12)를 피봇으로 하고 있고, 현재의 rotation 값은 0이다.
animator/rotation.xml
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="600"
android:propertyName="rotation"
android:valueFrom="360"
android:valueTo="0" />
rotation이라는 property를 0에서 360으로 600ms간 보간시키는 objectAnimator를 만들었다.
av_android.xml
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/ic_android">
<target
android:name="rotationGroup"
android:animation="@animator/rotation" />
</animated-vector>
ic_android 라는 drawable을 지정해주었고, target에는 대상으로 할 속성의 name 값과 animation을 지정해주었다. 한 바퀴 빙글~ 도는 애니메이션이 만들어졌다. target은 여러개의 대상에 각기 다른 animator를 지정할 수도 있다. 더 복잡하고 자세한 것은 백터 드로어블 개요를 참고 바란다.
더 편리하게 만들 수 있는 방법
부스트캠프 7기 그룹프로젝트의 로고 애니메이션 또한 AnimatedVectorDrawable으로 구성했다. pathData 속성을 변형시켜 자연스럽게 다른 모양으로 바뀌는 애니메이션을 만든 것이다. 하지만 위에서 살펴본 간단한 예시에서도 pathData는 복잡했다. 우리 인간이 pathData 값을 지정해서 애니메이션을 만드는 것은.. 상당히 어려운 일이다.
어떻게 만들었냐고요? Shape Shifter의 힘을 빌려 만들었지요
애니메이션의 시작과 끝 상태를 각각 svg로 export 하여 준비합니다. 왼쪽의 pathData를 오른쪽 pathData로 변하게 만들 것입니다.
Shape Shifter에서 svg를 import하면 아래와 비슷하게 vector 요소가 생깁니다. 빨간 화살표로 가리킨 시계 아이콘을 누르면 animation을 지정할 수 있는 속성을 선택할 수 있습니다. duration과 value를 지정해주면 아래와 같이 미리보기 애니메이션이 출력됩니다.
이렇게 생성되는 AnimatedVectorDrawable은 아래와 같습니다. 위에서 만들었던 3개의 xml 파일이 1개로 합쳐진 형태
<animated-vector
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt">
<aapt:attr name="android:drawable">
<vector
android:name="vector"
android:width="512dp"
android:height="512dp"
android:viewportWidth="512"
android:viewportHeight="512">
<path
android:name="path"
android:pathData="M 139 200.5 L 313 170.5 L 376.5 307.5 L 313 281 L 313 416 L 198 416 L 225.5 249 L 139 200.5 Z"
android:fillColor="#000000"
android:strokeWidth="1"/>
</vector>
</aapt:attr>
<target android:name="path">
<aapt:attr name="android:animation">
<set>
<objectAnimator
android:propertyName="pathData"
android:startOffset="604"
android:duration="114"
android:valueFrom="M 131.5 327.5 L 254.5 233 L 351 327.5 L 286.5 327.5 L 313 416 L 198 416 L 216.5 327.5 L 131.5 327.5 Z"
android:valueTo="M 129 182 L 256 96 L 383 182 L 313 182 L 313 416 L 198 416 L 198 182 L 129 182 Z"
android:valueType="pathType"
android:interpolator="@android:anim/fast_out_slow_in"/>
</set>
</aapt:attr>
</target>
</animated-vector>
위에서 나온 삡 애니메이션은
초록, 파랑, 빨강 3가지 부분이 동시에 따로 4단계에 걸쳐 움직이고 있는 형태이다. layer처럼 drawable을 쌓는 것이 가능하다. 빨강색 레이저 지잉- 하는 부분은 alpha이 0이었다가 일정 시점에서 1로 변경되면서 나타나고, 위에서 아래로 pathData를 보간하며 이동한다. xml로 작성했으면 무진장 헷갈렸을 것이다. Shape Shifter로 시각적으로 애니메이션을 구성할 수 있기에 조금 꼼수만 부리면 복잡한 애니메이션도 만들 수 있다.
pathData 뿐만 아니라 다른 속성도 편하게 컨트롤 할 수 있다. trimPathEnd 속성을 0 -> 1로 변화시키며 만든 progress 너낌의 애니메이션이다.
어때요? 참 쉽죠?
'Android' 카테고리의 다른 글
[Compose Navigation] 죽여도 죽여도 살아 돌아오는 끈질긴 ViewModel 본 사람? 저요 (2) | 2024.02.26 |
---|---|
[ViewModel] ViewModel이 달린 Fragment를 재사용했더니 전세계가 경악하고 구글이 벌벌떠는 일이 벌어졌습니다?! (3) | 2023.06.05 |
[Hilt] 보이지 않는 곳에서 무슨 일이 벌어지고 있었을까 (0) | 2023.05.24 |