管理和顯示載入狀態

Paging 程式庫會追蹤分頁資料的載入要求狀態,並透過 LoadState 類別公開資料。應用程式可透過 PagingDataAdapter 註冊事件監聽器,以便接收目前狀態的相關資訊,並據此更新使用者介面。這些狀態是由轉接程式提供,因為其與使用者介面同步。這表示事件監聽器在使用者介面套用載入網頁時會收到更新。

針對每個 LoadType 和資料來源類型 (PagingSourceRemoteMediator) 會提供獨立的 LoadState 信號。此事件監聽器提供的 CombinedLoadStates 物件會提供所有信號的載入狀態資訊。您可以使用此詳細資訊,向使用者顯示適用的載入指標。

載入狀態

Paging 程式庫會透過 LoadState 物件公開使用者介面中的載入狀態。根據目前的載入狀態,LoadState 物件採用下列其中一種格式:

在您的使用者介面中有兩種使用 LoadState 的方法:使用事件監聽器,或使用特殊的清單轉接程式,直接在 RecyclerView 清單顯示載入狀態。

透過事件監聽器存取載入狀態

如要在使用者介面中查看一般用途的載入狀態,請使用 loadStateFlow 串流或由 PagingDataAdapter 提供的 addLoadStateListener() 方法。這些機制提供 CombinedLoadStates 物件的存取權,其中包含每種載入類型的 LoadState 行為相關資訊。

在下列範例中,PagingDataAdapter 會根據重新整理載入的目前狀態顯示不同的使用者介面元件:

KotlinJavaJava
// 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 的清單轉接程式,可讓您在目前顯示的分頁資料清單中直接顯示載入狀態。此轉接程式可存取清單的目前載入狀態,方便您傳遞給顯示資訊的自訂檢視畫面持有人。

首先,建立檢視畫面持有人類別,以持續參考螢幕上的載入和錯誤檢視畫面。建立可接受 LoadState 做為參數的 bind() 函式。此函式應根據載入狀態參數來切換檢視畫面的瀏覽權限:

KotlinJavaJava
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() 方法。這些方法會建立自訂檢視畫面持有人的執行個體,並繫結相關聯的載入狀態。

KotlinJavaJava
// 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() 方法:

KotlinJavaJava
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()

存取其他載入狀態資訊

PagingDataAdapterCombinedLoadStates 物件會提供 PagingSource 導入的載入狀態資訊,以及 RemoteMediator 導入的載入狀態資訊 (如果有的話)。

為了方便,您可以使用 CombinedLoadStatesrefreshappendprepend 屬性,即可存取適當載入類型的 LoadState 物件。一般來說,這些屬性會延遲 RemoteMediator 導入的載入狀態 (如果有的話);除此之外,其中包含 PagingSource 導入的適當載入狀態。如要進一步瞭解基礎邏輯,請參閱 CombinedLoadStates 的參考說明文件。

KotlinJavaJava
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 載入狀態可以保證與使用者介面更新保持同步。由於 refreshappend 以及 prepend 屬性可能會從 PagingSourceRemoteMediator 取得載入狀態,故無法保證會與使用者介面更新保持同步。因此,在使用者介面新增任何新資料之前,可能會導致載入顯示完成的使用者介面問題。

基於這個原因,便利存取子針對在標頭或頁尾中顯示載入狀態運作良好,但針對其他用途,您可能需要透過 PagingSourceRemoteMediator 特別存取載入狀態。CombinedLoadStates 會提供適用於此目的的 sourcemediator 屬性。這些屬性會各自公開 LoadStates 物件,其中分別包含 PagingSourceRemoteMediatorLoadState 物件:

KotlinJavaJava
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 狀態來確認重新整理作業已完成。您必須篩除所有您不需要的信號:

KotlinJavaJava
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 時才會觸發。這能確保在所有使用者介面更新發生前,遠端重新整理作業已全部完成。

Stream API 可讓您執行此類型作業。您的應用程式可以指定所需的載入事件,並在符合適當條件時處理新資料。

目前沒有任何建議。

建議 Google 帳戶。