안드로이드 개발을 하면 할수록 그 어떤 것 보다 예상 소요 시간 , 사용 기술을 확답할 수 없는 것이 바로 처음 보는 UI/UX 를 요구받았을 때 인것 같다. 나 역시도 그러한데, 이번 포스트는 출시 후 아직도 참여를 이어가고 있는 사이드 프로젝트에서 애니메이션을 요청받아 구현한 기록을 남기려고 한다.
처음에 가능할까요 . . ? 라고 질문을 받았을 땐 아 그냥 막대그래프 오른쪽으로 증가 ? 뭐 그정도는 할 수 있을 것 같아요 ! 가 답변이었다. 하지만 본격적으로 개발을 하려고 요구사항을 제대로 읽어보자 무엇을 사용해서 구현해야하는지가 고민이었고, 그 답으로는 value animator를 택했다.
value animator는 공식문서도 리터럴리 document.. 일 뿐 이고, 블로그 레퍼런스도 별로 없어서 자세히 기록해보고자 한다. 그니까 거두절미하고, 무엇을 개발했는지 살펴보자.
구현 화면 및 요구사항
실제 구현한 화면으로, 요구사항은 다음과 같았다.
- 사용자가 저 뷰를 봤을때 (스크롤해서 내려갔을때) 애니메이션이 시작되게 해주세요.
- 애니메이션이 0부터 시작하진 말고 1,3,12 달 간의 차이를 두고 증가하게 해주세요. (특정 비율 존재)
- 1, 3, 12 달의 애니메이션이 각각 시간차를 두게 해주세요
- 금액을 나타내는 텍스트가 있어야하고, 해당 텍스트가 처음에는 나타나지 않고 애니메이션이 완료되면 뜨게 해주세요.
일단 첫번째 요구사항은 그 자체로 기록할 것 + 공부한 것이 많으므로 다음에 따로 포스팅하고, 오늘은 애니메이션에 대한 것만 알아보자. 다시 그때로 돌아가면, 와이어프레임으로는 '그냥 막대그래프가 오른쪽으로 증가하면 되겠는데'만 생각했던 나는 그럼 텍스트는 어떡하지 ? 라는 문제에 봉착했다. 애초에 저 네모 상자를 어떤 뷰로 구현할 지 부터가 고민이었던 것이다.
처음 아이디어는 네모 상자를 View로, 텍스트를 textView로 따로 구현이었다. 가능한 방법이긴 하지만 뷰에는 애니메이션을 넣고, 그 뷰가 커짐에 따라 텍스트뷰가 쫓아가고, 와중에 텍스트뷰의 나타남 시점까지 함께 관리하는 것이 다소 비효율적이겠다는 판단이 들었다.
사실 이때 위 방식으로 구현을 해놓고, 마음에 들지 않아서 하루 정도 덮어두었다가 다시 노트북을 키고 우연히 아이디어가 떠올랐다.
저걸 막대 그래프라고 보지 말고 그냥 텍스트의 배경이라고 보면 어떨까 ? 라는 아이디어로 출발해 그냥 저 그래프 자체를 textView의 background xml로 구현했다. 구현 또한 간편하고, 애니메이션 실행 중 textView하나만 관리해주면 되니 개발자 입장에서도 신경쓸게 덜어졌다.
이렇게 레이아웃을 다 짜두었고, 다음은 본격적으로 애니메이션을 구현했다.
Value Animator
This class provides a simple timing engine for running animations which calculate animated values and set them on target objects.
앞서의 요구사항을 다시 살펴보면 만들어야할 애니메이션은 '특정 지점(값)부터 시작해 특정 지점(값)까지 증가' 해야한다. Value Animator는 안드로이드 애니메이션 API 중에 하나로, 값의 변화에 집중한 애니메이션이다. 따라서 사용자가 절약한 금액을 1, 3, 12달에 따라 시각화해서 보여주어야 했던 당시 요구사항에 맞추어 Value Animator를 선택했다.
private fun animateTextView(
textView: TextView,
amount: Int,
) {
val params = textView.layoutParams
val animator = ValueAnimator.ofInt(0, width).apply {
duration = ANIMATION_DURATION.toLong()
addUpdateListener { valueAnimator ->
params?.width = valueAnimator.animatedValue as Int
textView.requestLayout()
}
}
animator.start()
}
가장 기본형으로, 당시 나는 여러개의 textView에 애니메이션을 적용하고 있어서 인자로 값과 텍스트뷰를 받았다. 자세히 살펴보자.
- ValueAnimator.ofInt(0, width) 를 통해 객체를 생성하고 0 ~ width 까지의 값을 애니메이션으로 생성한다
- duration 속성을 활용해 지속 시간을 지정한다
- addUpdateListener는 애니메이션이 업데이트될 때마다 호출된다
- textView.params, 즉 너비를 현재 애니메이션 값으로 설정한다
- textView.requestLayout()을 통해 해당 레이아웃을 다시 요청하여 화면에 변경 사항을 반영한다
애니메이션 상태 리스너 활용하기
value animator는 애니메이션 안에서의 상태 리스너 활용이 가능한데, 다음과 같다.
- doOnEnd
- doOnCancel
- doOnPause
- doOnResume
- doOnStart
- doOnRepeat
각 상태 별로의 리스너를 활용해, 그에 맞는 추가적인 처리 및 효과를 주는게 가능하다. 앞선 요구사항을 보면, 텍스트가 시작할 땐 없고, 애니메이션이 완료되면 나타나게 해야하므로 이를 활용했고, 추가하면 다음과 같다.
private fun animateTextView(
textView: TextView,
amount: Int,
) {
val params = textView.layoutParams
val animator = ValueAnimator.ofInt(0, width).apply {
duration = ANIMATION_DURATION.toLong()
addUpdateListener { valueAnimator ->
params?.width = valueAnimator.animatedValue as Int
textView.requestLayout()
}
doOnStart {
textView.text = ""
}
doOnEnd {
textView.text =
String.format(
getString(R.string.mypageString),
amount.formatAmountNumber()
)
}
}
}
animator.start()
}
위와 같이 doOnStart에서는 텍스트에 빈 값을 주고, doOnEnd 즉 완료가 되면 텍스트에 절약한 값을 넣어주고 반영시켰다.
애니메이션 제어하기
앞선 코드의 마지막 줄을 보면 animator.start()가 있다. 한눈에 알 수 있듯이, 고맙게도 애니메이션의 제어 또한 가능하다.
- animator.start()
- animator.cancel()
- animator.pause()
- animator.resume()
애니메이션 상태 가져오기
마지막으로 애니메이션의 현재 상태가 어떤지 또한 받아와서 활용할 수 있다.
- isRunning: 애니메이션이 현재 실행 중인지 여부를 확인
- isPaused: 애니메이션이 일시 정지 상태인지 여부를 확인
- isStarted: 애니메이션이 시작되었는지 여부를 확인
- isCancelled: 애니메이션이 취소되었는지 여부를 확인
애니메이션 반복
- repeatCount: 애니메이션 반복 횟수를 설정한다. ValueAnimator.INFINITE를 설정하면 무한 반복
- repeatMode: 애니메이션 반복 방식을 설정한다. REVERSE를 설정하면 역방향으로 반복된다
이렇게 value animator는 값의 변화를 애니메이션으로 보여주는 데 적합하고, 위의 내용처럼 다양한 속성과 함수를 제공해주기 때문에 쉽게 구현이 가능하다. 이렇게 애니메이션을 구현했고, 그 다음엔 그 과정에서 일어났던 뷰 사이즈의 문제점과 그 트러블 슈팅에서 배웠던 점에 대해서 이어서 알아보자.
이어서 ➡️
[Android] 증가하는 시간 차 그래프 애니메이션 구현하기 - (2) ViewTreeObserver, GlobalLayoutListener
[Android] 증가하는 시간 차 그래프 애니메이션 구현하기 - (1) ValueAnimator안드로이드 개발을 하면 할수록 그 어떤 것 보다 예상 소요 시간 , 사용 기술을 확답할 수 없는 것이 바로 처음 보는 UI/UX 를
sxunea.tistory.com
참고 자료
ValueAnimator | Android Developers
developer.android.com