PullToRefreshBox

Functions summary

Unit
@Composable
PullToRefreshBox(
    isRefreshing: Boolean,
    onRefresh: () -> Unit,
    state: PullToRefreshState,
    modifier: Modifier,
    contentAlignment: Alignment,
    indicator: @Composable BoxScope.() -> Unit,
    enabled: Boolean,
    threshold: Dp,
    content: @Composable BoxScope.() -> Unit
)

Container that wraps scrollable content to add pull-to-refresh gesture support.

Functions

@Composable
fun PullToRefreshBox(
    isRefreshing: Boolean,
    onRefresh: () -> Unit,
    state: PullToRefreshState,
    modifier: Modifier = Modifier,
    contentAlignment: Alignment = Alignment.TopCenter,
    indicator: @Composable BoxScope.() -> Unit = { PullToRefreshDefaults.Indicator( modifier = Modifier.align(Alignment.TopCenter), isRefreshing = isRefreshing, state = state, ) },
    enabled: Boolean = true,
    threshold: Dp = PullToRefreshDefaults.PositionalThreshold,
    content: @Composable BoxScope.() -> Unit
): Unit

Container that wraps scrollable content to add pull-to-refresh gesture support.

By default, it uses PullToRefreshDefaults.Indicator as the refresh indicator, which internally uses a androidx.wear.compose.material3.CircularProgressIndicator when refreshing. You may also choose to set your own indicator.

Example of a standard PullToRefreshBox wrapping a androidx.wear.compose.foundation.lazy.TransformingLazyColumn:

import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material.icons.filled.Refresh
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.wear.compose.foundation.lazy.TransformingLazyColumn
import androidx.wear.compose.foundation.lazy.rememberTransformingLazyColumnState
import androidx.wear.compose.material3.AppScaffold
import androidx.wear.compose.material3.Button
import androidx.wear.compose.material3.ButtonDefaults
import androidx.wear.compose.material3.PullToRefreshBox
import androidx.wear.compose.material3.PullToRefreshState
import androidx.wear.compose.material3.ScreenScaffold
import androidx.wear.compose.material3.SurfaceTransformation
import androidx.wear.compose.material3.Text
import androidx.wear.compose.material3.TimeText
import androidx.wear.compose.material3.lazy.rememberTransformationSpec
import androidx.wear.compose.material3.lazy.transformedHeight

var itemCount by remember { mutableIntStateOf(10) }
var isRefreshing by remember { mutableStateOf(false) }
val coroutineScope = rememberCoroutineScope()
val listState = rememberTransformingLazyColumnState()
val transformationSpec = rememberTransformationSpec()
val state = remember { PullToRefreshState() }

AppScaffold {
    ScreenScaffold(scrollState = listState, timeText = { TimeText() }) { contentPadding ->
        PullToRefreshBox(
            isRefreshing = isRefreshing,
            onRefresh = {
                isRefreshing = true
                coroutineScope.launch {
                    delay(2000)
                    itemCount += 5
                    isRefreshing = false
                }
            },
            state = state,
            modifier = Modifier.fillMaxSize(),
        ) {
            TransformingLazyColumn(
                state = listState,
                contentPadding = contentPadding,
                modifier = Modifier.fillMaxSize(),
            ) {
                items(itemCount) { index ->
                    Button(
                        onClick = {},
                        label = { Text("Item ${index + 1}") },
                        transformation = SurfaceTransformation(transformationSpec),
                        modifier =
                            Modifier.transformedHeight(this, transformationSpec)
                                .minimumVerticalContentPadding(
                                    ButtonDefaults.minimumVerticalListContentPadding
                                )
                                .fillMaxWidth(),
                    )
                }
            }
        }
    }
}

Example of a PullToRefreshBox with a custom indicator:

