Coverage Summary for Class: RecyclerScrollStateCalculator (kr.open.library.simple_ui.xml.ui.view.recyclerview)
| Class |
Method, %
|
Branch, %
|
Line, %
|
Instruction, %
|
| RecyclerScrollStateCalculator |
100%
(8/8)
|
92.1%
(35/38)
|
100%
(67/67)
|
99.3%
(268/270)
|
| RecyclerScrollStateCalculator$HorizontalEdgeCheckResult |
100%
(1/1)
|
|
100%
(5/5)
|
100%
(22/22)
|
| RecyclerScrollStateCalculator$ScrollDirectionUpdateResult |
100%
(1/1)
|
|
100%
(3/3)
|
100%
(12/12)
|
| RecyclerScrollStateCalculator$VerticalEdgeCheckResult |
100%
(1/1)
|
|
100%
(5/5)
|
100%
(22/22)
|
| Total |
100%
(11/11)
|
92.1%
(35/38)
|
100%
(80/80)
|
99.4%
(324/326)
|
package kr.open.library.simple_ui.xml.ui.view.recyclerview
import kotlin.math.abs
/**
* Pure logic class for calculating RecyclerView scroll states.<br>
* Designed to be unit-testable without Android dependencies.<br>
* Handles scroll state calculation logic for RecyclerScrollStateView.<br><br>
* RecyclerView의 스크롤 상태를 계산하는 순수 로직 클래스입니다.<br>
* 안드로이드 의존성 없이 유닛 테스트 가능하도록 설계되었습니다.<br>
* RecyclerScrollStateView의 스크롤 상태 계산 로직을 담당합니다.<br>
*
* @param edgeReachThreshold Threshold in pixels for edge reach detection.<br><br>
* 가장자리 도달 감지를 위한 픽셀 단위 임계값.<br>
*
* @param scrollDirectionThreshold Threshold in pixels for scroll direction change detection.<br><br>
* 스크롤 방향 변경 감지를 위한 픽셀 단위 임계값.<br>
*/
internal class RecyclerScrollStateCalculator(
private var edgeReachThreshold: Int,
private var scrollDirectionThreshold: Int,
) {
private var accumulatedDx = 0
private var accumulatedDy = 0
private var isAtTop = false
private var isAtBottom = false
private var isAtLeft = false
private var isAtRight = false
private var currentScrollDirection: ScrollDirection = ScrollDirection.IDLE
/**
* Result of vertical edge check.<br><br>
* 수직 엣지 체크 결과.<br>
*
* @property topChanged Whether the top edge state changed.<br><br>
* 상단 가장자리 상태가 변경되었는지 여부.<br>
*
* @property isAtTop Whether currently at the top edge.<br><br>
* 현재 상단 가장자리에 있는지 여부.<br>
*
* @property bottomChanged Whether the bottom edge state changed.<br><br>
* 하단 가장자리 상태가 변경되었는지 여부.<br>
*
* @property isAtBottom Whether currently at the bottom edge.<br><br>
* 현재 하단 가장자리에 있는지 여부.<br>
*/
data class VerticalEdgeCheckResult(
val topChanged: Boolean,
val isAtTop: Boolean,
val bottomChanged: Boolean,
val isAtBottom: Boolean,
)
/**
* Result of horizontal edge check.<br><br>
* 수평 엣지 체크 결과.<br>
*
* @property leftChanged Whether the left edge state changed.<br><br>
* 왼쪽 가장자리 상태가 변경되었는지 여부.<br>
*
* @property isAtLeft Whether currently at the left edge.<br><br>
* 현재 왼쪽 가장자리에 있는지 여부.<br>
*
* @property rightChanged Whether the right edge state changed.<br><br>
* 오른쪽 가장자리 상태가 변경되었는지 여부.<br>
*
* @property isAtRight Whether currently at the right edge.<br><br>
* 현재 오른쪽 가장자리에 있는지 여부.<br>
*/
data class HorizontalEdgeCheckResult(
val leftChanged: Boolean,
val isAtLeft: Boolean,
val rightChanged: Boolean,
val isAtRight: Boolean,
)
/**
* Result of scroll direction update.<br><br>
* 스크롤 방향 업데이트 결과.<br>
*
* @property directionChanged Whether the scroll direction changed.<br><br>
* 스크롤 방향이 변경되었는지 여부.<br>
*
* @property newDirection The new scroll direction.<br><br>
* 새로운 스크롤 방향.<br>
*/
data class ScrollDirectionUpdateResult(
val directionChanged: Boolean,
val newDirection: ScrollDirection,
)
/**
* Checks vertical edges and returns the changes.<br><br>
* 수직 엣지를 체크하고 변경사항을 반환합니다.<br>
*
* @param verticalScrollOffset Current vertical scroll offset.<br><br>
* 현재 수직 스크롤 오프셋.<br>
*
* @param canScrollDown Whether can scroll down.<br><br>
* 아래로 스크롤 가능 여부.<br>
*
* @param verticalScrollExtent Vertical scroll extent.<br><br>
* 수직 스크롤 범위.<br>
*
* @param verticalScrollRange Total vertical scroll range.<br><br>
* 전체 수직 스크롤 범위.<br>
*
* @return The edge change result.<br><br>
* 엣지 변경 결과.<br>
*/
fun checkVerticalEdges(
verticalScrollOffset: Int,
canScrollDown: Boolean,
verticalScrollExtent: Int,
verticalScrollRange: Int,
): VerticalEdgeCheckResult {
val newIsAtTop = verticalScrollOffset <= edgeReachThreshold
val topChanged = newIsAtTop != isAtTop
isAtTop = newIsAtTop
val isBottomReached =
!canScrollDown &&
verticalScrollExtent + verticalScrollOffset + edgeReachThreshold >= verticalScrollRange
val bottomChanged = isBottomReached != isAtBottom
isAtBottom = isBottomReached
return VerticalEdgeCheckResult(
topChanged = topChanged,
isAtTop = newIsAtTop,
bottomChanged = bottomChanged,
isAtBottom = isBottomReached,
)
}
/**
* Checks horizontal edges and returns the changes.<br><br>
* 수평 엣지를 체크하고 변경사항을 반환합니다.<br>
*
* @param horizontalScrollOffset Current horizontal scroll offset.<br><br>
* 현재 수평 스크롤 오프셋.<br>
*
* @param canScrollRight Whether can scroll right.<br><br>
* 오른쪽으로 스크롤 가능 여부.<br>
*
* @param horizontalScrollExtent Horizontal scroll extent.<br><br>
* 수평 스크롤 범위.<br>
*
* @param horizontalScrollRange Total horizontal scroll range.<br><br>
* 전체 수평 스크롤 범위.<br>
*
* @return The edge change result.<br><br>
* 엣지 변경 결과.<br>
*/
fun checkHorizontalEdges(
horizontalScrollOffset: Int,
canScrollRight: Boolean,
horizontalScrollExtent: Int,
horizontalScrollRange: Int,
): HorizontalEdgeCheckResult {
val newIsAtLeft = horizontalScrollOffset <= edgeReachThreshold
val leftChanged = newIsAtLeft != isAtLeft
isAtLeft = newIsAtLeft
val isRightReached =
!canScrollRight &&
horizontalScrollExtent + horizontalScrollOffset + edgeReachThreshold >= horizontalScrollRange
val rightChanged = isRightReached != isAtRight
isAtRight = isRightReached
return HorizontalEdgeCheckResult(
leftChanged = leftChanged,
isAtLeft = newIsAtLeft,
rightChanged = rightChanged,
isAtRight = isRightReached,
)
}
/**
* Updates vertical scroll direction and returns the changes.<br><br>
* 수직 스크롤 방향을 업데이트하고 변경사항을 반환합니다.<br>
*
* @param dy Vertical scroll delta value (positive: down, negative: up).<br><br>
* 수직 스크롤 델타값 (양수: 아래로, 음수: 위로).<br>
*
* @return The scroll direction update result.<br><br>
* 스크롤 방향 업데이트 결과.<br>
*/
fun updateVerticalScrollDirection(dy: Int): ScrollDirectionUpdateResult {
accumulatedDy += dy
if (abs(accumulatedDy) >= scrollDirectionThreshold) {
val newDirection = if (accumulatedDy > 0) ScrollDirection.DOWN else ScrollDirection.UP
val directionChanged = newDirection != currentScrollDirection
if (directionChanged) {
currentScrollDirection = newDirection
}
accumulatedDy = 0
return ScrollDirectionUpdateResult(
directionChanged = directionChanged,
newDirection = newDirection,
)
}
return ScrollDirectionUpdateResult(
directionChanged = false,
newDirection = currentScrollDirection,
)
}
/**
* Updates horizontal scroll direction and returns the changes.<br><br>
* 수평 스크롤 방향을 업데이트하고 변경사항을 반환합니다.<br>
*
* @param dx Horizontal scroll delta value (positive: right, negative: left).<br><br>
* 수평 스크롤 델타값 (양수: 오른쪽, 음수: 왼쪽).<br>
*
* @return The scroll direction update result.<br><br>
* 스크롤 방향 업데이트 결과.<br>
*/
fun updateHorizontalScrollDirection(dx: Int): ScrollDirectionUpdateResult {
accumulatedDx += dx
if (abs(accumulatedDx) >= scrollDirectionThreshold) {
val newDirection = if (accumulatedDx > 0) ScrollDirection.RIGHT else ScrollDirection.LEFT
val directionChanged = newDirection != currentScrollDirection
if (directionChanged) {
currentScrollDirection = newDirection
}
accumulatedDx = 0
return ScrollDirectionUpdateResult(
directionChanged = directionChanged,
newDirection = newDirection,
)
}
return ScrollDirectionUpdateResult(
directionChanged = false,
newDirection = currentScrollDirection,
)
}
/**
* Called when scroll becomes IDLE state.<br>
* Resets accumulated scroll values and changes direction to IDLE.<br><br>
* 스크롤이 IDLE 상태가 되었을 때 호출됩니다.<br>
* 축적된 스크롤 값을 초기화하고 방향을 IDLE로 변경합니다.<br>
*
* @return The scroll direction update result.<br><br>
* 스크롤 방향 업데이트 결과.<br>
*/
fun resetScrollAccumulation(): ScrollDirectionUpdateResult {
accumulatedDx = 0
accumulatedDy = 0
val directionChanged = currentScrollDirection != ScrollDirection.IDLE
if (directionChanged) {
currentScrollDirection = ScrollDirection.IDLE
}
return ScrollDirectionUpdateResult(
directionChanged = directionChanged,
newDirection = ScrollDirection.IDLE,
)
}
/**
* Updates threshold values.<br><br>
* 임계값을 업데이트합니다.<br>
*
* @param edgeReachThreshold Edge reach threshold (null to keep unchanged).<br><br>
* 엣지 도달 임계값 (null이면 변경하지 않음).<br>
*
* @param scrollDirectionThreshold Scroll direction threshold (null to keep unchanged).<br><br>
* 스크롤 방향 임계값 (null이면 변경하지 않음).<br>
*/
fun updateThresholds(edgeReachThreshold: Int? = null, scrollDirectionThreshold: Int? = null) {
edgeReachThreshold?.let { this.edgeReachThreshold = it }
scrollDirectionThreshold?.let { this.scrollDirectionThreshold = it }
}
}