下拉即可刷新

借助“下拉刷新”组件,用户可以在应用内容开头向下拖动以刷新数据。

API Surface

使用 PullToRefreshBox 可组合项实现“拉动刷新”功能,该功能可用作可滚动内容的容器。以下关键参数可控制刷新行为和外观:

  • isRefreshing:一个布尔值,指示刷新操作目前是否正在进行。
  • onRefresh:在用户发起刷新时执行的 lambda 函数。
  • indicator:自定义在“拉动以刷新”时绘制的指示器。

基本示例

以下代码段演示了 PullToRefreshBox 的基本用法:

@Composable
fun PullToRefreshBasicSample(
    items: List<String>,
    isRefreshing: Boolean,
    onRefresh: () -> Unit,
    modifier: Modifier = Modifier
) {
    PullToRefreshBox(
        isRefreshing = isRefreshing,
        onRefresh = onRefresh,
        modifier = modifier
    ) {
        LazyColumn(Modifier.fillMaxSize()) {
            items(items) {
                ListItem({ Text(text = it) })
            }
        }
    }
}

代码要点

  • PullToRefreshBox 会封装 LazyColumn,后者用于显示字符串列表。
  • PullToRefreshBox 需要 isRefreshingonRefresh 参数。
  • PullToRefreshBox 块中的内容代表可滚动内容。

结果

此视频演示了上述代码中的基本下拉刷新实现:

图 1。对项列表的基本下拉刷新实现。

高级示例:自定义指示器颜色

@Composable
fun PullToRefreshCustomStyleSample(
    items: List<String>,
    isRefreshing: Boolean,
    onRefresh: () -> Unit,
    modifier: Modifier = Modifier
) {
    val state = rememberPullToRefreshState()

    PullToRefreshBox(
        isRefreshing = isRefreshing,
        onRefresh = onRefresh,
        modifier = modifier,
        state = state,
        indicator = {
            Indicator(
                modifier = Modifier.align(Alignment.TopCenter),
                isRefreshing = isRefreshing,
                containerColor = MaterialTheme.colorScheme.primaryContainer,
                color = MaterialTheme.colorScheme.onPrimaryContainer,
                state = state
            )
        },
    ) {
        LazyColumn(Modifier.fillMaxSize()) {
            items(items) {
                ListItem({ Text(text = it) })
            }
        }
    }
}

代码要点

  • 指示器颜色是通过 indicator 参数中的 containerColorcolor 属性来自定义的。
  • rememberPullToRefreshState() 用于管理刷新操作的状态。您可以将此状态与 indicator 参数搭配使用。

结果

此视频展示了带有彩色指示器的下拉刷新实现:

图 2。使用自定义样式的下拉刷新实现。

高级示例:创建完全自定义的指示器

您可以利用现有的可组合项和动画创建复杂的自定义指示器。以下代码段演示了如何在下拉刷新实现中创建完全自定义的指示器:

@Composable
fun PullToRefreshCustomIndicatorSample(
    items: List<String>,
    isRefreshing: Boolean,
    onRefresh: () -> Unit,
    modifier: Modifier = Modifier
) {
    val state = rememberPullToRefreshState()

    PullToRefreshBox(
        isRefreshing = isRefreshing,
        onRefresh = onRefresh,
        modifier = modifier,
        state = state,
        indicator = {
            MyCustomIndicator(
                state = state,
                isRefreshing = isRefreshing,
                modifier = Modifier.align(Alignment.TopCenter)
            )
        }
    ) {
        LazyColumn(Modifier.fillMaxSize()) {
            items(items) {
                ListItem({ Text(text = it) })
            }
        }
    }
}

// ...
@Composable
fun MyCustomIndicator(
    state: PullToRefreshState,
    isRefreshing: Boolean,
    modifier: Modifier = Modifier,
) {
    Box(
        modifier = modifier.pullToRefreshIndicator(
            state = state,
            isRefreshing = isRefreshing,
            containerColor = PullToRefreshDefaults.containerColor,
            threshold = PositionalThreshold
        ),
        contentAlignment = Alignment.Center
    ) {
        Crossfade(
            targetState = isRefreshing,
            animationSpec = tween(durationMillis = CROSSFADE_DURATION_MILLIS),
            modifier = Modifier.align(Alignment.Center)
        ) { refreshing ->
            if (refreshing) {
                CircularProgressIndicator(Modifier.size(SPINNER_SIZE))
            } else {
                val distanceFraction = { state.distanceFraction.coerceIn(0f, 1f) }
                Icon(
                    imageVector = Icons.Filled.CloudDownload,
                    contentDescription = "Refresh",
                    modifier = Modifier
                        .size(18.dp)
                        .graphicsLayer {
                            val progress = distanceFraction()
                            this.alpha = progress
                            this.scaleX = progress
                            this.scaleY = progress
                        }
                )
            }
        }
    }
}

代码要点

  • 上一个代码段使用了该库提供的 Indicator。此代码段会创建一个名为 MyCustomIndicator 的自定义指示器可组合项。在此可组合项中,pullToRefreshIndicator 修饰符会处理定位和触发刷新。
  • 与前面的代码段一样,PullToRefreshState 实例已被提取,因此可以将同一实例同时传递给 PullToRefreshBoxpullToRefreshModifier
  • 容器颜色和位置阈值来自 PullToRefreshDefaults 类。这样,您就可以重复使用 Material 库中的默认行为和样式,同时仅自定义您感兴趣的元素。
  • MyCustomIndicator 使用 Crossfade 在云图标和 CircularProgressIndicator 之间进行转换。云朵图标会随着用户拉动而放大,并在刷新操作开始时转换为 CircularProgressIndicator
    • targetState 使用 isRefreshing 来确定要显示哪种状态(云图标或圆形进度指示器)。
    • animationSpec 为过渡定义了 tween 动画,其指定时长为 CROSSFADE_DURATION_MILLIS
    • state.distanceFraction 表示用户向下拉动了多远,范围从 0f(未拉动)到 1f(完全拉动)。
    • graphicsLayer 修饰符可修改缩放比例和透明度。

结果

此视频展示了上方代码中的自定义指示器:

图 3。带有自定义指示器的下拉刷新实现。

其他资源