-
Notifications
You must be signed in to change notification settings - Fork 0
폭죽 애니메이션
HyeonSeongKang edited this page Aug 29, 2023
·
5 revisions
안녕하세요. 안드로이드 팀의 강현성
입니다.
이번 프로젝트에서 폭죽 애니메이션 구현했는데 어떻게 구현했는지에 대해서 공유하려 합니다.
폭죽 애니메이션은 여러 개의 사각형들이 화면 상단의 랜덤한 위치에서 폭발시키는 효과를 구현합니다. 각 사각형은 랜덤한 방향과 속도로 움직이며, 화면 바깥으로 나가게 되면 삭제됩니다.
repeat(numberOfExplosions) {
// 화면 상단의 랜덤 위치에서 폭죽 시작
val explosionCenterX = Random.nextInt(frameLayout.width).toFloat()
val explosionCenterY = Random.nextInt(frameLayout.height / 3).toFloat()
val particles = List(numberOfParticles) {
val width = Random.nextInt(20) + 10
val height = Random.nextInt(20) + 8
val color = Color.parseColor(colors[Random.nextInt(colors.size)])
val particle = View(frameLayout.context).apply {
setBackgroundColor(color)
layoutParams = FrameLayout.LayoutParams(width, height)
x = explosionCenterX - width / 2f
y = explosionCenterY - height / 2f
alpha = 0.8f
}
frameLayout.addView(particle)
particle
}
}
x속도 = con(각도)
y속도 = sin(각도)
val angle = Math.toRadians(Random.nextInt(360).toDouble())
val speed = Random.nextInt(10) + 5 // 5~15 사이의 속도
val xVelocity = cos(angle) * speed
val yVelocity = sin(angle) * speed
- 3.1. 파티클 움직임 설정 particles 리스트의 모든 값을 반복합니다. 각 파티클 대해서, 이전 단계에서 계산한 각도와 속도를 사용하여 움직임을 결정합니다.
particles.forEach { particle ->
// ... [각도 및 속도 계산 코드]
- 3.2. 애니메이터 설정
- ofFloat(0f, 1f)는 애니메이션의 시작과 끝 값을 나타냅니다. 여기서는 0에서 1까지의 값이지만, 실제 움직임은 이 값들에 의해 직접적으로 제어되지는 않습니다.
- duration은 애니메이션의 지속 시간을 설정하며, 여기서는 무작위로 2초에서 5초 사이의 값이 설정됩니다.
- LinearInterpolator는 애니메이션의 속도가 꾸준히 유지되게 합니다.
val animator = ValueAnimator.ofFloat(0f, 1f).apply {
duration = (Random.nextInt(3001) + 2000).toLong() // 2~5초
interpolator = LinearInterpolator()
- 3.3. 움직임 업데이트
addUpdateListener {
particle.x += xVelocity.toFloat()
particle.y += yVelocity.toFloat()
// 화면 밖으로 나가면 삭제
if (particle.x < 0 || particle.x > frameLayout.width || particle.y < 0 || particle.y > frameLayout.height) {
frameLayout.removeView(particle)
this.cancel()
}
}
- 3.4. 애니메이션 종료 처리
animator.addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
frameLayout.removeView(particle)
}
})
- 3.5. 애니메이션 시작
animator.start()
- 성능 저하: 너무 많은 파티클을 동시에 처리하려고 할 때, 뷰의 생성과 제거에 따른 오버헤드가 커져 앱의 전반적인 성능에 영향을 미칠 수 있습니다.
- 메모리 사용 증가: 각 파티클은 개별 뷰로써 메모리를 소비합니다. 따라서 너무 많은 파티클을 생성하면 앱의 메모리 사용량이 급격히 증가할 수 있습니다.
- UI 스레드 부하: 파티클 애니메이션은 UI 스레드에서 실행됩니다. 너무 많은 파티클이 동시에 애니메이션 되면 UI 스레드에 부하가 걸리게 되며, 이는 애니메이션의 부드러움을 해칠 수 있습니다.
- 파티클 수 줄이기: 필요한 최소한의 파티클만 사용합니다. 사실 이게 가장 간단한 방법입니다.
- 뷰 풀 사용: 뷰 풀(View Pool)을 사용하여 이미 생성된 파티클 뷰를 재사용합니다. 파티클이 화면 밖으로 나가거나 애니메이션이 종료되면 뷰 풀로 반환하고, 새로운 파티클이 필요할 때 다시 가져와 사용합니다.(구현해보고 있습니다.)
private val activeAnimations = mutableListOf<Animator>()
fun explodeView(frameLayout: FrameLayout, numberOfParticles: Int = 150, numberOfExplosions: Int = 5) {
activeAnimations.forEach { it.cancel() }
activeAnimations.clear()
frameLayout.removeAllViews()
val colors = listOf("#FF7676", "#FD3F33", "#FFB876", "#FFB801", "#76ADFF", "#357FED")
repeat(numberOfExplosions) {
// 화면 상단의 랜덤 위치에서 폭죽 시작
val explosionCenterX = Random.nextInt(frameLayout.width).toFloat()
val explosionCenterY = Random.nextInt(frameLayout.height / 3).toFloat()
val particles = List(numberOfParticles) {
val width = Random.nextInt(20) + 10
val height = Random.nextInt(20) + 8
val color = Color.parseColor(colors[Random.nextInt(colors.size)])
val particle = View(frameLayout.context).apply {
setBackgroundColor(color)
layoutParams = FrameLayout.LayoutParams(width, height)
x = explosionCenterX - width / 2f
y = explosionCenterY - height / 2f
alpha = 0.8f
}
frameLayout.addView(particle)
particle
}
particles.forEach { particle ->
val angle = Math.toRadians(Random.nextInt(360).toDouble())
val speed = Random.nextInt(10) + 5 // 5~15 사이의 속도
val xVelocity = cos(angle) * speed
val yVelocity = sin(angle) * speed
val animator = ValueAnimator.ofFloat(0f, 1f).apply {
duration = (Random.nextInt(3001) + 2000).toLong() // 2~5초
interpolator = LinearInterpolator()
addUpdateListener {
particle.x += xVelocity.toFloat()
particle.y += yVelocity.toFloat()
// 화면 밖으로 나가면 삭제
if (particle.x < 0 || particle.x > frameLayout.width || particle.y < 0 || particle.y > frameLayout.height) {
frameLayout.removeView(particle)
this.cancel()
}
}
}
animator.addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
frameLayout.removeView(particle)
}
})
animator.start()
}
}
}
- FE - 나도 오픈소스 개발자? (NPM 배포기)
- FE - 합성 컴포넌트에 스토리북 한 스푼 🥄
- FE - Tailwind CSS 찐하게 사용해보기
- AOS - 안드로이드 네트워크 연결
- AOS - API 요청에 따른 동적 탭 생성
- AOS - 나도 오픈소스 개발자? (jitpack 배포기)
- AOS - 폭죽 애니메이션
- AOS - 가이드 모드 애니메이션
- AOS - 뷰모델과 애니메이션을 같이 사용했을때의 ISSUE
- BE - 무중단 배포에 대해 알아보자!
- BE - 더 무중단스러운 배포를 위한 graceful shutdown
- BE - 쿼리 최적화에 대해 알아보자!
- BE - 실전, 쿼리 가속도 업!