레이블이 지정된 범위 슬라이더를 대화형으로 만들기
터치 핸들은 막대를 가로질러 드래그할 수 있어야 하고, 탭할 때 즉시 위치를 지정하고, 상호 작용이 완료되면 가장 가까운 값에 맞춰야 합니다.
이동
제스처 감지기를 사용하는 방법을 살펴보았습니다. 이를 달성하기 위해 detectTapGestures 및 detectDragGestures를 사용할 수 있습니다. 그러나 탭하거나 드래그할 때 핸들을 터치 포인트에 배치할 때 거의 동일한 작업을 수행하기를 원하므로 간략하게 언급한 awaitPointerEventScope를 사용하여 더 유연하고 더 적합한 터치 핸들러를 구현할 수 있습니다.
관심 있는 터치 상태를 봉인된 클래스로 정의할 수 있습니다.
sealed class TouchInteraction {
object NoInteraction : TouchInteraction()
object Up : TouchInteraction()
data class Move(val position: Offset) : TouchInteraction()
}
현재 상호 작용이 없는지, 핸들이 특정 위치로 이동되고 사용자가 손가락을 뗐는지 알면 충분합니다.
우리의 터치 핸들러는 pointerInput Modifier를 사용하여 구현됩니다.
fun Modifier.touchInteraction(key: Any, block: (TouchInteraction) -> Unit): Modifier =
pointerInput(key) {
forEachGesture {
awaitPointerEventScope {
do {
val event: PointerEvent = awaitPointerEvent()
event.changes
.forEach { pointerInputChange: PointerInputChange ->
if (pointerInputChange.positionChange() != Offset.Zero) pointerInputChange.consume()
}
block(TouchInteraction.Move(event.changes.first().position))
} while (event.changes.any { it.pressed })
block(TouchInteraction.Up)
}
}
}
우리는 awaitPointerEventScope로 사용자의 터치 입력을 기다립니다. 입력을 받으면 사용자가 레이블이 지정된 범위 슬라이더와 상호 작용하고 있음을 알 수 있습니다. 사용자의 손가락이 Composable에 있는 한 이벤트를 반복하고 이벤트의 절대 위치를 가져와 TouchInteraction.Move 이벤트로 직접 전달합니다. 사용자가 손가락을 떼는 즉시 TouchInteraction.Up으로 응답하여 UI가 핸들을 가장 가까운 단계로 스냅하여 반응할 수 있는 기회를 제공합니다.
컴포저블에서 Modifier를 캔버스에 추가하고, 현재 상호작용 상태를 추적하기 위한 세 가지 상태 변수를 추가하고, 핸들 위치를 업데이트하는 로직을 추가합니다.
var touchInteractionState by remember { mutableStateOf<TouchInteraction>(TouchInteraction.NoInteraction) }
var moveLeft by remember { mutableStateOf(false) }
var moveRight by remember { mutableStateOf(false) }
...
Canvas(
modifier = modifier
.touchInteraction(remember { MutableInteractionSource() }) {
touchInteractionState = it
}
) {
...
}
when (val touchInteraction = touchInteractionState) {
is TouchInteraction.Move -> {
val touchPositionX = touchInteraction.position.x
if (abs(touchPositionX - leftCirclePosition.x) < abs(touchPositionX - rightCirclePosition.x)) {
leftCirclePosition = calculateNewLeftCirclePosition(touchPositionX, leftCirclePosition, rightCirclePosition, stepSpacing, stepXCoordinates.first())
moveLeft = true
} else {
rightCirclePosition = calculateNewRightCirclePosition(touchPositionX, leftCirclePosition, rightCirclePosition, stepSpacing, stepXCoordinates.last())
moveRight = true
}
}
is TouchInteraction.Up -> {
moveLeft = false
moveRight = false
touchInteractionState = TouchInteraction.NoInteraction
}
else -> {
// nothing to do
}
}
이동할 핸들을 알아야 합니다. 이를 위해 터치 상호 작용의 x 위치를 보고 왼쪽 핸들과 오른쪽 핸들 사이의 거리를 계산하고 상호 작용이 가장 가까운 핸들을 이동합니다. 핸들의 새 위치를 계산할 때 고려해야 할 사항은 핸들이 바를 벗어나지 않아야 하며 이동하는 동안 두 핸들이 겹치지 않아야 한다는 것입니다. 더 명확하게 하기 위해 왼쪽 핸들의 업데이트된 위치 계산을 간단히 살펴보겠습니다.
private fun calculateNewLeftCirclePosition(
touchPositionX: Float,
leftCirclePosition: Offset,
rightCirclePosition: Offset,
stepSpacing: Float,
firstStepXPosition: Float
): Offset = when {
touchPositionX < firstStepXPosition -> leftCirclePosition.copy(x = firstStepXPosition)
touchPositionX > (rightCirclePosition.x - stepSpacing) -> leftCirclePosition
else -> leftCirclePosition.copy(x = touchPositionX)
}
터치 위치, 다른 핸들의 위치, 계단의 간격, 이 경우 첫 번째 계단의 위치에 따라 볼 수 있듯이 왼쪽 핸들이 가질 수 있는 새 위치를 계산합니다.
슬라이더를 터치하는 동안 핸들이 움직이고, 핸들이 즉시 점프하는 위치를 탭할 수 있으며, 손가락을 떼지 않고도 두 핸들 사이를 이동할 수도 있습니다.
그것을 빨리 확인
핸들은 아직 원하는 대로 작동하지 않습니다. 사용자가 손가락을 뗀 후와 제어 핸들이 변경된 후 가장 가까운 단계로 스냅되어야 합니다. 이를 실현하기 위해 우리는 터치 상호 작용 논리를 업데이트하여 가장 가까운 단계와 해당 x 좌표를 찾고 이에 따라 핸들 위치를 업데이트합니다.
is TouchInteraction.Move -> {
val touchPositionX = touchInteraction.position.x
if (abs(touchPositionX - leftCirclePosition.x) < abs(touchPositionX - rightCirclePosition.x)) {
leftCirclePosition = calculateNewLeftCirclePosition(touchPositionX, leftCirclePosition, rightCirclePosition, stepSpacing, stepXCoordinates.first())
moveLeft = true
if (moveRight) {
val (closestRightValue, _) = stepXCoordinates.getClosestNumber(rightCirclePosition.x)
rightCirclePosition = rightCirclePosition.copy(x = closestRightValue)
moveRight = false
}
} else {
rightCirclePosition = calculateNewRightCirclePosition(touchPositionX, leftCirclePosition, rightCirclePosition, stepSpacing, stepXCoordinates.last())
moveRight = true
if (moveLeft) {
val (closestRightValue, _) = stepXCoordinates.getClosestNumber(leftCirclePosition.x)
leftCirclePosition = leftCirclePosition.copy(x = closestRightValue)
moveLeft = false
}
}
}
is TouchInteraction.Up -> {
val (closestLeftValue, closestLeftIndex) = stepXCoordinates.getClosestNumber(leftCirclePosition.x)
val (closestRightValue, closestRightIndex) = stepXCoordinates.getClosestNumber(rightCirclePosition.x)
if (moveLeft) {
leftCirclePosition = leftCirclePosition.copy(x = closestLeftValue)
moveLeft = false
} else if (moveRight) {
rightCirclePosition = rightCirclePosition.copy(x = closestRightValue)
moveRight = false
}
touchInteractionState = TouchInteraction.NoInteraction
}
이제 이미 달성하려는 최종 결과처럼 보입니다 :-). 그러나 한 가지 사소한 세부 사항이 누락되었습니다. 업데이트된 범위를 호출자에게 다시 전달해야 호출자가 응답할 수 있습니다 ;-).
이 마지막 단계는 이제 매우 쉽습니다. Composable에 콜백 onRangeChanged를 매개변수로 추가합니다.
@Composable
fun <T : Number> LabeledRangeSlider(
selectedLowerBound: T,
selectedUpperBound: T,
steps: List<T>,
onRangeChanged: (lower: T, upper: T) -> Unit,
modifier: Modifier = Modifier,
sliderConfig: SliderConfig = SliderConfig()
)
그리고 사용자가 선택한 단계의 값으로 손가락을 뗄 때마다 간단히 호출합니다.
is TouchInteraction.Up -> {
val (closestLeftValue, closestLeftIndex) = stepXCoordinates.getClosestNumber(leftCirclePosition.x)
val (closestRightValue, closestRightIndex) = stepXCoordinates.getClosestNumber(rightCirclePosition.x)
if (moveLeft) {
leftCirclePosition = leftCirclePosition.copy(x = closestLeftValue)
onRangeChanged(steps[closestLeftIndex], steps[closestRightIndex])
moveLeft = false
} else if (moveRight) {
rightCirclePosition = rightCirclePosition.copy(x = closestRightValue)
onRangeChanged(steps[closestLeftIndex], steps[closestRightIndex])
moveRight = false
}
touchInteractionState = TouchInteraction.NoInteraction
}
결론
해냈습니다 🎉. 우리는 레이블이 지정된 범위 슬라이더를 처음부터 새로 만들어 컴포저블에 필요한 모든 것을 직접 그리고 해당 모디파이어 🥳와 상호작용하도록 만들었습니다.
Labeled Range Slider의 전체 소스 코드는 GitHub에서 찾을 수 있습니다.
이 시리즈를 따라하면서 도움이 되는 영감을 얻었기를 바랍니다 :-).
Reference
이 문제에 관하여(레이블이 지정된 범위 슬라이더를 대화형으로 만들기), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/lex_fury/make-the-labeled-range-slider-interactive-5gf텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)