Jetpack Compose 的高性能下拉刷新与上拉加载更多组件

Jetpack Compose 的高性能下拉刷新与上拉加载更多组件

GitHub:compose-refresh-layout

基于 Jetpack Compose 构建的声明式嵌套滑动刷新容器,提供极简、高性能的下拉刷新与上拉加载能力。其核心使用原生 graphicsLayer 进行零重绘渲染,具备丝滑的物理阻尼回弹效果。


运行效果


环境要求

  • Kotlin 1.9+
  • Jetpack Compose 1.5.0+
  • minSdk 24

安装

在模块的 build.gradle.kts 中添加依赖:

scss 复制代码
dependencies {
    implementation("io.github.lx-0713:compose-smart-refresh:1.0.1")
}

确保 settings.gradle.kts 包含 mavenCentral() 仓库:

scss 复制代码
dependencyResolutionManagement {
    repositories {
        mavenCentral()
    }
}

基础用法

SmartSwipeRefresh 包裹任意支持嵌套滚动的组件(LazyColumnLazyRow 等),通过 rememberSmartSwipeRefreshState() 获取状态机,在回调中执行业务逻辑后调用 state.finish() 结束即可。

scss 复制代码
@Composable
fun BasicDemo() {
    val state = rememberSmartSwipeRefreshState()
    val scope = rememberCoroutineScope()
    var items by remember { mutableStateOf((1..20).toList()) }
​
    SmartSwipeRefresh(
        state = state,
        onRefresh = {
            scope.launch {
                delay(1500)          // 模拟网络请求
                items = (1..20).toList()
                state.finish()       // 结束刷新
            }
        },
        onLoadMore = {
            scope.launch {
                delay(1500)
                if (items.size >= 60) {
                    state.finish(noMoreData = true)  // 无更多数据
                } else {
                    items = items + (items.size + 1..items.size + 20)
                    state.finish()
                }
            }
        }
    ) {
        LazyColumn(modifier = Modifier.fillMaxSize()) {
            items(items) { item ->
                Text("Item $item", modifier = Modifier.padding(16.dp))
            }
        }
    }
}

注意onRefreshonLoadMore 回调都在主线程触发,必须通过 scope.launch {} 切换到协程中执行异步操作,并在完成后调用 state.finish()


横向列表

传入 orientation = Orientation.Horizontal 即可切换为横向模式,无需任何其他改动。Header 出现在列表左侧(向右拉触发刷新),Footer 出现在右侧(向左拉触发加载)。

ini 复制代码
SmartSwipeRefresh(
    state = state,
    orientation = Orientation.Horizontal,
    onRefresh = { scope.launch { delay(1000); state.finish() } },
    onLoadMore = { scope.launch { delay(1000); state.finish() } }
) {
    LazyRow(modifier = Modifier.fillMaxSize()) {
        items(20) { index ->
            Text("Item $index", modifier = Modifier.padding(16.dp))
        }
    }
}

自定义"无更多数据"文案

通过 noMoreDataText 参数替换默认底部文案。当 state.finish(noMoreData = true) 被调用后,Footer 会自动切换到无数据停靠状态并展示该文案:

ini 复制代码
SmartSwipeRefresh(
    state = state,
    noMoreDataText = "--- 我可是有底线的 ---",
    onLoadMore = {
        scope.launch {
            delay(1000)
            state.finish(noMoreData = true)
        }
    }
) { ... }

通过 headerfooter 参数传入自定义 Composable。在自定义视图中,可通过 state.refreshFlagstate.loadMoreFlagstate.noMoreData 感知当前交互状态并做出响应:

scss 复制代码
SmartSwipeRefresh(
    state = state,
    header = {
        Box(
            modifier = Modifier.fillMaxWidth().height(60.dp),
            contentAlignment = Alignment.Center
        ) {
            when (state.refreshFlag) {
                SmartRefreshFlag.PULLING    -> Text("继续下拉刷新...")
                SmartRefreshFlag.REFRESHING -> CircularProgressIndicator()
                else                        -> Text("下拉刷新")
            }
        }
    },
    footer = {
        Box(
            modifier = Modifier.fillMaxWidth().height(50.dp),
            contentAlignment = Alignment.Center
        ) {
            if (state.noMoreData) {
                Text("没有更多了", color = Color.Gray)
            } else {
                Row(verticalAlignment = Alignment.CenterVertically) {
                    CircularProgressIndicator(modifier = Modifier.size(16.dp))
                    Spacer(modifier = Modifier.width(8.dp))
                    Text("加载中...")
                }
            }
        }
    }
) {
    LazyColumn { /* ... */ }
}

提示 :自定义 Header/Footer 的高度决定了拉动阈值,建议保持在 48.dp ~ 64.dp 之间以获得最佳手感。


程序主动触发刷新 / 加载

