AnimatedVectorDrawable이란, 애니메이션이 추가된 VectorDrawable 입니다.
pathData로 이루어진 Drawable에 자연스러운 애니메이션 효과를 추가할 수 있습니다.
대표적인 예시로 다음웹툰의 좌상단에 위치한 이미지가 있습니다.
다음웹툰의 UX에 관한 글 : https://brunch.co.kr/@kakao-it/279
이번에는 다음웹툰을 참고해서 H에서 Z로 변하는 AnimatedVectorDrawable을 구현해보도록 하겠습니다.
우선 사용할 벡터 아이콘이 필요하기 때문에, 각각 만들어주었습니다.
벡터 아이콘은 M, L, C, Z과 좌표 커맨드로 작성한 pathData로 이루어져 있으며, 해당 path값은 values/paths.xml 파일을 만들어 따로 관리하였습니다.
paths.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="path_h_1">M 0 0 L 0 24 L 6 24 L 6 0 Z</string>
<string name="path_h_2">M 24 0 L 24 24 L 18 24 L 18 0 Z</string>
<string name="path_h_3">M 0 9 L 24 9 L 24 15 L 0 15 Z</string>
<string name="path_z_3">M 24 0 L 18 0 L 0 24 L 6 24 Z</string>
<string name="path_z_1">M 0 0 L 24 0 L 18 8 L 0 8 Z</string>
<string name="path_z_2">M 0 24 L 6 16 L 24 16 L 24 24 Z</string>
</resources>
각 커맨드의 의미는 다음과 같습니다.
위 4가지 커맨드를 조합해서 원하는 벡터 이미지를 그릴 수 있습니다.
ic_h.xml
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="300dp"
android:height="300dp"
android:viewportHeight="30"
android:viewportWidth="30">
<group
android:name="rotate_1"
android:pivotY="12.0"
android:pivotX="12.0">
<path
android:name="h_1"
android:pathData="@string/path_h_1"
android:fillColor="@color/purple_200"/>
<path
android:name="h_2"
android:pathData="@string/path_h_2"
android:fillColor="@color/purple_200"/>
<path
android:name="h_3"
android:pathData="@string/path_h_3"
android:fillColor="@color/purple_200"/>
</group>
</vector>
ic_z.xml
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="300dp"
android:height="300dp"
android:viewportHeight="30"
android:viewportWidth="30">
<group>
<path
android:name="z_1"
android:pathData="@string/path_z_1"
android:fillColor="@color/teal_200"/>
<path
android:name="z_2"
android:pathData="@string/path_z_2"
android:fillColor="@color/teal_200"/>
<path
android:name="z_3"
android:pathData="@string/path_z_3"
android:fillColor="@color/teal_200"/>
</group>
</vector>
h_to_z.xml
<?xml version="1.0" encoding="utf-8"?>
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:drawable="@drawable/ic_h">
<target android:name="h_1" >
<aapt:attr name="android:animation">
<set
android:fillAfter = "true"
android:ordering="together">
<objectAnimator
android:propertyName="pathData"
android:valueType="pathType"
android:valueFrom="@string/path_h_1"
android:valueTo="@string/path_z_1"
android:duration="400" />
<objectAnimator
android:propertyName="fillColor"
android:valueFrom="@color/purple_200"
android:valueTo="@color/teal_200"
android:duration="400" />
</set>
</aapt:attr>
</target>
<target android:name="h_2" >
<aapt:attr name="android:animation">
<set
android:fillAfter = "true"
android:ordering="together">
<objectAnimator
android:propertyName="pathData"
android:valueType="pathType"
android:valueFrom="@string/path_h_2"
android:valueTo="@string/path_z_2"
android:duration="400" />
<objectAnimator
android:propertyName="fillColor"
android:valueFrom="@color/purple_200"
android:valueTo="@color/teal_200"
android:duration="400" />
</set>
</aapt:attr>
</target>
<target android:name="h_3" >
<aapt:attr name="android:animation">
<set
android:fillAfter = "true"
android:ordering="together">
<objectAnimator
android:propertyName="pathData"
android:valueType="pathType"
android:valueFrom="@string/path_h_3"
android:valueTo="@string/path_z_3"
android:duration="400" />
<objectAnimator
android:propertyName="fillColor"
android:valueFrom="@color/purple_200"
android:valueTo="@color/teal_200"
android:duration="400" />
</set>
</aapt:attr>
</target>
</animated-vector>
<animated-vector>
태그로 h에서 z로 변하는 AnimatedVectorDrawable을 작성합니다.
drawable에는 h 아이콘을 지정해주고, 하위 태그 <target>
아래에 애니메이션을 지정합니다.
이때, target의 name은 VectorDrawable에서 지정한 path의 name과 같게 지정해야 합니다.
<target android:name="h_1" >
<aapt:attr name="android:animation">
<set
android:fillAfter = "true"
android:ordering="together">
<objectAnimator
android:propertyName="pathData"
android:valueType="pathType"
android:valueFrom="@string/path_h_1"
android:valueTo="@string/path_z_1"
android:duration="400" />
<objectAnimator
android:propertyName="fillColor"
android:valueFrom="@color/purple_200"
android:valueTo="@color/teal_200"
android:duration="400" />
</set>
</aapt:attr>
</target>
예를 들어 위 코드는 h_1 name을 가진 path에 <set>
아래 지정된 애니메이션을 지정하는 코드 입니다.
위와 같은 방식으로 z에서 h로 변하는 AnimatedVectorDrawable도 작성합니다.
activity_vector_drawable.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".vectordrawable.VectorDrawableActivity">
<ImageView
android:id="@+id/vector_img"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/h_to_z"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
ImageView를 두고 정의한 AnimatedVectorDrawable를 지정합니다.
VectorDrawableActivity.kt
class VectorDrawableActivity : AppCompatActivity() {
private val image by lazy { findViewById<ImageView>(R.id.vector_img) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_vector_drawable)
image.setOnClickListener {
if ( image.tag == null ) {
image.setImageResource(R.drawable.h_to_z)
(image.drawable as AnimatedVectorDrawable).start()
image.tag = 1
} else {
image.setImageResource(R.drawable.z_to_h)
(image.drawable as AnimatedVectorDrawable).start()
image.tag = null
}
}
}
}
이미지뷰를 클릭할 때마다 h에서 z로, z에서 h로 변하도록 코드를 작성하였습니다.
이미지를 클릭하면 애니메이션이 실행되며 변화하는 것을 볼 수 있습니다.
path를 이용한 애니메이션을 구현하면 path가 자연스럽게 변화하는 것을 볼 수 있습니다.
이를 Path morphing이라고 하며, 구현 시 주의해야할 점이 몇 가지 있습니다.
주의할 점은 다음과 같습니다.
A path에서 B path로 변화할 때는 같은 수의 command가 필요함
ex) M .. L .. L .. L .. Z → M .. L .. Z 불가능
각 대응되는 커맨드는 같은 커맨드여야만 함
ex) M .. C .. Z → M .. L .. Z 불가능
Path morphing은 1:1 대응이여야만 함
ex) 3개의 pathData를 이용한 벡터 이미지에서 2개의 pathData를 이용한 벡터 이미지로의 path morphing은 불가능
다음웹툰 발표를 보면 모든 도형을 6등분 했다고 하는데, 이것 때문이 아닐까...라는 생각이 듭니다.