import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Refresh
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.graphics.graphicsLayer
import androidx.compose.ui.unit.dp
import androidx.wear.compose.foundation.lazy.TransformingLazyColumn
import androidx.wear.compose.foundation.lazy.rememberTransformingLazyColumnState
import androidx.wear.compose.material3.AppScaffold
import androidx.wear.compose.material3.Button
import androidx.wear.compose.material3.ButtonDefaults
import androidx.wear.compose.material3.CircularProgressIndicator
import androidx.wear.compose.material3.Icon
import androidx.wear.compose.material3.MaterialTheme
import androidx.wear.compose.material3.ProgressIndicatorDefaults
import androidx.wear.compose.material3.PullToRefreshBox
import androidx.wear.compose.material3.PullToRefreshDefaults
import androidx.wear.compose.material3.PullToRefreshState
import androidx.wear.compose.material3.ScreenScaffold
import androidx.wear.compose.material3.SurfaceTransformation
import androidx.wear.compose.material3.Text
import androidx.wear.compose.material3.TimeText
import androidx.wear.compose.material3.lazy.rememberTransformationSpec
import androidx.wear.compose.material3.lazy.transformedHeight

var isRefreshing by remember { mutableStateOf(false) }
val coroutineScope = rememberCoroutineScope()
val state = remember { PullToRefreshState() }
val listState = rememberTransformingLazyColumnState()
val transformationSpec = rememberTransformationSpec()

AppScaffold {
    ScreenScaffold(scrollState = listState, timeText = { TimeText() }) { contentPadding ->
        PullToRefreshBox(
            isRefreshing = isRefreshing,
            onRefresh = {
                isRefreshing = true
                coroutineScope.launch {
                    delay(2000)
                    isRefreshing = false
                }
            },
            state = state,
            indicator = {
                PullToRefreshDefaults.IndicatorBox(
                    state = state,
                    isRefreshing = isRefreshing,
                    shape = MaterialTheme.shapes.medium,
                    containerColor = MaterialTheme.colorScheme.primaryContainer,
                    modifier = Modifier.align(Alignment.TopCenter),
                ) {
                    if (isRefreshing) {
                        CircularProgressIndicator(
                            strokeWidth = 2.5.dp,
                            colors =
                                ProgressIndicatorDefaults.colors(
                                    indicatorColor =
                                        MaterialTheme.colorScheme.onPrimaryContainer
                                ),
                            modifier = Modifier.size(24.dp),
                        )
                    } else {
                        val rotation = state.distanceFraction * 180f
                        val scale = state.distanceFraction.coerceIn(0f, 1f)
                        Icon(
                            imageVector = Icons.Default.Refresh,
                            contentDescription = "Pull to refresh",
                            tint = MaterialTheme.colorScheme.onPrimaryContainer,
                            modifier =
                                Modifier.size(24.dp).graphicsLayer {
                                    rotationZ = rotation
                                    scaleX = scale
                                    scaleY = scale
                                },
                        )
                    }
                }
            },
            modifier = Modifier.fillMaxSize(),
        ) {
            TransformingLazyColumn(
                state = listState,
                contentPadding = contentPadding,
                modifier = Modifier.fillMaxSize(),
            ) {
                items(10) { index ->
                    Button(
                        onClick = {},
                        label = { Text("Custom item ${index + 1}") },
                        transformation = SurfaceTransformation(transformationSpec),
                        modifier =
                            Modifier.transformedHeight(this, transformationSpec)
                                .minimumVerticalContentPadding(
                                    ButtonDefaults.minimumVerticalListContentPadding
                                )
                                .fillMaxWidth(),
                    )
                }
            }
        }
    }
}
Parameters
isRefreshing: Boolean

whether a refresh is occurring

onRefresh: () -> Unit

callback invoked when the user gesture crosses the threshold, thereby requesting a refresh.

state: PullToRefreshState

the state that keeps track of distance pulled

modifier: Modifier = Modifier

the Modifier to be applied to this container

contentAlignment: Alignment = Alignment.TopCenter

the default alignment inside the Box.

indicator: @Composable BoxScope.() -> Unit = { PullToRefreshDefaults.Indicator( modifier = Modifier.align(Alignment.TopCenter), isRefreshing = isRefreshing, state = state, ) }

the indicator that will be drawn on top of the content when the user begins a pull or a refresh is occurring

enabled: Boolean = true

whether nested scroll events should be consumed by this component

threshold: Dp = PullToRefreshDefaults.PositionalThreshold

how much distance can be scrolled down before onRefresh is invoked

content: @Composable BoxScope.() -> Unit

the content of the pull refresh container, typically a scrollable layout such as androidx.wear.compose.foundation.lazy.TransformingLazyColumn or a layout using androidx.compose.foundation.verticalScroll