分页库跟踪分页数据的加载请求状态,并通过 LoadState
类将其公开。您的应用可以向 PagingDataAdapter
注册一个监听器,以接收有关当前状态的信息并相应地更新 UI。这些状态由适配器提供,因为它们与 UI 同步。这意味着您的监听器在页面加载已应用于 UI 时接收更新。
为每个 LoadType
和数据源类型(PagingSource
或 RemoteMediator
)提供了单独的 LoadState
信号。监听器提供的 CombinedLoadStates
对象提供有关来自所有这些信号的加载状态的信息。您可以使用此详细的信息向用户显示相应的加载指示器。
加载状态
分页库通过 LoadState
对象公开加载状态,以便在 UI 中使用。根据当前加载状态,LoadState
对象采用三种形式之一
- 如果没有活动的加载操作且没有错误,则
LoadState
是一个LoadState.NotLoading
对象。此子类还包含endOfPaginationReached
属性,该属性指示是否已到达分页的末尾。 - 如果存在活动的加载操作,则
LoadState
是一个LoadState.Loading
对象。 - 如果存在错误,则
LoadState
是一个LoadState.Error
对象。
在你的 UI 中使用 LoadState
有两种方法:使用监听器或使用特殊的列表适配器直接在 RecyclerView
列表中展示加载状态。
使用监听器访问加载状态
要获取加载状态以供在你的 UI 中通用使用,请使用你的 PagingDataAdapter
提供的 loadStateFlow
流或 addLoadStateListener()
方法。这些机制提供对 CombinedLoadStates
对象的访问,该对象包含有关每种加载类型 LoadState
行为的信息。
在以下示例中,PagingDataAdapter
根据刷新加载的当前状态显示不同的 UI 组件
Kotlin
// Activities can use lifecycleScope directly, but Fragments should instead use // viewLifecycleOwner.lifecycleScope. lifecycleScope.launch { pagingAdapter.loadStateFlow.collectLatest { loadStates -> progressBar.isVisible = loadStates.refresh is LoadState.Loading retry.isVisible = loadState.refresh !is LoadState.Loading errorMsg.isVisible = loadState.refresh is LoadState.Error } }
Java
pagingAdapter.addLoadStateListener(loadStates -> { progressBar.setVisibility(loadStates.refresh instanceof LoadState.Loading ? View.VISIBLE : View.GONE); retry.setVisibility(loadStates.refresh instanceof LoadState.Loading ? View.GONE : View.VISIBLE); errorMsg.setVisibility(loadStates.refresh instanceof LoadState.Error ? View.VISIBLE : View.GONE); });
Java
pagingAdapter.addLoadStateListener(loadStates -> { progressBar.setVisibility(loadStates.refresh instanceof LoadState.Loading ? View.VISIBLE : View.GONE); retry.setVisibility(loadStates.refresh instanceof LoadState.Loading ? View.GONE : View.VISIBLE); errorMsg.setVisibility(loadStates.refresh instanceof LoadState.Error ? View.VISIBLE : View.GONE); });
有关 CombinedLoadStates
的更多信息,请参阅 访问其他加载状态信息。
使用适配器展示加载状态
Paging 库提供另一个名为 LoadStateAdapter
的列表适配器,用于直接在分页数据的显示列表中展示加载状态。此适配器提供对列表的当前加载状态的访问,你可以将其传递给显示信息的自定义视图持有者。
首先,创建一个视图持有者类,该类在屏幕上保存对加载视图和错误视图的引用。创建一个接受 LoadState
作为参数的 bind()
函数。此函数应根据加载状态参数切换视图可见性
Kotlin
class LoadStateViewHolder( parent: ViewGroup, retry: () -> Unit ) : RecyclerView.ViewHolder( LayoutInflater.from(parent.context) .inflate(R.layout.load_state_item, parent, false) ) { private val binding = LoadStateItemBinding.bind(itemView) private val progressBar: ProgressBar = binding.progressBar private val errorMsg: TextView = binding.errorMsg private val retry: Button = binding.retryButton .also { it.setOnClickListener { retry() } } fun bind(loadState: LoadState) { if (loadState is LoadState.Error) { errorMsg.text = loadState.error.localizedMessage } progressBar.isVisible = loadState is LoadState.Loading retry.isVisible = loadState is LoadState.Error errorMsg.isVisible = loadState is LoadState.Error } }
Java
class LoadStateViewHolder extends RecyclerView.ViewHolder { private ProgressBar mProgressBar; private TextView mErrorMsg; private Button mRetry; LoadStateViewHolder( @NonNull ViewGroup parent, @NonNull View.OnClickListener retryCallback) { super(LayoutInflater.from(parent.getContext()) .inflate(R.layout.load_state_item, parent, false)); LoadStateItemBinding binding = LoadStateItemBinding.bind(itemView); mProgressBar = binding.progressBar; mErrorMsg = binding.errorMsg; mRetry = binding.retryButton; } public void bind(LoadState loadState) { if (loadState instanceof LoadState.Error) { LoadState.Error loadStateError = (LoadState.Error) loadState; mErrorMsg.setText(loadStateError.getError().getLocalizedMessage()); } mProgressBar.setVisibility(loadState instanceof LoadState.Loading ? View.VISIBLE : View.GONE); mRetry.setVisibility(loadState instanceof LoadState.Error ? View.VISIBLE : View.GONE); mErrorMsg.setVisibility(loadState instanceof LoadState.Error ? View.VISIBLE : View.GONE); } }
Java
class LoadStateViewHolder extends RecyclerView.ViewHolder { private ProgressBar mProgressBar; private TextView mErrorMsg; private Button mRetry; LoadStateViewHolder( @NonNull ViewGroup parent, @NonNull View.OnClickListener retryCallback) { super(LayoutInflater.from(parent.getContext()) .inflate(R.layout.load_state_item, parent, false)); LoadStateItemBinding binding = LoadStateItemBinding.bind(itemView); mProgressBar = binding.progressBar; mErrorMsg = binding.errorMsg; mRetry = binding.retryButton; } public void bind(LoadState loadState) { if (loadState instanceof LoadState.Error) { LoadState.Error loadStateError = (LoadState.Error) loadState; mErrorMsg.setText(loadStateError.getError().getLocalizedMessage()); } mProgressBar.setVisibility(loadState instanceof LoadState.Loading ? View.VISIBLE : View.GONE); mRetry.setVisibility(loadState instanceof LoadState.Error ? View.VISIBLE : View.GONE); mErrorMsg.setVisibility(loadState instanceof LoadState.Error ? View.VISIBLE : View.GONE); } }
接下来,创建一个实现 LoadStateAdapter
的类,并定义 onCreateViewHolder()
和 onBindViewHolder()
方法。这些方法创建自定义视图持有者的实例并将关联的加载状态绑定到该实例。
Kotlin
// Adapter that displays a loading spinner when // state is LoadState.Loading, and an error message and retry // button when state is LoadState.Error. class ExampleLoadStateAdapter( private val retry: () -> Unit ) : LoadStateAdapter<LoadStateViewHolder>() { override fun onCreateViewHolder( parent: ViewGroup, loadState: LoadState ) = LoadStateViewHolder(parent, retry) override fun onBindViewHolder( holder: LoadStateViewHolder, loadState: LoadState ) = holder.bind(loadState) }
Java
// Adapter that displays a loading spinner when // state is LoadState.Loading, and an error message and retry // button when state is LoadState.Error. class ExampleLoadStateAdapter extends LoadStateAdapter<LoadStateViewHolder> { private View.OnClickListener mRetryCallback; ExampleLoadStateAdapter(View.OnClickListener retryCallback) { mRetryCallback = retryCallback; } @NotNull @Override public LoadStateViewHolder onCreateViewHolder(@NotNull ViewGroup parent, @NotNull LoadState loadState) { return new LoadStateViewHolder(parent, mRetryCallback); } @Override public void onBindViewHolder(@NotNull LoadStateViewHolder holder, @NotNull LoadState loadState) { holder.bind(loadState); } }
Java
// Adapter that displays a loading spinner when // state is LoadState.Loading, and an error message and retry // button when state is LoadState.Error. class ExampleLoadStateAdapter extends LoadStateAdapter<LoadStateViewHolder> { private View.OnClickListener mRetryCallback; ExampleLoadStateAdapter(View.OnClickListener retryCallback) { mRetryCallback = retryCallback; } @NotNull @Override public LoadStateViewHolder onCreateViewHolder(@NotNull ViewGroup parent, @NotNull LoadState loadState) { return new LoadStateViewHolder(parent, mRetryCallback); } @Override public void onBindViewHolder(@NotNull LoadStateViewHolder holder, @NotNull LoadState loadState) { holder.bind(loadState); } }
将加载状态显示为标题或页脚
要将加载进度显示在标题和页脚中,请从你的 PagingDataAdapter
对象调用 withLoadStateHeaderAndFooter()
方法
Kotlin
pagingAdapter .withLoadStateHeaderAndFooter( header = ExampleLoadStateAdapter(adapter::retry), footer = ExampleLoadStateAdapter(adapter::retry) )
Java
pagingAdapter .withLoadStateHeaderAndFooter( new ExampleLoadStateAdapter(pagingAdapter::retry), new ExampleLoadStateAdapter(pagingAdapter::retry));
Java
pagingAdapter .withLoadStateHeaderAndFooter( new ExampleLoadStateAdapter(pagingAdapter::retry), new ExampleLoadStateAdapter(pagingAdapter::retry));
如果你希望 RecyclerView
列表仅在标题中或仅在页脚中显示加载状态,则可以改用调用 withLoadStateHeader()
或 withLoadStateFooter()
。
访问其他加载状态信息
来自 PagingDataAdapter
的 CombinedLoadStates
对象提供有关你的 PagingSource
实现以及你的 RemoteMediator
实现的加载状态的信息(如果存在)。
为了方便起见,你可以使用来自 CombinedLoadStates
的 refresh
、append
和 prepend
属性访问相应加载类型的 LoadState
对象。这些属性通常默认使用来自 RemoteMediator
实现的加载状态(如果存在);否则,它们将包含来自 PagingSource
实现的相应加载状态。有关基础逻辑的更多详细信息,请参阅 CombinedLoadStates
的参考文档。
Kotlin
lifecycleScope.launch { pagingAdapter.loadStateFlow.collectLatest { loadStates -> // Observe refresh load state from RemoteMediator if present, or // from PagingSource otherwise. refreshLoadState: LoadState = loadStates.refresh // Observe prepend load state from RemoteMediator if present, or // from PagingSource otherwise. prependLoadState: LoadState = loadStates.prepend // Observe append load state from RemoteMediator if present, or // from PagingSource otherwise. appendLoadState: LoadState = loadStates.append } }
Java
pagingAdapter.addLoadStateListener(loadStates -> { // Observe refresh load state from RemoteMediator if present, or // from PagingSource otherwise. LoadState refreshLoadState = loadStates.refresh; // Observe prepend load state from RemoteMediator if present, or // from PagingSource otherwise. LoadState prependLoadState = loadStates.prepend; // Observe append load state from RemoteMediator if present, or // from PagingSource otherwise. LoadState appendLoadState = loadStates.append; });
Java
pagingAdapter.addLoadStateListener(loadStates -> { // Observe refresh load state from RemoteMediator if present, or // from PagingSource otherwise. LoadState refreshLoadState = loadStates.refresh; // Observe prepend load state from RemoteMediator if present, or // from PagingSource otherwise. LoadState prependLoadState = loadStates.prepend; // Observe append load state from RemoteMediator if present, or // from PagingSource otherwise. LoadState appendLoadState = loadStates.append; });
但是,重要的是要记住,只有 PagingSource
加载状态保证与 UI 更新同步。因为 refresh
、append
和 prepend
属性可能分别从 PagingSource
或 RemoteMediator
获取加载状态,所以它们不保证与 UI 更新同步。这可能会导致 UI 问题,例如加载似乎在任何新数据添加到 UI 之前就已完成。
因此,便捷访问器非常适合在标题或页脚中显示加载状态,但在其他用例中,你可能需要专门访问来自 PagingSource
或 RemoteMediator
的加载状态。 CombinedLoadStates
提供了 source
和 mediator
属性来实现此目的。这些属性分别公开一个 LoadStates
对象,其中包含 PagingSource
或 RemoteMediator
的 LoadState
对象
Kotlin
lifecycleScope.launch { pagingAdapter.loadStateFlow.collectLatest { loadStates -> // Directly access the RemoteMediator refresh load state. mediatorRefreshLoadState: LoadState? = loadStates.mediator.refresh // Directly access the RemoteMediator append load state. mediatorAppendLoadState: LoadState? = loadStates.mediator.append // Directly access the RemoteMediator prepend load state. mediatorPrependLoadState: LoadState? = loadStates.mediator.prepend // Directly access the PagingSource refresh load state. sourceRefreshLoadState: LoadState = loadStates.source.refresh // Directly access the PagingSource append load state. sourceAppendLoadState: LoadState = loadStates.source.append // Directly access the PagingSource prepend load state. sourcePrependLoadState: LoadState = loadStates.source.prepend } }
Java
pagingAdapter.addLoadStateListener(loadStates -> { // Directly access the RemoteMediator refresh load state. LoadState mediatorRefreshLoadState = loadStates.mediator.refresh; // Directly access the RemoteMediator append load state. LoadState mediatorAppendLoadState = loadStates.mediator.append; // Directly access the RemoteMediator prepend load state. LoadState mediatorPrependLoadState = loadStates.mediator.prepend; // Directly access the PagingSource refresh load state. LoadState sourceRefreshLoadState = loadStates.source.refresh; // Directly access the PagingSource append load state. LoadState sourceAppendLoadState = loadStates.source.append; // Directly access the PagingSource prepend load state. LoadState sourcePrependLoadState = loadStates.source.prepend; });
Java
pagingAdapter.addLoadStateListener(loadStates -> { // Directly access the RemoteMediator refresh load state. LoadState mediatorRefreshLoadState = loadStates.mediator.refresh; // Directly access the RemoteMediator append load state. LoadState mediatorAppendLoadState = loadStates.mediator.append; // Directly access the RemoteMediator prepend load state. LoadState mediatorPrependLoadState = loadStates.mediator.prepend; // Directly access the PagingSource refresh load state. LoadState sourceRefreshLoadState = loadStates.source.refresh; // Directly access the PagingSource append load state. LoadState sourceAppendLoadState = loadStates.source.append; // Directly access the PagingSource prepend load state. LoadState sourcePrependLoadState = loadStates.source.prepend; });
对 LoadState 进行链式操作
由于 CombinedLoadStates
对象提供对所有加载状态更改的访问,因此根据特定事件过滤加载状态流非常重要。这确保你在适当的时间更新 UI,以避免卡顿和不必要的 UI 更新。
例如,假设你想要显示一个空视图,但只有在初始数据加载完成后才显示。此用例要求你验证数据刷新加载已启动,然后等待 NotLoading
状态以确认刷新已完成。你必须过滤掉除你需要的内容之外的所有信号
Kotlin
lifecycleScope.launchWhenCreated { adapter.loadStateFlow // Only emit when REFRESH LoadState for RemoteMediator changes. .distinctUntilChangedBy { it.refresh } // Only react to cases where REFRESH completes, such as NotLoading. .filter { it.refresh is LoadState.NotLoading } // Scroll to top is synchronous with UI updates, even if remote load was // triggered. .collect { binding.list.scrollToPosition(0) } }
Java
PublishSubject<CombinedLoadStates> subject = PublishSubject.create(); Disposable disposable = subject.distinctUntilChanged(CombinedLoadStates::getRefresh) .filter( combinedLoadStates -> combinedLoadStates.getRefresh() instanceof LoadState.NotLoading) .subscribe(combinedLoadStates -> binding.list.scrollToPosition(0)); pagingAdapter.addLoadStateListener(loadStates -> { subject.onNext(loadStates); });
Java
LiveData<CombinedLoadStates> liveData = new MutableLiveData<>(); LiveData<LoadState> refreshLiveData = Transformations.map(liveData, CombinedLoadStates::getRefresh); LiveData<LoadState> distinctLiveData = Transformations.distinctUntilChanged(refreshLiveData); distinctLiveData.observeForever(loadState -> { if (loadState instanceof LoadState.NotLoading) { binding.list.scrollToPosition(0); } });
此示例将等待刷新加载状态更新,但仅在状态为 NotLoading
时触发。这确保远程刷新完全完成,然后再进行任何 UI 更新。
流式 API 使这种类型的操作成为可能。你的应用程序可以指定所需的加载事件,并在满足适当条件时处理新数据。
为你推荐
- 注意:链接文本在 JavaScript 关闭时显示
- 加载和显示分页数据
- 从网络和数据库分页
- Paging 库概述