Coverage Summary for Class: SlideDirection (kr.open.library.simple_ui.xml.extensions.view)

Class Class, % Method, % Branch, % Line, % Instruction, %
SlideDirection 100% (1/1) 100% (1/1) 100% (4/4) 100% (30/30)


 /**
  * View animation extension functions for various animation effects.<br>
  * Provides convenient methods for scale, fade, slide, rotate, pulse, and shake animations.<br><br>
  * 다양한 애니메이션 효과를 위한 View 애니메이션 확장 함수입니다.<br>
  * 스케일, 페이드, 슬라이드, 회전, 펄스 및 흔들기 애니메이션을 위한 편리한 메서드를 제공합니다.<br>
  *
  * Example usage:<br>
  * ```kotlin
  * // Scale animation
  * button.animateScale(toScale = 1.2f, duration = 150L)
  *
  * // Pulse effect
  * heartIcon.pulse(minScale = 0.9f, maxScale = 1.1f)
  * heartIcon.stopPulse()
  *
  * // Slide animations
  * panel.slideIn(SlideDirection.RIGHT, duration = 250L)
  * panel.slideOut(SlideDirection.LEFT, hideOnComplete = true)
  *
  * // Shake animation
  * errorField.shake(intensity = 15f)
  *
  * // Rotate animation
  * arrowIcon.rotate(toDegrees = 180f)
  *
  * // Fade animations
  * imageView.fadeIn(500L)
  * progressBar.fadeOut(200L, hideOnComplete = true)
  * menuView.fadeToggle(400L)
  * ```
  */
 package kr.open.library.simple_ui.xml.extensions.view
 
 import android.animation.Animator
 import android.animation.AnimatorListenerAdapter
 import android.animation.ValueAnimator
 import android.view.View
 import android.view.animation.AccelerateDecelerateInterpolator
 import android.view.animation.LinearInterpolator
 import androidx.core.view.isVisible
 
 /**
  * Animates the view's scale with customizable parameters.<br><br>
  * 커스터마이징 가능한 매개변수로 View의 스케일을 애니메이션합니다.<br>
  *
  * @param fromScale Starting scale value (default: current scale).<br><br>
  *                  시작 스케일 값 (기본값: 현재 스케일).<br>
  *
  * @param toScale Target scale value.<br><br>
  *                대상 스케일 값.<br>
  *
  * @param duration Animation duration in milliseconds (default: 300ms).<br><br>
  *                 애니메이션 지속 시간(밀리초) (기본값: 300ms).<br>
  *
  * @param onComplete Optional callback when animation completes.<br><br>
  *                   애니메이션 완료 시 실행할 선택적 콜백.<br>
  */
 public fun View.animateScale(
     fromScale: Float = scaleX,
     toScale: Float,
     duration: Long = 300L,
     onComplete: (() -> Unit)? = null,
 ) {
     scaleX = fromScale
     scaleY = fromScale
 
     animate()
         .scaleX(toScale)
         .scaleY(toScale)
         .setDuration(duration)
         .setInterpolator(AccelerateDecelerateInterpolator())
         .setListener(
             object : AnimatorListenerAdapter() {
                 override fun onAnimationEnd(animation: Animator) {
                     onComplete?.invoke()
                 }
             },
         ).start()
 }
 
 /**
  * Creates a pulsing animation effect.<br><br>
  * 펄스 애니메이션 효과를 생성합니다.<br>
  *
  * @param minScale Minimum scale value (default: 0.95f).<br><br>
  *                 최소 스케일 값 (기본값: 0.95f).<br>
  *
  * @param maxScale Maximum scale value (default: 1.05f).<br><br>
  *                 최대 스케일 값 (기본값: 1.05f).<br>
  *
  * @param duration Duration for one complete pulse cycle in milliseconds (default: 1000ms).<br><br>
  *                 한 번의 완전한 펄스 주기 지속 시간(밀리초) (기본값: 1000ms).<br>
  *
  * @param repeatCount Number of times to repeat (-1 for infinite, default: -1).<br><br>
  *                    반복 횟수 (무한 반복은 -1, 기본값: -1).<br>
  */
 public fun View.pulse(
     minScale: Float = 0.95f,
     maxScale: Float = 1.05f,
     duration: Long = 1000L,
     repeatCount: Int = ValueAnimator.INFINITE,
 ) {
     (getTag(ViewIds.PULSE_ANIMATOR) as? ValueAnimator)?.cancel()
 
     val animator = ValueAnimator.ofFloat(minScale, maxScale, minScale)
     animator.duration = duration
     animator.repeatCount = repeatCount
     animator.interpolator = AccelerateDecelerateInterpolator()
 
     animator.addUpdateListener { animation ->
         val scale = animation.animatedValue as Float
         scaleX = scale
         scaleY = scale
     }
 
     setTag(ViewIds.PULSE_ANIMATOR, animator)
     animator.start()
 }
 
 /**
  * Stops any pulsing animation on this view.<br><br>
  * 이 View의 펄스 애니메이션을 중지합니다.<br>
  */
 public fun View.stopPulse() {
     (getTag(ViewIds.PULSE_ANIMATOR) as? ValueAnimator)?.let { animator ->
         animator.cancel()
         setTag(ViewIds.PULSE_ANIMATOR, null)
         scaleX = 1f
         scaleY = 1f
     }
 }
 
 /**
  * Animates view sliding in from a specific direction.<br><br>
  * 특정 방향에서 View가 슬라이드 인되는 애니메이션을 실행합니다.<br>
  *
  * @param direction Direction to slide from (LEFT, RIGHT, TOP, BOTTOM).<br><br>
  *                  슬라이드할 방향 (LEFT, RIGHT, TOP, BOTTOM).<br>
  *
  * @param distance Distance to slide in pixels (default: view width/height).<br><br>
  *               슬라이드 거리(픽셀) (기본값: view의 너비/높이).<br>
  *
  * @param duration Animation duration in milliseconds (default: 300ms).<br><br>
  *                 애니메이션 지속 시간(밀리초) (기본값: 300ms).<br>
  *
  * @param onComplete Optional callback when animation completes.<br><br>
  *                   애니메이션 완료 시 실행할 선택적 콜백.<br>
  */
 public fun View.slideIn(
     direction: SlideDirection,
     distance: Float = 0f,
     duration: Long = 300L,
     onComplete: (() -> Unit)? = null,
 ) {
     val actualDistance = resolveSlideDistance(direction, distance)
 
     val (startX, startY) =
         when (direction) {
             SlideDirection.LEFT -> -actualDistance to 0f
             SlideDirection.RIGHT -> actualDistance to 0f
             SlideDirection.TOP -> 0f to -actualDistance
             SlideDirection.BOTTOM -> 0f to actualDistance
         }
 
     translationX = startX
     translationY = startY
     isVisible = true
 
     animate()
         .translationX(0f)
         .translationY(0f)
         .setDuration(duration)
         .setInterpolator(AccelerateDecelerateInterpolator())
         .setListener(
             object : AnimatorListenerAdapter() {
                 override fun onAnimationEnd(animation: Animator) {
                     onComplete?.invoke()
                 }
             },
         ).start()
 }
 
 /**
  * Animates view sliding out to a specific direction.<br><br>
  * 특정 방향으로 View가 슬라이드 아웃되는 애니메이션을 실행합니다.<br>
  *
  * @param direction Direction to slide to (LEFT, RIGHT, TOP, BOTTOM).<br><br>
  *                  슬라이드할 방향 (LEFT, RIGHT, TOP, BOTTOM).<br>
  *
  * @param distance Distance to slide in pixels (default: view width/height).<br><br>
  *               슬라이드 거리(픽셀) (기본값: view의 너비/높이).<br>
  *
  * @param duration Animation duration in milliseconds (default: 300ms).<br><br>
  *                 애니메이션 지속 시간(밀리초) (기본값: 300ms).<br>
  *
  * @param hideOnComplete Whether to set visibility to GONE after animation (default: true).<br><br>
  *                       애니메이션 후 가시성을 GONE으로 설정할지 여부 (기본값: true).<br>
  *
  * @param onComplete Optional callback when animation completes.<br><br>
  *                   애니메이션 완료 시 실행할 선택적 콜백.<br>
  */
 public fun View.slideOut(
     direction: SlideDirection,
     distance: Float = 0f,
     duration: Long = 300L,
     hideOnComplete: Boolean = true,
     onComplete: (() -> Unit)? = null,
 ) {
     val actualDistance = resolveSlideDistance(direction, distance)
 
     val (endX, endY) =
         when (direction) {
             SlideDirection.LEFT -> -actualDistance to 0f
             SlideDirection.RIGHT -> actualDistance to 0f
             SlideDirection.TOP -> 0f to -actualDistance
             SlideDirection.BOTTOM -> 0f to actualDistance
         }
 
     animate()
         .translationX(endX)
         .translationY(endY)
         .setDuration(duration)
         .setInterpolator(AccelerateDecelerateInterpolator())
         .setListener(
             object : AnimatorListenerAdapter() {
                 override fun onAnimationEnd(animation: Animator) {
                     if (hideOnComplete) {
                         visibility = View.GONE
                     }
                     onComplete?.invoke()
                 }
             },
         ).start()
 }
 
 /**
  * Creates a shake animation effect.<br><br>
  * 흔들기 애니메이션 효과를 생성합니다.<br>
  *
  * @param intensity Shake intensity in pixels (default: 10f).<br><br>
  *                  흔들기 강도(픽셀) (기본값: 10f).<br>
  *
  * @param duration Duration of the shake animation in milliseconds (default: 500ms).<br><br>
  *                 흔들기 애니메이션 지속 시간(밀리초) (기본값: 500ms).<br>
  *
  * @param onComplete Optional callback when animation completes.<br><br>
  *                   애니메이션 완료 시 실행할 선택적 콜백.<br>
  */
 public fun View.shake(
     intensity: Float = 10f,
     duration: Long = 500L,
     onComplete: (() -> Unit)? = null,
 ) {
     val originalX = translationX
 
     animate()
         .translationX(originalX + intensity)
         .setDuration(duration / 10)
         .setInterpolator(LinearInterpolator())
         .setListener(
             object : AnimatorListenerAdapter() {
                 private var shakeCount = 0
                 private val maxShakes = 10
 
                 override fun onAnimationEnd(animation: Animator) {
                     shakeCount++
                     if (shakeCount < maxShakes) {
                         val direction = if (shakeCount % 2 == 0) 1 else -1
                         val currentIntensity = intensity * (1f - shakeCount.toFloat() / maxShakes)
                         animate()
                             .translationX(originalX + (currentIntensity * direction))
                             .setDuration(duration / 10)
                             .setListener(this)
                             .start()
                     } else {
                         animate()
                             .translationX(originalX)
                             .setDuration(duration / 20)
                             .setListener(
                                 object : AnimatorListenerAdapter() {
                                     override fun onAnimationEnd(animation: Animator) {
                                         onComplete?.invoke()
                                     }
                                 },
                             ).start()
                     }
                 }
             },
         ).start()
 }
 
 /**
  * Enum representing slide directions for slide animations.<br><br>
  * 슬라이드 애니메이션의 방향을 나타내는 열거형입니다.<br>
  */
 public enum class SlideDirection {
     LEFT,
     RIGHT,
     TOP,
     BOTTOM,
 }
 
 /**
  * Creates a rotate animation.<br><br>
  * 회전 애니메이션을 생성합니다.<br>
  *
  * @param fromDegrees Starting rotation in degrees (default: current rotation).<br><br>
  *                    시작 회전 각도(도) (기본값: 현재 회전).<br>
  *
  * @param toDegrees Target rotation in degrees.<br><br>
  *                  대상 회전 각도(도).<br>
  *
  * @param duration Animation duration in milliseconds (default: 300ms).<br><br>
  *                 애니메이션 지속 시간(밀리초) (기본값: 300ms).<br>
  *
  * @param onComplete Optional callback when animation completes.<br><br>
  *                   애니메이션 완료 시 실행할 선택적 콜백.<br>
  */
 public fun View.rotate(
     fromDegrees: Float = rotation,
     toDegrees: Float,
     duration: Long = 300L,
     onComplete: (() -> Unit)? = null,
 ) {
     rotation = fromDegrees
 
     animate()
         .rotation(toDegrees)
         .setDuration(duration)
         .setInterpolator(AccelerateDecelerateInterpolator())
         .setListener(
             object : AnimatorListenerAdapter() {
                 override fun onAnimationEnd(animation: Animator) {
                     onComplete?.invoke()
                 }
             },
         ).start()
 }
 
 /**
  * Fades in the view with animation.<br><br>
  * 애니메이션과 함께 View를 페이드 인합니다.<br>
  *
  * @param duration Animation duration in milliseconds (default: 300ms).<br><br>
  *                 애니메이션 지속 시간(밀리초) (기본값: 300ms).<br>
  *
  * @param onComplete Optional callback when animation completes.<br><br>
  *                   애니메이션 완료 시 실행할 선택적 콜백.<br>
  */
 public fun View.fadeIn(
     duration: Long = 300L,
     onComplete: (() -> Unit)? = null,
 ) {
     if (alpha == 1f && isVisible) {
         onComplete?.invoke()
         return
     }
 
     alpha = 0f
     isVisible = true
     animate()
         .alpha(1f)
         .setDuration(duration)
         .setListener(
             object : AnimatorListenerAdapter() {
                 override fun onAnimationEnd(animation: Animator) {
                     onComplete?.invoke()
                 }
             },
         ).start()
 }
 
 /**
  * Fades out the view with animation.<br><br>
  * 애니메이션과 함께 View를 페이드 아웃합니다.<br>
  *
  * @param duration Animation duration in milliseconds (default: 300ms).<br><br>
  *                 애니메이션 지속 시간(밀리초) (기본값: 300ms).<br>
  *
  * @param hideOnComplete Whether to set visibility to GONE after animation (default: true).<br><br>
  *                       애니메이션 후 가시성을 GONE으로 설정할지 여부 (기본값: true).<br>
  *
  * @param onComplete Optional callback when animation completes.<br><br>
  *                   애니메이션 완료 시 실행할 선택적 콜백.<br>
  */
 public fun View.fadeOut(
     duration: Long = 300L,
     hideOnComplete: Boolean = true,
     onComplete: (() -> Unit)? = null,
 ) {
     if (alpha == 0f || !isVisible) {
         onComplete?.invoke()
     } else {
         animate()
             .alpha(0f)
             .setDuration(duration)
             .setListener(
                 object : AnimatorListenerAdapter() {
                     override fun onAnimationEnd(animation: Animator) {
                         if (hideOnComplete) {
                             visibility = View.GONE
                         }
                         onComplete?.invoke()
                     }
                 },
             ).start()
     }
 }
 
 /**
  * Toggles visibility with fade animation.<br><br>
  * 페이드 애니메이션과 함께 가시성을 토글합니다.<br>
  *
  * @param duration Animation duration in milliseconds (default: 300ms).<br><br>
  *                 애니메이션 지속 시간(밀리초) (기본값: 300ms).<br>
  *
  * @param onComplete Optional callback when animation completes.<br><br>
  *                   애니메이션 완료 시 실행할 선택적 콜백.<br>
  */
 public fun View.fadeToggle(
     duration: Long = 300L,
     onComplete: (() -> Unit)? = null,
 ) {
     if (isVisible && alpha > 0f) {
         fadeOut(duration, true, onComplete)
     } else {
         fadeIn(duration, onComplete)
     }
 }
 
 private fun View.resolveSlideDistance(
     direction: SlideDirection,
     requestedDistance: Float,
 ): Float {
     if (requestedDistance != 0f) return requestedDistance
 
     val layoutDistance =
         when (direction) {
             SlideDirection.LEFT, SlideDirection.RIGHT -> width
             SlideDirection.TOP, SlideDirection.BOTTOM -> height
         }
     if (layoutDistance > 0) return layoutDistance.toFloat()
 
     val measuredDistance =
         when (direction) {
             SlideDirection.LEFT, SlideDirection.RIGHT -> measuredWidth
             SlideDirection.TOP, SlideDirection.BOTTOM -> measuredHeight
         }
     if (measuredDistance > 0) return measuredDistance.toFloat()
 
     val density = resources.displayMetrics.density
     return if (density > 0f) 56f * density else 56f
 }