코루틴에 기반을 둔 방법이 있다.
interface MultipleEventsCutterManager {
fun processEvent(event: () -> Unit)
}
@OptIn(FlowPreview::class)
@Composable
fun <T>multipleEventsCutter(
content: @Composable (MultipleEventsCutterManager) -> T
) : T {
val debounceState = remember {
MutableSharedFlow<() -> Unit>(
replay = 0,
extraBufferCapacity = 1,
onBufferOverflow = BufferOverflow.DROP_OLDEST
)
}
val result = content(
object : MultipleEventsCutterManager {
override fun processEvent(event: () -> Unit) {
debounceState.tryEmit(event)
}
}
)
LaunchedEffect(true) {
debounceState
.debounce(300L)
.collect { onClick ->
onClick.invoke()
}
}
return result
}
이 방식에서는 SharedFlow를 생성한다.
SharedFlow는 생성시 설정을 할 수 있다.
replay를 이용하여 이전에 보낸 값들을 다시 보낼지를 정할 수 있고 onBufferOverflow를 통하여 버퍼가 가득 찰 경우 어떻게 할지 정할 수 있다. DROP_OLDEST를 통하여 스택에 쌓인 오래된 순서대로 버려낼 수 있다.
그리고 debounce를 이용하여 가장 최근에 클릭한 이벤트를 받을 수 있다.
Modifier를 이용하면 다음과 같이 사용한다.
fun Modifier.clickableSingle(
enabled: Boolean = true,
onClickLabel: String? = null,
role: Role? = null,
onClick: () -> Unit
) = composed(
inspectorInfo = debugInspectorInfo {
name = "clickable"
properties["enabled"] = enabled
properties["onClickLabel"] = onClickLabel
properties["role"] = role
properties["onClick"] = onClick
}
) {
multipleEventsCutter { manager ->
Modifier.clickable(
enabled = enabled,
onClickLabel = onClickLabel,
onClick = { manager.processEvent { onClick() } },
role = role,
indication = LocalIndication.current,
interactionSource = remember { MutableInteractionSource() }
)
}
}
두번째 방법은 첫 클릭과 다음 클릭의 텀을 계산하는 방식이다.
internal interface MultipleEventsCutter {
fun processEvent(event: () -> Unit)
companion object
}
internal fun MultipleEventsCutter.Companion.get(): MultipleEventsCutter =
MultipleEventsCutterImpl()
private class MultipleEventsCutterImpl : MultipleEventsCutter {
private val now: Long
get() = System.currentTimeMillis()
private var lastEventTimeMs: Long = 0
override fun processEvent(event: () -> Unit) {
if (now - lastEventTimeMs >= 300L) {
event.invoke()
}
lastEventTimeMs = now
}
}
코루틴 대신 시간 비교에 기반을 두었다. now 와 lastEventTimeMs를 비교하여 역치를 넘을 경우 이벤트를 실행시키는 방식이다.
이것 또한 Modifier에 적용이 가능하다.
fun Modifier.clickableSingle(
enabled: Boolean = true,
onClickLabel: String? = null,
role: Role? = null,
onClick: () -> Unit
) = composed(
inspectorInfo = debugInspectorInfo {
name = "clickable"
properties["enabled"] = enabled
properties["onClickLabel"] = onClickLabel
properties["role"] = role
properties["onClick"] = onClick
}
) {
val multipleEventsCutter = remember { MultipleEventsCutter.get() }
Modifier.clickable(
enabled = enabled,
onClickLabel = onClickLabel,
onClick = { multipleEventsCutter.processEvent { onClick() } },
role = role,
indication = LocalIndication.current,
interactionSource = remember { MutableInteractionSource() }
)
}