diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/MouseWheelScrollable.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/MouseWheelScrollable.kt index dea222c3d5cc8..f33bff298eee1 100644 --- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/MouseWheelScrollable.kt +++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/MouseWheelScrollable.kt @@ -33,6 +33,7 @@ import androidx.compose.ui.util.fastAny import androidx.compose.ui.util.fastForEach import kotlin.math.abs import kotlin.math.roundToInt +import kotlinx.coroutines.CancellationException import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.isActive import kotlinx.coroutines.launch @@ -146,11 +147,14 @@ private class AnimatedMouseWheelScrollNode( while (isActive) { val event = channel.receive() isAnimationRunning = true - scrollingLogic.animatedDispatchScroll(event, speed = 1f * density) { - // Sum delta from all pending events to avoid multiple animation restarts. - channel.sumOrNull() + try { + scrollingLogic.animatedDispatchScroll(event, speed = 1f * density) { + // Sum delta from all pending events to avoid multiple animation restarts. + channel.sumOrNull() + } + } finally { + isAnimationRunning = false } - isAnimationRunning = false } } } @@ -229,38 +233,42 @@ private class AnimatedMouseWheelScrollNode( if (target.isLowScrollingDelta()) { return } - scrollableState.scroll { - var requiredAnimation = true - var lastValue = 0f - val anim = AnimationState(0f) - while (requiredAnimation) { - requiredAnimation = false - val durationMillis = (abs(target - anim.value) / speed) - .roundToInt() - .coerceAtMost(maxDurationMillis) - anim.animateTo( - target, - animationSpec = tween( - durationMillis = durationMillis, - easing = LinearEasing - ), - sequentialAnimation = true - ) { - val delta = value - lastValue - if (!delta.isLowScrollingDelta()) { - val consumedDelta = scrollBy(delta) - if (!(delta - consumedDelta).isLowScrollingDelta()) { + var requiredAnimation = true + var lastValue = 0f + val anim = AnimationState(0f) + while (requiredAnimation) { + requiredAnimation = false + val durationMillis = (abs(target - anim.value) / speed) + .roundToInt() + .coerceAtMost(maxDurationMillis) + try { + scrollableState.scroll { + anim.animateTo( + target, + animationSpec = tween( + durationMillis = durationMillis, + easing = LinearEasing + ), + sequentialAnimation = true + ) { + val delta = value - lastValue + if (!delta.isLowScrollingDelta()) { + val consumedDelta = scrollBy(delta) + if (!(delta - consumedDelta).isLowScrollingDelta()) { + cancelAnimation() + return@animateTo + } + lastValue += delta + } + tryReceiveNext()?.let { + target += it + requiredAnimation = !(target - lastValue).isLowScrollingDelta() cancelAnimation() - return@animateTo } - lastValue += delta - } - tryReceiveNext()?.let { - target += it - requiredAnimation = !(target - lastValue).isLowScrollingDelta() - cancelAnimation() } } + } catch (ignore: CancellationException) { + requiredAnimation = true } } } diff --git a/compose/mpp/demo/src/commonMain/kotlin/androidx/compose/mpp/demo/LazyLayouts.kt b/compose/mpp/demo/src/commonMain/kotlin/androidx/compose/mpp/demo/LazyLayouts.kt index 86a40d537a868..ebf236b26e7c9 100644 --- a/compose/mpp/demo/src/commonMain/kotlin/androidx/compose/mpp/demo/LazyLayouts.kt +++ b/compose/mpp/demo/src/commonMain/kotlin/androidx/compose/mpp/demo/LazyLayouts.kt @@ -37,6 +37,7 @@ import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp import kotlin.random.Random +import kotlinx.coroutines.CancellationException val LazyLayouts = Screen.Selection( "LazyLayouts", @@ -52,7 +53,11 @@ private fun ExampleLazyColumn() { LaunchedEffect(Unit) { while (true) { withFrameMillis { } - state.scrollBy(2f) + try { + state.scrollBy(2f) + } catch (ignore: CancellationException) { + // Ignore cancelling by manual input + } } } LazyColumn(Modifier.fillMaxSize(), state = state) {