Skip to content

分页父类

当页面既需要“下拉刷新”又要“上拉加载更多”时,使用 BaseNetWorkListViewModel + BaseNetWorkListUiState + LoadMoreState + BaseNetWorkListView + RefreshLayout 的组合可以最大化复用状态管理与交互逻辑。相关文件:

  • app/src/main/java/com/joker/kit/core/base/viewmodel/BaseNetWorkListViewModel.kt
  • app/src/main/java/com/joker/kit/core/base/state/BaseNetWorkListUiState.kt
  • app/src/main/java/com/joker/kit/core/base/state/LoadMoreState.kt
  • app/src/main/java/com/joker/kit/core/ui/component/network/BaseNetWorkListView.kt
  • app/src/main/java/com/joker/kit/core/ui/component/refresh/

相比不分页的 BaseNetWorkViewModel,分页基类额外处理页码、刷新、加载更多及“空数据”状态,为列表场景减轻大量样板代码。

状态模型

页面状态:BaseNetWorkListUiState

状态含义默认视图
Loading首次加载或刷新中PageLoading()
Success列表有数据渲染传入的 content
Empty列表为空EmptyData(),带重试按钮
Error加载失败EmptyNetwork(),带重试按钮

加载更多:LoadMoreState

状态含义
PullToLoad可以上拉触发加载
Loading正在请求下一页
Success上一页加载成功,短暂展示
Error加载失败,通常提示“点击重试”
NoMore没有更多数据了

BaseNetWorkListView 负责切换页面级 UI(Loading/Empty/Error/Success),成功态下交给 RefreshLayout 和业务列表渲染;LoadMoreState 则由 RefreshLayout 底部提示条消费。

BaseNetWorkListViewModel 核心能力

  • 分页 Flow:子类实现 requestListData(),返回 Flow<NetworkResponse<NetworkPageData<T>>>NetworkPageData 包含 listpagination,基类会根据 totalsizepage 计算是否还有下一页。
  • 自动状态管理_uiState_listData_loadMoreState_isRefreshing 通过 MutableStateFlow 暴露给 UI。
  • 刷新与加载更多onRefresh() 重置页码并触发请求,onLoadMore() 根据 shouldTriggerLoadMore(lastIndex, totalCount) 判断是否递增页码。
  • 最小加载时间:首屏可开启 enableMinLoadingTime,防止骨架闪烁。
  • 导航联动:继承自 BaseViewModel,同样支持类型安全导航、登录拦截、observeRefreshState()

常用流程:

  1. 子类在 init { initLoad() } 中启动首屏请求。
  2. loadListData() 根据当前页和状态决定是否展示 Loading、如何处理错误。
  3. 成功时首屏调用 setFirstLoadSuccessState(),加载更多则追加到 _listData
  4. UI 通过 listData 渲染 LazyList,LoadMoreState 控制底部提示。

示例:商品评论分页

以下示例与 context/青商城 原项目 goods 模块代码示例 中的评论页面结构一致,只是为了说明当前脚手架的使用方式。ViewModel 负责接入仓库分页接口,UI 则由 Scaffold + BaseNetWorkListView + RefreshLayout 组成。

ViewModel

kotlin
@HiltViewModel
class GoodsCommentViewModel @Inject constructor(
    navigator: AppNavigator,
    userState: UserState,
    private val savedStateHandle: SavedStateHandle,
    private val repository: GoodsRepository,
) : BaseNetWorkListViewModel<Comment>( // Comment 为列表项类型
    navigator = navigator,
    userState = userState
) {
    private val goodsId: Long = savedStateHandle.toRoute<GoodsRoutes.Comment>().goodsId

    init {
        initLoad() // 首屏进来即加载第一页
    }

    override fun requestListData(): Flow<NetworkResponse<NetworkPageData<Comment>>> {
        return repository.getGoodsCommentPage(
            request = GoodsCommentPageRequest(
                goodsId = goodsId.toString(),
                page = currentPage,
                size = pageSize
            )
        )
    }
}

UI 层

