overscroll
Functions summary
Modifier |
Modifier.overscroll(overscrollEffect: OverscrollEffect?)Renders overscroll from the provided |
Cmn
|
Functions
Modifier.overscroll
fun Modifier.overscroll(overscrollEffect: OverscrollEffect?): Modifier
Renders overscroll from the provided overscrollEffect.
This modifier attaches the provided overscrollEffect's OverscrollEffect.node to the hierarchy, which renders the actual effect. Note that this modifier is only responsible for the visual part of overscroll - on its own it will not handle input events. In addition to using this modifier you also need to propagate events to the overscrollEffect, most commonly by using a androidx.compose.foundation.gestures.scrollable.
Alternatively, you can use a higher level API such as verticalScroll or androidx.compose.foundation.lazy.LazyColumn and provide a custom OverscrollEffect - these components will both render and provide events to the OverscrollEffect, so you do not need to manually render the effect with this modifier.
import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.spring import androidx.compose.foundation.OverscrollEffect import androidx.compose.foundation.background import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.gestures.rememberScrollableState import androidx.compose.foundation.gestures.scrollable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.size import androidx.compose.foundation.overscroll import androidx.compose.material3.Text import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.nestedscroll.NestedScrollSource import androidx.compose.ui.layout.Measurable import androidx.compose.ui.layout.MeasureResult import androidx.compose.ui.layout.MeasureScope import androidx.compose.ui.node.DelegatableNode import androidx.compose.ui.node.LayoutModifierNode import androidx.compose.ui.text.TextStyle import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.Velocity import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp // our custom offset overscroll that offset the element it is applied to when we hit the bound // on the scrollable container. class OffsetOverscrollEffect(val scope: CoroutineScope) : OverscrollEffect { private val overscrollOffset = Animatable(0f) override fun applyToScroll( delta: Offset, source: NestedScrollSource, performScroll: (Offset) -> Offset, ): Offset { // in pre scroll we relax the overscroll if needed // relaxation: when we are in progress of the overscroll and user scrolls in the // different direction = substract the overscroll first val sameDirection = sign(delta.y) == sign(overscrollOffset.value) val consumedByPreScroll = if (abs(overscrollOffset.value) > 0.5 && !sameDirection) { val prevOverscrollValue = overscrollOffset.value val newOverscrollValue = overscrollOffset.value + delta.y if (sign(prevOverscrollValue) != sign(newOverscrollValue)) { // sign changed, coerce to start scrolling and exit scope.launch { overscrollOffset.snapTo(0f) } Offset(x = 0f, y = delta.y + prevOverscrollValue) } else { scope.launch { overscrollOffset.snapTo(overscrollOffset.value + delta.y) } delta.copy(x = 0f) } } else { Offset.Zero } val leftForScroll = delta - consumedByPreScroll val consumedByScroll = performScroll(leftForScroll) val overscrollDelta = leftForScroll - consumedByScroll // if it is a drag, not a fling, add the delta left to our over scroll value if (abs(overscrollDelta.y) > 0.5 && source == NestedScrollSource.UserInput) { scope.launch { // multiply by 0.1 for the sake of parallax effect overscrollOffset.snapTo(overscrollOffset.value + overscrollDelta.y * 0.1f) } } return consumedByPreScroll + consumedByScroll } override suspend fun applyToFling( velocity: Velocity, performFling: suspend (Velocity) -> Velocity, ) { val consumed = performFling(velocity) // when the fling happens - we just gradually animate our overscroll to 0 val remaining = velocity - consumed overscrollOffset.animateTo( targetValue = 0f, initialVelocity = remaining.y, animationSpec = spring(), ) } override val isInProgress: Boolean get() = overscrollOffset.value != 0f // Create a LayoutModifierNode that offsets by overscrollOffset.value override val node: DelegatableNode = object : Modifier.Node(), LayoutModifierNode { override fun MeasureScope.measure( measurable: Measurable, constraints: Constraints, ): MeasureResult { val placeable = measurable.measure(constraints) return layout(placeable.width, placeable.height) { val offsetValue = IntOffset(x = 0, y = overscrollOffset.value.roundToInt()) placeable.placeRelativeWithLayer(offsetValue.x, offsetValue.y) } } } } val offset = remember { mutableStateOf(0f) } val scope = rememberCoroutineScope() // Create the overscroll controller val overscroll = remember(scope) { OffsetOverscrollEffect(scope) } // let's build a scrollable that scroll until -512 to 512 val scrollStateRange = (-512f).rangeTo(512f) Box( Modifier.size(150.dp) .scrollable( orientation = Orientation.Vertical, state = rememberScrollableState { delta -> // use the scroll data and indicate how much this element consumed. val oldValue = offset.value // coerce to our range offset.value = (offset.value + delta).coerceIn(scrollStateRange) offset.value - oldValue // indicate that we consumed what's needed }, // pass the overscroll to the scrollable so the data is updated overscrollEffect = overscroll, ) .background(Color.LightGray), contentAlignment = Alignment.Center, ) { Text( offset.value.roundToInt().toString(), style = TextStyle(fontSize = 32.sp), modifier = Modifier // show the overscroll only on the text, not the containers (just for fun) .overscroll(overscroll), ) }
| Parameters | |
|---|---|
overscrollEffect: OverscrollEffect? |
the |