728x90
애니메이션 시작/종료 여부 확인
1. Listener 생성
- 일단 애니메이션의 시작/종료를 알릴 수 있는 Listener를 생성합니다.
- AnimationListener을 사용하지 않고 Listener를 만드는 이유는 단순히 시작/종료 타이밍을 알려는 것이 아닌 애니메이션이 종료되면 결과를 리턴 받기 위함입니다.
package com.jhdroid.roulette
interface RotateListener {
fun onRotateStart()
fun onRotateEnd(result: String)
}
- 코드 설명
- onRotateStart() : 애니메이션의 시작을 알립니다. 매개변수가 필요 없습니다.
- onRotateEnd(String) : 애니메이션의 종료를 알립니다. 결과를 전달해주기 때문에 매개변수가 필요합니다.
2. 회전 함수 수정
- 이전 글에서 생성한 룰렛 회전 함수를 수정해서 사용자도 RotateListener을 통해 회전의 시작/종료를 알 수 있도록 코드를 수정합니다.
fun rotateRoulette(toDegrees: Float, duration: Long, rotateListener: RotateListener?) {
val animListener = object : Animation.AnimationListener {
override fun onAnimationRepeat(animation: Animation?) {}
override fun onAnimationStart(animation: Animation?) {
rotateListener?.onRotateStart()
}
override fun onAnimationEnd(animation: Animation?) {
rotateListener?.onRotateEnd("<결과>")
}
}
val rotateAnim = RotateAnimation(
0f, toDegrees,
Animation.RELATIVE_TO_SELF, 0.5f,
Animation.RELATIVE_TO_SELF, 0.5f
)
rotateAnim.duration = duration
rotateAnim.fillAfter = true
rotateAnim.setAnimationListener(animListener)
startAnimation(rotateAnim)
}
- 코드 설명
- animListener
- 애니메이션의 시작, 종료, 재시작을 알 수 있도록 해주는 리스너입니다.
- 이 리스너에서 시작/종료 여부를 받아 RotateListener를 통해 사용자에게도 전달해줍니다.
- rotateAnim
- 이전 글에서 작성한 코드와 동일하고 AnimationListener를 설정해주는 코드만 추가되었습니다.
- animListener
3. 테스트 코드 수정
- MainActivity에서 테스트할 코드를 작성해줍니다.
- 기존 레이아웃에 결과를 보여줄 TextView를 추가해줍니다.(코드 생략)
- MainActivity에 테스트 코드를 추가합니다.
fun rotateRoulette() {
val rotateListener = object: RotateListener {
override fun onRotateStart() {
binding.rotateResultTv.text = "Result : "
}
override fun onRotateEnd(result: String) {
binding.rotateResultTv.text = "Result : $result"
}
}
val toDegrees = (2000..10000).random().toFloat()
binding.roulette.rotateRoulette(toDegrees, 3000, rotateListener)
}
- 코드 설명
- rotateListener
- onRotateStart() : 애니메이션이 시작되면 TextView의 내용을 초기화해줍니다.
- onRotateEnd(String) : 애니메이션이 종료되면 결과를 전달받아 출력해줍니다.
- rotateListener
- 실행 결과를 보면 회전이 종료되고 임의로 설정한 "<결과>"를 출력하는 것을 확인할 수 있다.
회전 결과 계산해서 실제 결과 리턴하기
1. 회전 결과 계산
- 회전 결과 계산식은 구글링 결과 원하는 식을 찾을 수 없어서 직접 삽질하면서 만들었습니다.
- 때문에 계산식이 이상할 수 있지만 결과는 잘 나옵니다(?)
- 룰렛은 동일한 회전 각도로 회전을 하면 룰렛 크기와 상관없이 항상 룰렛의 일정 각도가 12시 방향에 오게 됩니다.
- 제가 생각한 방법은 12시에 오는 각도를 구하고 이 각도에 해당하는 룰렛 칸의 데이터를 리턴해주는 방법입니다.
룰렛이 항상 361도를 회전할 때의 결과 구하는 방법
- 회전 각도를 361도로 설정하면 룰렛 사이즈에 상관없이 항상 269도가 12시 방향에 오게 됨(결과 각도)
- 안드로이드에서 룰렛을 그릴 때는 12시 방향에 270도가 오게 됨(1번 글 참고)
- 360도는 제자리이기 때문에 회전 각도에서 360도를 나눈 후 나머지를 가지고 계산(나머지 각도)
- 361도를 360으로 나눈 후 나머지는 1
- 나머지 각도가 270보다 작으면 (270 - 나머지 각도)로 결과 각도를 구함
- 나머지 각도가 270보다 크면 (360 - 나머지 각도 + 270)으로 결과 각도를 구함
- 361도의 나머지 각도는 1로 270보다 작기 때문에 270 - 1 = 269 결과 각도가 됨
- 룰렛 사이즈에 따라 한 칸의 각도가 달라지게 됨 -> (360 / 룰렛 사이즈) = 룰렛 한 칸의 각도
- 룰렛 사이즈가 4일 때 룰렛 한 칸의 각도는 90도
- 1번 칸은 0~90, 2번 칸은 90~180, 3번 칸은 180~270, 4번 칸은 270~360
- 룰렛 사이즈만큼 for문을 돌려(1~룰렛 사이즈) 결과 각도에 해당하는 칸의 데이터를 리턴해줌
- 결과 각도(269도)가 한 칸의 각도보다 작으면 해당 인덱스의 데이터를 리턴
- 이때 비교하는 각도는 한 칸의 각도 중 큰 값(90, 180, 270, 360)
- for문을 돌리면 순서대로
- 269 < 90
- 269 < 180
- 269 < 270 -> 3번째 칸의 데이터 리턴
- 269 < 180
private fun getRouletteRotateResult(degrees: Float): String {
val moveDegrees = degrees % 360
val resultAngle = if (moveDegrees > 270) 360 - moveDegrees + 270 else 270 - moveDegrees
for (i in 1..rouletteSize) {
if (resultAngle < (360 / rouletteSize) * i) {
if (i - 1 >= rouletteDataList.size) {
return "empty"
}
return rouletteDataList[i - 1]
}
}
return ""
}
- for문을 돌릴 때는 1부터 돌리기 때문에 인덱스를 구할 때는 i - 1을 해줘야 합니다.
- 해당 인덱스보다 룰렛 데이터 리스트 사이즈가 더 크다면 리스트의 해당 인덱스는 비어있다는 뜻으로 "empty"를 리턴해줍니다.
2. 결과 리턴 코드 적용
- 이제 이 결과 리턴 함수를 회전 함수에 적용해서 실제 결과를 리턴하는 코드를 작성해보겠습니다.
fun rotateRoulette(toDegrees: Float, duration: Long, rotateListener: RotateListener?) {
val animListener = object : Animation.AnimationListener {
override fun onAnimationRepeat(animation: Animation?) {}
override fun onAnimationStart(animation: Animation?) {
rotateListener?.onRotateStart()
}
override fun onAnimationEnd(animation: Animation?) {
rotateListener?.onRotateEnd(getRouletteRotateResult(toDegrees))
}
}
val rotateAnim = RotateAnimation(
0f, toDegrees,
Animation.RELATIVE_TO_SELF, 0.5f,
Animation.RELATIVE_TO_SELF, 0.5f
)
rotateAnim.duration = duration
rotateAnim.fillAfter = true
rotateAnim.setAnimationListener(animListener)
startAnimation(rotateAnim)
}
- Listener를 적용한 코드에서 onRotateEnd() 코드 부분에 결과 리턴 함수 적용만 했습니다.
- 회전 각도를 받아 결과 리턴 함수에 전달해서 결과를 계산하고 애니메이션이 종료되면 그 결과를 그대로 리턴해줍니다.
3. 테스트
- 아직 Roulette 클래스에 getter & setter를 만들지 않아(이걸 먼저 작성했어야 했는데...) 일단 Rolette 클래스에서 임의로 데이터를 설정해주고 테스트했습니다.
class Roulette @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
private val strokePaint = Paint()
private val fillPaint = Paint()
private val textPaint = Paint()
private var rouletteSize = 8
private var rouletteDataList = listOf("JhDroid", "Android", "Blog", "IT", "Developer", "Kotlin", "Java", "Happy")
...
}
다음 글은 사용자가 사용하기 편하도록 getter & setter 추가와 typedArray 적용 방법입니다.
* 글에 틀린 부분이 있으면 댓글 부탁드립니다 :D
728x90
'프로젝트 > Roulette Wheel View' 카테고리의 다른 글
[Roulette] 룰렛을 그리고 회전시키기 (4) - 사용 편의성 개선 (0) | 2021.03.13 |
---|---|
[Roulette] 룰렛을 그리고 회전시키기 (2) - 텍스트 쓰기, 애니메이션 적용 (0) | 2021.02.23 |
[Roulette] 룰렛을 그리고 회전시키기 (1) - 룰렛 그리기 (2) | 2021.02.19 |