kotlin
@Composable
fun GoodsCommentRoute(
    viewModel: GoodsCommentViewModel = hiltViewModel(),
) {
    val uiState by viewModel.uiState.collectAsState()
    val listData by viewModel.listData.collectAsState()
    val isRefreshing by viewModel.isRefreshing.collectAsState()
    val loadMoreState by viewModel.loadMoreState.collectAsState()

    Scaffold { innerPadding ->
        BaseNetWorkListView(
            uiState = uiState,
            padding = innerPadding,
            onRetry = viewModel::retryRequest
        ) {
            RefreshLayout(
                isRefreshing = isRefreshing,
                loadMoreState = loadMoreState,
                onRefresh = viewModel::onRefresh,
                onLoadMore = viewModel::onLoadMore,
                shouldTriggerLoadMore = viewModel::shouldTriggerLoadMore
            ) {
                items(listData.size) { index ->
                    CommentCard(comment = listData[index])
                }
            }
        }
    }
}
  • BaseNetWorkListView 负责切换“页面级”状态(骨架/空态/错误/成功),内部成功态交给 RefreshLayout
  • RefreshLayout 结合 LoadMoreState,内置下拉刷新、上拉加载更多,并通过 shouldTriggerLoadMore 判断触发时机;需要网格时可设置 isGrid = true 并提供 gridContent
  • 列表项遵循 designsystem 的尺寸/间距常量(例如 Size.ktSpacer.kt),保持布局一致。

通过上述模式,分页列表只需关心“接口怎么请求”和“成功后如何渲染”,刷新/加载更多/错误状态交由基类和通用组件处理,从而快速实现统一体验的列表页面。 结束后若需要响应其它模块的刷新指令,直接调用 observeRefreshState() 与导航结果集成即可。

API 参考

BaseNetWorkListViewModel<T>

名称类型默认值说明
currentPageInt1当前请求页,onLoadMore() 会自增
pageSizeInt10每页条数,用于拼接请求参数
uiStateStateFlow<BaseNetWorkListUiState>Loading控制骨架/空态/错误/成功
listDataStateFlow<List<T>>emptyList()已加载的聚合数据
loadMoreStateStateFlow<LoadMoreState>PullToLoad底部加载提示状态
isRefreshingStateFlow<Boolean>false是否正在下拉刷新
enableMinLoadingTimeBooleanfalse是否启用 240ms 最小加载动画
initLoad()函数-init 中调用以启动首屏请求
requestListData()函数-protected abstract fun requestListData(): Flow<NetworkResponse<NetworkPageData<T>>>
onRefresh()函数-重置页码并重新请求
onLoadMore()函数-触发下一页加载
retryRequest()函数-回到第一页并重新执行请求
shouldTriggerLoadMore(lastIndex, totalCount)函数-判断是否接近底部,常配合 LazyList
observeRefreshState(backStackEntry, key)函数key = RefreshResultKey监听导航返回的刷新信号

BaseNetWorkListView

参数类型默认值说明
uiStateBaseNetWorkListUiState-控制 Loading/Empty/Error/Success
modifierModifierModifier调整尺寸或背景
paddingPaddingValuesPaddingValues()透传 ScaffoldinnerPadding
onRetry() -> Unit{}空态/错误态的重试回调
customLoading@Composable (() -> Unit)?null自定义加载骨架,默认 PageLoading()
customError@Composable (() -> Unit)?null自定义错误占位,默认 EmptyNetwork()
customEmpty@Composable (() -> Unit)?null自定义空数据占位,默认 EmptyData()
content@Composable () -> Unit-成功态内容,通常放入 RefreshLayout

RefreshLayout

参数类型默认值说明
modifierModifierModifier控制整体尺寸/背景
isGridBooleanfalse开启后使用瀑布流布局
listStateLazyListState?null不传则内部 rememberLazyListState()
gridStateLazyStaggeredGridState?nullisGrid=true 时可透传外部状态
isRefreshingBooleanfalse同步头部刷新动画
loadMoreStateLoadMoreStateLoadMoreState.PullToLoad控制底部加载提示
scrollBehaviorTopAppBarScrollBehavior?null需要折叠时传入顶部栏行为
onRefresh() -> Unit{}下拉刷新回调
onLoadMore() -> Unit{}上拉加载回调
shouldTriggerLoadMore(lastIndex: Int, totalCount: Int) -> Boolean{ _, _ -> false }监听可见项并决定何时加载更多
gridContentLazyStaggeredGridScope.() -> Unit{}isGrid=true 时的网格内容
contentLazyListScope.() -> Unit{}列表内容,isGrid=false 下使用