androidx.compose.material.pullrefresh

Classes

PullRefreshState

A state object that can be used in conjunction with pullRefresh to add pull-to-refresh behaviour to a scroll component.

Cmn

Objects

PullRefreshDefaults

Default parameter values for rememberPullRefreshState.

Cmn

Top-level functions summary

Unit
@Composable
@ExperimentalMaterialApi
PullRefreshIndicator(
    refreshing: Boolean,
    state: PullRefreshState,
    modifier: Modifier,
    backgroundColor: Color,
    contentColor: Color,
    scale: Boolean
)

The default indicator for Compose pull-to-refresh, based on Android's SwipeRefreshLayout.

Cmn
PullRefreshState
@Composable
@ExperimentalMaterialApi
rememberPullRefreshState(
    refreshing: Boolean,
    onRefresh: () -> Unit,
    refreshThreshold: Dp,
    refreshingOffset: Dp
)

Creates a PullRefreshState that is remembered across compositions.

Cmn

Extension 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
Modifier

A modifier for translating the position and scaling the size of a pull-to-refresh indicator based on the given PullRefreshState.

Cmn

Top-level functions

PullRefreshIndicator

@Composable
@ExperimentalMaterialApi
fun PullRefreshIndicator(
    refreshing: Boolean,
    state: PullRefreshState,
    modifier: Modifier = Modifier,
    backgroundColor: Color = MaterialTheme.colors.surface,
    contentColor: Color = contentColorFor(backgroundColor),
    scale: Boolean = false
): Unit

The default indicator for Compose pull-to-refresh, based on Android's SwipeRefreshLayout.

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
refreshing: Boolean

A boolean representing whether a refresh is occurring.

state: PullRefreshState

The PullRefreshState which controls where and how the indicator will be drawn.

modifier: Modifier = Modifier

Modifiers for the indicator.

backgroundColor: Color = MaterialTheme.colors.surface

The color of the indicator's background.

contentColor: Color = contentColorFor(backgroundColor)

The color of the indicator's arc and arrow.

scale: Boolean = false

A boolean controlling whether the indicator's size scales with pull progress or not.

rememberPullRefreshState

@Composable
@ExperimentalMaterialApi
fun rememberPullRefreshState(
    refreshing: Boolean,
    onRefresh: () -> Unit,
    refreshThreshold: Dp = PullRefreshDefaults.RefreshThreshold,
    refreshingOffset: Dp = PullRefreshDefaults.RefreshingOffset
): PullRefreshState

Creates a PullRefreshState that is remembered across compositions.

Changes to refreshing will result in PullRefreshState being updated.

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
refreshing: Boolean

A boolean representing whether a refresh is currently occurring.

onRefresh: () -> Unit

The function to be called to trigger a refresh.

refreshThreshold: Dp = PullRefreshDefaults.RefreshThreshold

The threshold below which, if a release occurs, onRefresh will be called.

refreshingOffset: Dp = PullRefreshDefaults.RefreshingOffset

The offset at which the indicator will be drawn while refreshing. This offset corresponds to the position of the bottom of the indicator.

Extension functions

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.

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.

pullRefreshIndicatorTransform

@ExperimentalMaterialApi
fun Modifier.pullRefreshIndicatorTransform(
    state: PullRefreshState,
    scale: Boolean = false
): Modifier

A modifier for translating the position and scaling the size of a pull-to-refresh indicator based on the given PullRefreshState.

import androidx.compose.animation.core.animate
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.CircularProgressIndicator
import androidx.compose.material.ListItem
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.material.pullrefresh.pullRefresh
import androidx.compose.material.pullrefresh.pullRefreshIndicatorTransform
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
import androidx.compose.ui.draw.rotate
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp

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)
val rotation = animateFloatAsState(state.progress * 120)

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

    Surface(
        modifier =
            Modifier.size(40.dp)
                .align(Alignment.TopCenter)
                .pullRefreshIndicatorTransform(state)
                .rotate(rotation.value),
        shape = RoundedCornerShape(10.dp),
        color = Color.DarkGray,
        elevation = if (state.progress > 0 || refreshing) 20.dp else 0.dp,
    ) {
        Box {
            if (refreshing) {
                CircularProgressIndicator(
                    modifier = Modifier.align(Alignment.Center).size(25.dp),
                    color = Color.White,
                    strokeWidth = 3.dp
                )
            }
        }
    }
}
Parameters
state: PullRefreshState

The PullRefreshState which determines the position of the indicator.

scale: Boolean = false

A boolean controlling whether the indicator's size scales with pull progress or not.