본문 바로가기

ANDROID

Android UI - Animation (Loading)

1. 이미지 안에 있고 뒤에 프로그레스바가 감싸져 있는 애니메이션

   - 하지만 컨텐츠가 표시되기 전에 항상 로딩 UI가 보이면 조금 과한 느낌을 줄수가 있다.

   - ContentLoadingProgressBar는 컨텐츠를 불러오는 속도가 느릴때만 로딩 UI를 보여준다 (ex : API 응답이 느린경우)

(1) drawable 제작

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:gravity="center">
        <rotate
            android:drawable="@drawable/ic_launcher_background"
            android:pivotX="50%"
            android:pivotY="50%"
            android:fromDegrees="0"
            android:toDegrees="360"
            />
    </item>
    <item android:drawable="@drawable/ic_launcher_foreground"
            android:gravity="center"/>

</layer-list>

(2) 프로그레스바

<ProgressBar
	style="@style/Widget.AppCompat.ProgressBar"
    ...
    android:indeterminateDrawable="@drawable/loading"
    android:indeterminateDuration="1000" />

- 500ms 이내에 불러오면 로딩 UI를 보여주지 않고

   한번 보여진 UI는 최소 500ms동안 보여준다.

public class ContentLoadingProgressBar extends ProgressBar {
	private static final int MIN_SHOW_TIME = 500;
    private static final int MIN_DELAY = 500;
    
    public synchronized void show() {
    	postDelayed(mDelayedShow, MIN_DELAY);
    }
    
    public synchronized void hide() {
    	long diff = System.currentTImeMillis() - mStartTime;
        if(diff >= MIN_SHOW_TIME || mStartTime == -1) {
        	setVisibility(View.GONE)
        }
        else {
        	postDelayed(mDelayedHide, MIN_SHOW_TIME - diff);
        }
    }
}

2. ProgressBar 와 Drawable를 이용하여, Progress Animation을 만듬

   재생, 녹음/녹화, 다운로드 등에 사용 가능하다.

(1) 바깥쪽 프로그레스바

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item>
        <shape android:shape="ring"
            android:thickness="5dp"
            android:useLevel="false">
            <solid android:color="#ffffff" />
        </shape>
    </item>
    <item android:drawable="@drawable/ic_launcher_foreground"
        android:gravity="center"/>
</layer-list>

(2) 안쪽 프로그레스바

<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
    android:fromDegrees="270"
    android:toDegrees="270">
    <shape android:shape="ring"
        android:thickness="5dp"
        android:useLevel="true">
        <solid android:color="@color/colorPrimary"/>
    </shape>
</rotate>

(3) xml

<ProgressBar
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
android:layout_width="150dp"
android:layout_height="150dp"
android:indeterminate="false"
android:progressDrawable="@drawable/file_down_2"
android:background="@drawable/file_down"
android:max="500"
tools:progress="200" />

3. Frame Animation

로딩 UI를 Frame Animation 으로 보여주고 싶다면

AnimationDrawable을 이용하여 손쉽게 구현 가능 (아래 사진이 계속 반복되는 형태)

(1) 애니메이션 제작 (drawable)

<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
    android:oneshot="false">
    <item android:drawable="@drawable/ic_launcher_foreground"
    android:duration="500" />
    <item android:drawable="@drawable/ic_launcher_foreground"
        android:duration="500" />
    <item android:drawable="@drawable/ic_launcher_foreground"
        android:duration="500" />
    <item android:drawable="@drawable/ic_launcher_foreground"
        android:duration="500" />
    <item android:drawable="@drawable/ic_launcher_foreground"
        android:duration="500" />
</animation-list>

(2) 적용

<ImageView />

val drawable: AnimationDrawable = ...
imageView.setImageDrawable(drawable)
drawable.start()
drawable.stop

   - 지금같은 내용은 좀 지저분해질수 있어서 아래 의 클래스를 이용하는걸 추천

<com.example.widget.AnimationImageView
	android:src="@drawable/frame_loading" />

(2-1) 예시코드 (kt)

class AnimatedImageView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null
) : AppCompatImageView(context, attrs) {

    private var anim: AnimationDrawable? = null
    private var isAttached: Boolean = false

    // Tracks the last image that was set, so that we don't refresh the image if it is exactly
    // the same as the previous one. If this is a resid, we track that. If it's a drawable, we
    // track the hashcode of the drawable.
    private var drawableId: Int = 0

    private fun updateAnim() {
        val drawable = drawable
        if (isAttached) {
            anim?.stop()
        }
        if (drawable is AnimationDrawable) {
            anim = drawable
            if (isShown) {
                drawable.start()
            }
        } else {
            anim = null
        }
    }

    override fun setImageDrawable(drawable: Drawable?) {
        if (drawable != null) {
            if (drawableId == drawable.hashCode()) return
            drawableId = drawable.hashCode()
        } else {
            drawableId = 0
        }
        super.setImageDrawable(drawable)
        updateAnim()
    }

    override fun setImageResource(resid: Int) {
        if (drawableId == resid) return
        drawableId = resid
        super.setImageResource(resid)
        updateAnim()
    }

    override fun onAttachedToWindow() {
        super.onAttachedToWindow()
        isAttached = true
        updateAnim()
    }

    override fun onDetachedFromWindow() {
        super.onDetachedFromWindow()
        anim?.stop()
        isAttached = false
    }

    override fun onVisibilityChanged(changedView: View, vis: Int) {
        super.onVisibilityChanged(changedView, vis)
        if (isShown) {
            anim?.start()
        } else {
            anim?.stop()
        }
    }
}

4. Notification Icon

    - API 21 이상 가능

<animation-list android:oneshot="false">
	<item
    	android:drawable="@drawable/stat_sys_download_anim0"
        android:duration="200"/>
    <item
    	android:drawable="@drawable/stat_sys_download_anim1"
        android:duration="200"/>
    ...
</animation-list>

Notification.Builder(...)
	.setSmailIcon(R.drawable.stat_sys_download)
    ...
    .build()