管理和显示加载状态

Paging 库会跟踪分页数据的加载请求状态,并通过 LoadState 类将其公开。应用可以向 PagingDataAdapter 注册一个监听器,用于接收有关当前状态的信息并相应地更新界面。这些状态在该适配器中提供,因为它们与界面保持同步。这意味着,如果已对界面应用页面加载,您的监听器就会收到更新。

每个 LoadType 和数据源类型(PagingSourceRemoteMediator)都会获得一个单独的 LoadState 信号。监听器提供的 CombinedLoadStates 对象则会提供来自所有这些信号的加载状态的信息。您可以利用此详细信息向用户显示相应的加载指示器。

加载状态

Paging 库通过 LoadState 对象公开要在界面中使用的加载状态。LoadState 对象根据当前的加载状态采用以下三种形式之一:

可通过两种方法在界面中使用 LoadState:使用监听器,或使用特殊的列表适配器直接在 RecyclerView 列表中显示加载状态。

使用监听器获取加载状态

为了获取加载状态以用于界面中的一般用途,请使用 loadStateFlow 流或 PagingDataAdapter 提供的 addLoadStateListener() 方法。这些机制可用于获取 CombinedLoadStates 对象,该对象包含有关每个加载类型的 LoadState 行为的信息。

在以下示例中,PagingDataAdapter 根据刷新加载的当前状态显示不同的界面组件:

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 的列表适配器,用于直接在显示的分页数据列表中呈现加载状态。您可以通过此适配器获取列表的当前加载状态,将该状态传递给显示该信息的自定义 ViewHolder。

首先,创建一个 ViewHolder 类,用于保留对屏幕上的加载视图和错误视图的引用。创建一个接受 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() 方法:这些方法会创建自定义 ViewHolder 的一个实例,并绑定关联的加载状态。

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 中的 refreshappendprepend 属性访问相应加载类型的 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 加载状态一定会与界面更新保持同步。由于 refreshappendprepend 属性可能会从 PagingSource 获取加载状态,也可能会从 RemoteMediator 获取加载状态,因此不能保证它们一定会与界面更新保持同步。这可能会导致以下界面问题:在任何新数据添加到界面中之前,加载似乎就已完成。

因此,便利访问函数适用于在页眉或页脚中显示加载状态,但在其他用例中,您可能需要从 PagingSourceRemoteMediator 中明确获取加载状态。为此,CombinedLoadStates 提供了 sourcemediator 属性。这两个属性会各公开一个 LoadStates 对象,分别包含 PagingSourceRemoteMediatorLoadState 对象:

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 对象可以获取加载状态的所有更改,因此必须根据特定事件过滤加载状态流。这样可以确保在适当的时间更新界面,以避免卡顿和不必要的界面更新。

例如,假设您想显示一个空视图,但仅在初始数据加载完成后显示。此用例需要验证数据刷新加载是否已开始,然后等待 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 时触发。这样可以确保在发生任何界面更新之前,远程刷新已完全完成。

流 API 使此类操作成为可能。应用可以指定需要的加载事件,并在满足相应条件时处理新数据。