Skip to content

分页父类

当页面既需要"下拉刷新"又要"上拉加载更多"时,使用 BaseNetWorkListViewModel + BaseNetWorkListUiState + 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/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(),带重试按钮

刷新与加载更多状态

分页列表额外暴露以下 StateFlow,供 RefreshLayout 消费:

Flow类型含义
isRefreshingStateFlow<Boolean>是否正在下拉刷新
isLoadingMoreStateFlow<Boolean>是否正在上拉加载更多
hasMoreDataStateFlow<Boolean>是否还有更多数据(控制底部提示)

BaseNetWorkListView 负责切换页面级 UI(Loading/Empty/Error/Success),成功态下交给 RefreshLayout 和业务列表渲染;RefreshLayout 内部基于 UltraSwipeRefresh 自动处理下拉刷新和上拉加载更多的手势触发与动画,无需手动计算触发时机。

BaseNetWorkListViewModel 核心能力

  • 分页 Flow:子类实现 requestListData(),返回 Flow<NetworkResponse<NetworkPageData<T>>>NetworkPageData 包含 listpagination,基类会根据 totalsizepage 计算是否还有下一页。
  • 自动状态管理_uiState_listData_isRefreshing_isLoadingMore_hasMoreData 通过 MutableStateFlow 暴露给 UI。
  • 刷新与加载更多onRefresh() 重置页码并触发请求,onLoadMore() 自动递增页码并请求下一页。
  • 最少加载时间:首屏可开启 enableMinLoadingTime(320ms),防止骨架闪烁;刷新和加载更多也内置最短展示时长(500ms),避免状态一闪而过。
  • 结果监听observeRefreshState(key) 基于 NavigationResultKey 监听页面刷新信号,自动去重,无内存泄漏。

常用流程:

  1. 子类在 init { initLoad() } 中启动首屏请求。
  2. loadListData() 根据当前页和状态决定是否展示 Loading、如何处理错误。
  3. 成功时首屏重置列表并调用 setFirstLoadSuccessState(),加载更多则追加到 _listData
  4. UI 通过 listData 渲染列表,isRefreshing / isLoadingMore / hasMoreData 控制 RefreshLayout 的刷新/加载状态。

示例:商品分页列表

以下示例与项目中 NetworkListDemoScreen / NetworkListDemoViewModel 的结构一致。

ViewModel

kotlin
@HiltViewModel
class NetworkListDemoViewModel @Inject constructor(
    private val goodsRepository: GoodsRepository
) : BaseNetWorkListViewModel<Goods>() {

    override val enableMinLoadingTime: Boolean get() = true
    override val pageSize: Int get() = 15

    init {
        initLoad()
    }

    override fun requestListData(): Flow<NetworkResponse<NetworkPageData<Goods>>> {
        return goodsRepository.getGoodsPage(
            GoodsSearchRequest(
                page = currentPage,
                size = pageSize
            )
        )
    }
}

UI 层

kotlin
@Composable
internal fun NetworkListDemoRoute(
    viewModel: NetworkListDemoViewModel = hiltViewModel()
) {
    val uiState by viewModel.uiState.collectAsState()
    val listData by viewModel.listData.collectAsState()
    val isRefreshing by viewModel.isRefreshing.collectAsState()
    val isLoadingMore by viewModel.isLoadingMore.collectAsState()
    val hasMoreData by viewModel.hasMoreData.collectAsState()

    NetworkListDemoScreen(
        uiState = uiState,
        list = listData,
        isRefreshing = isRefreshing,
        isLoadingMore = isLoadingMore,
        hasMoreData = hasMoreData,
        onRefresh = viewModel::onRefresh,
        onLoadMore = viewModel::onLoadMore,
        onRetry = viewModel::retryRequest,
    )
}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
internal fun NetworkListDemoScreen(
    uiState: BaseNetWorkListUiState = BaseNetWorkListUiState.Loading,
    list: List<Goods> = emptyList(),
    isRefreshing: Boolean = false,
    isLoadingMore: Boolean = false,
    hasMoreData: Boolean = false,
    onRefresh: () -> Unit = {},
    onLoadMore: () -> Unit = {},
    onRetry: () -> Unit = {},
) {
    AppScaffold(
        titleText = "Network List Demo",
        onBackClick = { navigateBack() }
    ) {
        BaseNetWorkListView(
            uiState = uiState,
            onRetry = onRetry,
            customLoading = { NetworkListLoadingSkeleton() }
        ) {
            RefreshLayout(
                list = list,
                isRefreshing = isRefreshing,
                isLoadingMore = isLoadingMore,
                hasMoreData = hasMoreData,
                onRefresh = onRefresh,
                onLoadMore = onLoadMore
            ) {
                itemsIndexed(list) { _, item ->
                    GoodsListItem(goods = item)
                }
            }
        }
    }
}
  • BaseNetWorkListView 负责切换"页面级"状态(骨架/空态/错误/成功),内部成功态交给 RefreshLayout
  • RefreshLayout 基于 UltraSwipeRefresh 统一封装下拉刷新与上拉加载更多,内置经典指示器动画与震动反馈;需要网格时可设置 isGrid = true 并提供 gridContent
  • 列表项遵循 designsystem 的尺寸/间距常量,保持布局一致。

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

API 参考

BaseNetWorkListViewModel<T>

名称类型默认值说明
currentPageInt1当前请求页,onLoadMore() 会自增
pageSizeInt10每页条数,子类可重写
uiStateStateFlow<BaseNetWorkListUiState>Loading控制骨架/空态/错误/成功
listDataStateFlow<List<T>>emptyList()已加载的聚合数据
isRefreshingStateFlow<Boolean>false是否正在下拉刷新
isLoadingMoreStateFlow<Boolean>false是否正在上拉加载更多
hasMoreDataStateFlow<Boolean>false是否还有更多数据
enableMinLoadingTimeBooleanfalse是否启用 320ms 最小加载动画(首屏)
initLoad()函数-init 中调用以启动首屏请求
requestListData()函数-protected abstract fun requestListData(): Flow<NetworkResponse<NetworkPageData<T>>>
onRefresh()函数-重置页码并重新请求
onLoadMore()函数-触发下一页加载
retryRequest()函数-回到第一页并重新执行请求
observeRefreshState(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控制整体尺寸/背景
listList<T>-列表数据,用于控制加载更多是否可用
isGridBooleanfalse开启后使用瀑布流布局
listStateLazyListState?null不传则内部 rememberLazyListState()
gridStateLazyStaggeredGridState?nullisGrid=true 时可透传外部状态
isRefreshingBooleanfalse同步头部刷新动画
isLoadingMoreBooleanfalse同步底部加载动画
hasMoreDataBooleanfalse是否还有更多数据,控制底部提示
scrollBehaviorTopAppBarScrollBehavior?null需要折叠时传入顶部栏行为
onRefresh() -> Unit{}下拉刷新回调
onLoadMore() -> Unit{}上拉加载回调
headerIndicator@Composable (UltraSwipeRefreshState) -> UnitClassicRefreshHeader刷新头部指示器
noMoreContent@Composable () -> Unit默认文本提示没有更多数据时的底部指示器
gridContentLazyStaggeredGridScope.() -> Unit{}isGrid=true 时的网格内容
contentLazyListScope.() -> Unit{}列表内容,isGrid=false 下使用