pullRefresh

Functions summary

Modifier

A nested scroll modifier that provides scroll events to state.

Cmn
Modifier
@ExperimentalMaterialApi
Modifier.pullRefresh(
    onPull: (pullDelta: Float) -> Float,
    onRelease: suspend (flingVelocity: Float) -> Float,
    enabled: Boolean
)

A nested scroll modifier that provides onPull and onRelease callbacks to aid building custom pull refresh components.

Cmn

Functions

Modifier.pullRefresh

@ExperimentalMaterialApi
fun Modifier.pullRefresh(state: PullRefreshState, enabled: Boolean = true): Modifier

A nested scroll modifier that provides scroll events to state.

Note that this modifier must be added above a scrolling container, such as a lazy column, in order to receive scroll events. For example:

import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.ListItem
import androidx.compose.material.Text
import androidx.compose.material.pullrefresh.PullRefreshIndicator
import androidx.compose.material.pullrefresh.pullRefresh
import androidx.compose.material.pullrefresh.rememberPullRefreshState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier

val refreshScope = rememberCoroutineScope()
var refreshing by remember { mutableStateOf(false) }
var itemCount by remember { mutableStateOf(15) }

fun refresh() =
    refreshScope.launch {
        refreshing = true
        delay(1500)
        itemCount += 5
        refreshing = false
    }

val state = rememberPullRefreshState(refreshing, ::refresh)

Box(Modifier.pullRefresh(state)) {
    LazyColumn(Modifier.fillMaxSize()) {
        if (!refreshing) {
            items(itemCount) { ListItem { Text(text = "Item ${itemCount - it}") } }
        }
    }

    PullRefreshIndicator(refreshing, state, Modifier.align(Alignment.TopCenter))
}
Parameters
state: PullRefreshState

The PullRefreshState associated with this pull-to-refresh component. The state will be updated by this modifier.

enabled: Boolean = true

If not enabled, all scroll delta and fling velocity will be ignored.

Modifier.pullRefresh

@ExperimentalMaterialApi
fun Modifier.pullRefresh(
    onPull: (pullDelta: Float) -> Float,
    onRelease: suspend (flingVelocity: Float) -> Float,
    enabled: Boolean = true
): Modifier

A nested scroll modifier that provides onPull and onRelease callbacks to aid building custom pull refresh components.

Note that this modifier must be added above a scrolling container, such as a lazy column, in order to receive scroll events. For example:

import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.animate
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.LinearProgressIndicator
import androidx.compose.material.ListItem
import androidx.compose.material.Text
import androidx.compose.material.pullrefresh.pullRefresh
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.dp

val refreshScope = rememberCoroutineScope()
val threshold = with(LocalDensity.current) { 160.dp.toPx() }

var refreshing by remember { mutableStateOf(false) }
var itemCount by remember { mutableStateOf(15) }
var currentDistance by remember { mutableStateOf(0f) }

val progress = currentDistance / threshold

fun refresh() =
    refreshScope.launch {
        refreshing = true
        delay(1500)
        itemCount += 5
        refreshing = false
    }

fun onPull(pullDelta: Float): Float =
    when {
        refreshing -> 0f
        else -> {
            val newOffset = (currentDistance + pullDelta).coerceAtLeast(0f)
            val dragConsumed = newOffset - currentDistance
            currentDistance = newOffset
            dragConsumed
        }
    }

fun onRelease(velocity: Float): Float {
    if (refreshing) return 0f // Already refreshing - don't call refresh again.
    if (currentDistance > threshold) refresh()

    refreshScope.launch {
        animate(initialValue = currentDistance, targetValue = 0f) { value, _ ->
            currentDistance = value
        }
    }

    // Only consume if the fling is downwards and the indicator is visible
    return if (velocity > 0f && currentDistance > 0f) {
        velocity
    } else {
        0f
    }
}

Box(Modifier.pullRefresh(::onPull, ::onRelease)) {
    LazyColumn {
        if (!refreshing) {
            items(itemCount) { ListItem { Text(text = "Item ${itemCount - it}") } }
        }
    }

    // Custom progress indicator
    AnimatedVisibility(visible = (refreshing || progress > 0)) {
        if (refreshing) {
            LinearProgressIndicator(Modifier.fillMaxWidth())
        } else {
            LinearProgressIndicator(progress, Modifier.fillMaxWidth())
        }
    }
}
Parameters
onPull: (pullDelta: Float) -> Float

Callback for dispatching vertical scroll delta, takes float pullDelta as argument. Positive delta (pulling down) is dispatched only if the child does not consume it (i.e. pulling down despite being at the top of a scrollable component), whereas negative delta (swiping up) is dispatched first (in case it is needed to push the indicator back up), and then the unconsumed delta is passed on to the child. The callback returns how much delta was consumed.

onRelease: suspend (flingVelocity: Float) -> Float

Callback for when drag is released, takes float flingVelocity as argument. The callback returns how much velocity was consumed - in most cases this should only consume velocity if pull refresh has been dragged already and the velocity is positive (the fling is downwards), as an upwards fling should typically still scroll a scrollable component beneath the pullRefresh. This is invoked before any remaining velocity is passed to the child.

enabled: Boolean = true

If not enabled, all scroll delta and fling velocity will be ignored and neither onPull nor onRelease will be invoked.