[Android] RenderThread와 AnimatedVectorDrawable

Joy·2022년 4월 5일
0
post-thumbnail

android studio arctic fox | 2020.3.1 Patch 4

📚 RenderThread를 알아본 이유?

회사에서 카메라 동작 인식과 Lottie 애니메이션을 함께 사용하는데 모바일 앱에서는 괜찮았지만 tv앱에서는 Lottie 애니메이션이 나올 때마다 몸에 찍어놓은 스켈레톤 UI 그리는 속도가 느려져서 운동 인식이 멈춰보이는 현상이 있었습니다.

Lottie 애니메이션이 실행될 때마다 몸을 인식하는 스켈레톤이 멈추지만 운동 카운팅도 잘 올라가고, 로그도 문제 없이 찍히는 것으로 봤을 때 인식에는 문제 없다는 것을 알았습니다.

TV 셋톱박스 OS 사양이 안드로이드 폰 보다 낮은데 UI 스래드에서 너무 동작을 많이하는 것이 문제인 것 같아서 먼저 다른 방법이 있는지 Lottie API를 읽어보았습니다.

읽다보니 보니 Lottie vs Android Vector Drawable 부분이 있었는데 Lottie의 장점과 AnimatedVectorDrawable의 장점이 간단하게 쓰여있었습니다.

그리고 AnimatedVectorDrawable의 장점에 'RenderThread에서 실행되는 애니메이션으로 인해 성능이 더 빨라졌습니다.' 한 줄이 적혀 있었고 메인스레드가 아닌 다른 스레드에서 애니메이션이 동작할 수 있다는 사실을 확인하기 위해 알아보았습니다.

참고로 AnimatedVectorDrawable을 적용하니 스켈레톤도 잘 그려지고 애니메이션도 잘 동작하게 되었습니다.

📚 RenderThread와 AnimatedVectorDrawable

먼저 Lottie 문서에 참조되어 있던 RenderThread 페이지에 들어가서 RenderThread가 무엇인지 읽어보았습니다. 요약하자면 UI 스래드가 아닌 UI를 그릴 수 있는 또 다른 스래드 입니다.

AnimatedVectorDrawable은 API 25 이상일 경우 RenderThread에서 실행 됩니다. 따라서 UI 스래드에 지연이 있더라도 AnimatedVectorDrawable을 적용한 애니메이션은 영향을 받지 않습니다.(참고: Android 공식문서)

📚 구현

공식 문서에 있는 animated-vector XML을 사용하여 버튼을 누르면 에니메이션이 동작하고 꺼지는 코드를 구현해봤습니다.

📍drawable에 sample_anim.xml 추가

  • sample_anim.xml
<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:height="64dp"
             android:width="64dp"
             android:viewportHeight="600"
             android:viewportWidth="600" >
             <group
                 android:name="rotationGroup"
                 android:pivotX="300.0"
                 android:pivotY="300.0"
                 android:rotation="45.0" >
                 <path
                     android:name="v"
                     android:fillColor="#000000"
                     android:pathData="M300,70 l 0,-70 70,70 0,0 -70,70z" />
             </group>
         </vector>
     </aapt:attr>

     <target android:name="rotationGroup"> *
         <aapt:attr name="android:animation">
             <objectAnimator
             android:duration="6000"
             android:propertyName="rotation"
             android:valueFrom="0"
             android:valueTo="360" />
         </aapt:attr>
     </target>

     <target android:name="v" >
         <aapt:attr name="android:animation">
             <set>
                 <objectAnimator
                     android:duration="3000"
                     android:propertyName="pathData"
                     android:valueFrom="M300,70 l 0,-70 70,70 0,0 -70,70z"
                     android:valueTo="M300,70 l 0,-70 70,0  0,140 -70,0 z"
                     android:valueType="pathType"/>
             </set>
         </aapt:attr>
      </target>
 </animated-vector>

📍sample_anim.xml을 사용할 AnimationUtil 추가

  • AnimationUtil.kt
import android.content.Context
import android.graphics.drawable.Animatable2
import android.graphics.drawable.AnimatedVectorDrawable
import android.graphics.drawable.Drawable
import android.view.View
import android.widget.ImageView
import androidx.core.content.ContextCompat

class AnimationUtil(context: Context) {
    private val sampleVector: AnimatedVectorDrawable = ContextCompat.getDrawable(context, R.drawable.sample_anim) as AnimatedVectorDrawable

    fun playAnimation(imageView: ImageView){
        imageView.setImageDrawable(sampleVector)
        imageView.visibility = View.VISIBLE
        sampleVector.start()
        sampleVector.registerAnimationCallback(object : Animatable2.AnimationCallback() {
            override fun onAnimationEnd(drawable: Drawable?) {
                super.onAnimationEnd(drawable)
                imageView.visibility = View.INVISIBLE
                sampleVector.stop()
            }
        })
    }
}

📍Activity에서 playAnimation 호출

  • activity_main.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=".MainActivity">

    <ImageView
        android:id="@+id/image_view"
        android:layout_width="200dp"
        android:layout_height="200dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/animation_start"
        app:layout_constraintTop_toBottomOf="@+id/image_view"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>
  • MainActivity.kt
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {
    private val animationUtil: AnimationUtil by lazy { AnimationUtil(this) }
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        button.setOnClickListener {
            animationUtil.playAnimation(image_view)
        }
    }
}

📍실행화면

  • 위 코드는 Github에 올려두었으니 참고해주세요.
profile
어떤 것이든 그것이 지닌 특별한 속성이나 가치를 간과해서는 안 된다.

0개의 댓글