[Android] 증가하는 시간 차 그래프 애니메이션 구현하기 - (1) ValueAnimator
안드로이드 개발을 하면 할수록 그 어떤 것 보다 예상 소요 시간 , 사용 기술을 확답할 수 없는 것이 바로 처음 보는 UI/UX 를 요구받았을 때 인것 같다. 나 역시도 그러한데, 이번 포스트는 출시
sxunea.tistory.com
앞선 글에서 ValueAnimator를 통해, 원하는 만큼의 너비까지 증가하도록 구현했지만 생각과 달리 애니메이션이 제대로 동작하지 않는다는 문제를 발견했다. 각 그래프의 너비를 한달 후는 전체 너비의 3/5, 세달 후는 2/3, 일년 후는 5/6 로 지정하고자 하였는데, 모든 그래프가 똑같은 길이 (화면 전체 너비)만큼 증가하고, 왼쪽에 있는 일반 textView를 침범했다.
그 이유는 애니메이션에 해당하는 textView의 최대 너비는 전체 너비에서 같은 선 상에 있는 다른 뷰 들의 너비를 제외한 길이가 되어야 하는데, 그 값이 측정되기 전에, 즉 레이아웃이 완료되기 전에 값을 가져왔기 때문이다.
따라서 애니메이션을 동작시키면 뷰의 너비를 측정하지 못한 상태에서 애니메이션이 시작되기 때문에 원했던 너비를 초과해서 다른 뷰의 영역까지 침범해 UI가 원하는 대로 구현되지 않았다.
이를 해결하기 위해 애니메이션이 시작되기 전에 뷰를 측정하기 위한, 즉 뷰의 변경을 감지하기 위한 도구를 찾아보았고, 그 결과 ViewTreeObserver를 도입하게 되었다.
ViewTreeObserver
A view tree observer is used to register listeners that can be notified of global changes in the view tree. Such global events include, but are not limited to, layout of the whole tree, beginning of the drawing pass, touch mode change.... A ViewTreeObserver should never be instantiated by applications as it is provided by the views hierarchy.
간단히 말하면 ViewTree를 관찰한다. 이 ViewTreeObserver는 다양한 리스너를 가지고 있으며, 각 리스너의 역할에 따라 ViewTree에 일어나는 변경사항을 감지할 수 있다.
Listener 종류
Listener | Description |
ViewTreeObserver.OnDrawListener | 뷰가 그려질 때 |
ViewTreeObserver.OnGlobalFocusChangeListener | 뷰 트리의 내의 포커스가 변경될 때 |
ViewTreeObserver.OnGlobalLayoutListener | 뷰 트리의 global layout 상태나 visibility 여부가 변경될 때 |
ViewTreeObserver.OnPreDrawListener | 뷰 트리가 그려지려고 할 때 |
ViewTreeObserver.OnScrollChangedListener | 뷰 트리 항목이 스크롤 되었을 때 |
ViewTreeObserver.OnTouchModeChangeListener | 터치 모드가 변경될 때 |
ViewTreeObserver.OnWindowAttachListener | 뷰 트리가 window에 attach 되거나 detach될 때 |
ViewTreeObserver.OnWindowFocusChangeListener | 뷰 트리의 window focus 상태가 변경 될 때 |
리스너의 종류는 다음과 같고, 그 전에 onScrollChangedListener를 사용해본 적이 있었는데 이게 ViewTreeObserver에 속해있었던 줄도 몰랐어서 잠깐 반성의 시간을 갖기도 했다.
오늘 포스팅은 이 리스너 중 OnGlobalLayoutListener를 활용해 문제를 해결했던 과정에 대해 이야기 해보자.
GlobalLayoutListener
GlobalLayoutListener는 뷰 트리의 전역 레이아웃 상태나, 가시성 여부가 변경될 때 호출되는 리스너이다. 주로 뷰의 초기 레이아웃이 완료된 후에 수행할 작업을 정의할 때 사용된다. 따라서 나는 textView의 크기가 결정되고 애니메이션이 호출될 수 있도록 이를 도입했다.
따라서 이를 바탕으로 구현한 코드는 다음과 같다.
private fun animateTextView(
textView: TextView,
amount: Int,
) {
val params = textView.layoutParams
val parentView = textView.parent as ViewGroup
val textViewWidth = parentView.measuredWidth - binding.tvMypage2weeks1Year.width
val width = getGraphAnimationWidth(textViewWidth, periodType)
textView.viewTreeObserver.addOnGlobalLayoutListener(object :
ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
val animator = ValueAnimator.ofInt(0, width).apply {
duration = ANIMATION_DURATION.toLong()
addUpdateListener { valueAnimator ->
params?.width = valueAnimator.animatedValue as Int
textView.requestLayout()
}
.. 생략
textView.viewTreeObserver.removeOnGlobalLayoutListener(this)
.. 생략
}
})
}
- addOnGlobalLayoutListener 호출:
- textView.viewTreeObserver.addOnGlobalLayoutListener를 통해 GlobalLayoutListener를 등록한다
- 이 리스너는 textView의 레이아웃이 완료된 후 호출된다
- 애니메이션 설정:
- 레이아웃이 완료된 후, ValueAnimator를 사용하여 뷰의 너비를 애니메이션으로 조정한다
- 여기는 앞 포스트에 자세히 나와 있으니 설명 및 코드는 생략한다
- removeOnGlobalLayoutListener 호출:
- 리스너는 한 번만 필요하기 때문에, 작업이 완료된 후 리스너를 제거한다
GlobalLayoutListener를 사용하면 꼭 놓치지 않아야 하는 부분이 3번으로, 리스너를 등록하고 작업을 완료하고 나면 이를 꼭 제거해 주어야한다. 제거해 주지 않으면 OnGlobalLayoutListener가 불필요하게 반복 호출되므로, 성능저하나 메모리 누수의 원인이 될 수 있다.
이를 통해서 textView의 너비를 결정하고, 즉 뷰의 크기를 결정한 후 애니메이션을 시작하도록 구현했다. 이를 통해서 레이아웃이 완료되기 전에 애니메이션이 시작되어 잘못된 UI가 나타나는 문제를 해결했다. 이렇게 ViewTreeObserver는 다양한 리스너를 가지고 뷰의 변경사항을 다양한 시점에서 관찰하고 호출할 수 있으니 적합한 상황에서 잘 사용해보자.
첫 포스트에서
- 사용자가 저 뷰를 봤을때 (스크롤해서 내려갔을때) 애니메이션이 시작되게 해주세요.
이 요구사항은 따로 포스팅을 통해 방법을 언급하겠다고 했는데, 실제로 이에 대한 내용이 애니메이션 개발기의 마지막 포스트가 될 예정이고, 심지어 이곳에서도 ViewTreeObserver를 사용했다. 사실 오늘 포스트만 잘 읽었어도 어떤 리스너를 사용했을 지는 감이 올 것이다 ㅎㅎㅎ 아무튼 이만 줄이고 OnGlobalLayoutListener의 쓰임을 잘 기억해두자
이어서 ➡️
[Android] 증가하는 시간 차 그래프 애니메이션 구현하기 - (3) ViewTreeObserver, OnScrollChangedListener
[Android] 증가하는 시간 차 그래프 애니메이션 구현하기 - (2) ViewTreeObserver, GlobalLayoutListener[Android] 증가하는 시간 차 그래프 애니메이션 구현하기 - (1) ValueAnimator안드로이드 개발을 하면 할수록 그
sxunea.tistory.com
참고자료
ViewTreeObserver | Android Developers
developer.android.com