Paging 库会跟踪分页数据的加载请求状态,并通过 LoadState
类将其公开。应用可以向 PagingDataAdapter
注册一个监听器,用于接收有关当前状态的信息并相应地更新界面。这些状态在该适配器中提供,因为它们与界面保持同步。这意味着,如果已对界面应用页面加载,您的监听器就会收到更新。
每个 LoadType
和数据源类型(PagingSource
或 RemoteMediator
)都会获得一个单独的 LoadState
信号。监听器提供的 CombinedLoadStates
对象则会提供来自所有这些信号的加载状态的信息。您可以利用此详细信息向用户显示相应的加载指示器。
加载状态
Paging 库通过 LoadState
对象公开要在界面中使用的加载状态。LoadState
对象根据当前的加载状态采用以下三种形式之一:
- 如果没有正在执行的加载操作且没有错误,那么
LoadState
为LoadState.NotLoading
对象。此子类还包含endOfPaginationReached
属性,用于指示是否已到达分页结束处。 - 如果有正在执行的加载操作,那么
LoadState
为LoadState.Loading
对象。 - 如果出现错误,那么
LoadState
为LoadState.Error
对象。
可通过两种方法在界面中使用 LoadState
:使用监听器,或使用特殊的列表适配器直接在 RecyclerView
列表中显示加载状态。
使用监听器获取加载状态
为了获取加载状态以用于界面中的一般用途,请使用 loadStateFlow
流或 PagingDataAdapter
提供的 addLoadStateListener()
方法。这些机制可用于获取 CombinedLoadStates
对象,该对象包含有关每个加载类型的 LoadState
行为的信息。
在以下示例中,PagingDataAdapter
根据刷新加载的当前状态显示不同的界面组件:
// 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
}
}
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);
});
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()
函数。此函数应根据加载状态参数切换视图可见性:
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
}
}
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);
}
}
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 的一个实例,并绑定关联的加载状态。
// 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)
}
// 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);
}
}
// 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()
方法:
pagingAdapter
.withLoadStateHeaderAndFooter(
header = ExampleLoadStateAdapter(adapter::retry),
footer = ExampleLoadStateAdapter(adapter::retry)
)
pagingAdapter
.withLoadStateHeaderAndFooter(
new ExampleLoadStateAdapter(pagingAdapter::retry),
new ExampleLoadStateAdapter(pagingAdapter::retry));
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
的参考文档。
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
}
}
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;
});
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
加载状态一定会与界面更新保持同步。由于 refresh
、append
和 prepend
属性可能会从 PagingSource
获取加载状态,也可能会从 RemoteMediator
获取加载状态,因此不能保证它们一定会与界面更新保持同步。这可能会导致以下界面问题:在任何新数据添加到界面中之前,加载似乎就已完成。
因此,便利访问函数适用于在页眉或页脚中显示加载状态,但在其他用例中,您可能需要从 PagingSource
或 RemoteMediator
中明确获取加载状态。为此,CombinedLoadStates
提供了 source
和 mediator
属性。这两个属性会各公开一个 LoadStates
对象,分别包含 PagingSource
或 RemoteMediator
的 LoadState
对象:
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
}
}
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;
});
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
状态以确认刷新已完成。您必须过滤掉除所需信号外的所有信号:
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) }
}
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);
});
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 使此类操作成为可能。应用可以指定需要的加载事件,并在满足相应条件时处理新数据。
目前没有任何推荐文档页面。
请尝试登录您的 Google 账号。