无需用户手动操作,可由代码主动驱动刷新行为:

scss 复制代码
// 进入页面时自动触发下拉刷新(Header 弹出)
LaunchedEffect(Unit) {
    state.autoRefresh()
}
​
// 程序主动触发上拉加载(Footer 弹出)
LaunchedEffect(Unit) {
    state.autoLoadMore()
}

autoRefresh()autoLoadMore() 内部均有幂等保护,重复调用不会产生副作用。


动态开关与阻尼调节

所有控制属性均支持运行时动态修改,Compose 会自动响应变化:

ini 复制代码
// 进入编辑模式时禁用下拉刷新
state.enableRefresh = false
​
// 数据请求中临时禁用上拉加载,防止重复触发
state.enableLoadMore = false
​
// 调节拖拽阻尼(0.1~1f,越小手感越紧致,默认 0.5f)
state.stickinessLevel = 0.3f

切换条件后重置无更多数据

切换搜索关键词、筛选条件或 Tab 后,需要重置底部状态以重新激活上拉加载:

scss 复制代码
// 搜索关键词变化时重置
LaunchedEffect(keyword) {
    state.resetNoMoreData()
}

执行下拉刷新(onRefresh 触发)时,noMoreData 会自动重置,无需手动调用。


仅启用单向刷新

若只需要下拉刷新,不传 onLoadMore 即可;若只需要上拉加载,不传 onRefresh 即可。也可以通过属性动态控制:

ini 复制代码
// 只有下拉刷新
SmartSwipeRefresh(
    state = state,
    onRefresh = { scope.launch { delay(1000); state.finish() } }
    // 不传 onLoadMore,上拉加载自动禁用
) { ... }

// 运行时关闭某一端
state.enableLoadMore = false

API 速查

SmartSwipeRefresh 参数

参数 类型 说明 默认值
state SmartSwipeRefreshState 核心状态机(必填)
orientation Orientation VerticalHorizontal Vertical
onRefresh (() -> Unit)? 下拉刷新回调 null
onLoadMore (() -> Unit)? 上拉加载回调 null
noMoreDataText String? 自定义无更多数据文案 null
header @Composable () -> Unit 自定义头部 Composable RefreshHeader
footer @Composable () -> Unit 自定义尾部 Composable RefreshFooter
content @Composable () -> Unit 列表内容(必填)

SmartSwipeRefreshState 方法与属性

方法 / 属性 说明
finish(noMoreData: Boolean = false) 结束当前刷新或加载操作(自动识别类型)
autoRefresh() 程序主动触发下拉刷新,Header 自动弹出
autoLoadMore() 程序主动触发上拉加载,Footer 自动弹出
resetNoMoreData() 重置无更多数据状态
enableRefresh 动态开关下拉刷新
enableLoadMore 动态开关上拉加载
stickinessLevel 拖拽阻尼系数(0.1~1f,默认 0.5f)
refreshFlag 顶部当前状态(SmartRefreshFlag
loadMoreFlag 底部当前状态(SmartRefreshFlag
noMoreData 是否已无更多数据

SmartRefreshFlag 枚举

含义
IDLE 空闲,无越界偏移
PULLING 正在拉拽,尚未达到触发阈值,松手后自动复位
REFRESHING 已触发业务回调,等待 finish() 指令
FINISHING 归位动画执行中,期间屏蔽额外输入
相关推荐
alexhilton3 天前
使用Android Archive进行打包
android·kotlin·android jetpack
Junerver6 天前
我写了一个 Compose Multiplatform 组件库,你可能会用到
kotlin·android jetpack
我命由我123457 天前
Jetpack Room - Room 查询返回列表无需判空、LIKE 关键字
android·java·开发语言·java-ee·android jetpack·android-studio·android runtime
QING6188 天前
Kotlin 日常开发常用语法糖整理 —— 速记
android·kotlin·android jetpack
我命由我123458 天前
Android 开发问题:EditText 控件的 android:imeOptions=“actionDone“ 属性不生效
android·java·java-ee·android studio·android jetpack·android-studio·android runtime
我命由我123458 天前
Android 开发问题:获取到的 Android ID 发生了变化
android·java·开发语言·java-ee·android studio·android jetpack·android runtime
我命由我123458 天前
Android 开发问题:Unable to find explicit activity class
android·java·java-ee·android studio·android jetpack·android-studio·android runtime
我命由我123458 天前
Android 开发问题:全局的主题颜色设置,导致 CheckBox 控件在勾选状态下不显示样式
android·java·开发语言·java-ee·intellij-idea·intellij idea·android jetpack
alexhilton11 天前
Android的Agent优先时代:构建时vs运行时
android·kotlin·android jetpack
我命由我1234511 天前
Android 开发问题:View 的 getWidth、getHeight 方法返回的值都为 0
android·java·java-ee·android studio·android jetpack·android-studio·android runtime