天天看點

Android Jetpack元件(七)Paging

Android Jetpack元件系列文章:

Android Jetpack元件(一)LifeCycle

Android Jetpack元件(二)Navigation

Android Jetpack元件(三)ViewModel

Android Jetpack元件(四)LiveData

Android Jetpack元件(五)Room

Android JetPack元件(六)DataBinding

Android Jetpack元件(七)Paging

Android Jetpack元件(八)WorkManager

首語

我們經常以清單的形式加載大量的資料,這些資料一次性加載處理,必須消耗大量的時間和資料流暢,是以便有了分頁加載。應用開發過程中分頁加載時很普遍的需求,它能節省資料流量,提升應用的性能。

Google為了友善開發者完成分頁加載而推出了分頁元件—Paging。為幾種常見的分頁機制提供了統一的解決方案。

優勢

  • 分頁資料的記憶體中緩存。該功能可確定您的應用在處理分頁資料時高效利用系統資源。
  • 内置的請求重複資訊删除功能,可確定您的應用高效利用網絡帶寬和系統資源。
  • 可配置的

    RecyclerView

    擴充卡,會在使用者滾動到已加載資料的末尾時自動請求資料。
  • 對Kotlin協程和Flow以及LiveData和RxJava的一流支援。
  • 内置對錯誤處理功能的支援,包括重新整理和重試功能。

資料架構

Paging支援三種資料架構類型。

  • 網絡

    對網絡資料進行分頁加載是最常見的需求。API接口通常不太一樣,Paging提供了三種不同的方案,應對不同的分頁機制。Paging不提供任務錯誤處理功能,發生錯誤後可重試網絡請求。

  • 資料庫

    資料庫進行分頁加載和網絡類似,推薦使用Room資料庫修改和插入資料。

  • 網絡+資料庫

    出于使用者體驗的考慮,我們會利用資料庫對網絡資料進行緩存,這時需要處理網絡和資料庫兩個資料源,但是這樣會讓業務邏輯複雜,通常隻采用單一資料源作為解決方案,從網絡擷取資料,直接緩存進資料庫,清單直接從資料庫中擷取資料。

依賴

dependencies {
      def paging_version = "2.1.2"

      implementation "androidx.paging:paging-runtime:$paging_version" 
      // optional - RxJava support
      implementation "androidx.paging:paging-rxjava2:$paging_version" // For Kotlin use paging-rxjava2-ktx
  }
           

核心類

Paging的工作原理主要涉及三個類。

  • PagedListAdapter

    RecyclerView.Adapter

    基類,用于在

    RecyclerView

    顯示來自

    PagedList

    的分頁資料。
  • PagedList

    PagedList

    負責通知

    DataSource

    何時擷取資料,如加載第一頁、最後一頁及加載數量等。從DataSource擷取的資料将存儲在

    PagedList

    中。
  • DataSource

    DataSource

    中執行具體的資料載入工作,資料載入需要在工作線程中進行。

DataSource

根據分頁機制的不同,Paing為我們提供了三種DataSource。

  • PositionalDataSource

    适用于可通過任意位置加載資料,且目标資料源數量固定的情況。

  • PageKeyedDataSource

    适合資料源以“頁”的方式進行請求的情況。如擷取資料攜帶

    page

    pageSize

    時。
  • ItemKeyedDataSource

    适用于當目标資料的下一頁需要依賴上一頁資料中的最後一個對象中的某個字段作為key的情況,如評論資料的接口攜帶參數

    since

    pageSize

使用

我們對三種DataSource進行分别使用來展示分頁加載的效果。

PositionalDataSource

我們從網絡擷取資料,選取玩Android 開放API中的檢視某個公衆号曆史資料接口來擷取資料。

https://wanandroid.com/wxarticle/list/408/1/json

方法:GET

參數:

公衆号 ID:拼接在 url 中,eg:405

公衆号頁碼:拼接在url 中,eg:1

首先建立DataSource,繼承自PositionalDataSource,擷取網絡資料。

public class ArticlesDataSource extends PositionalDataSource<Article> {

    public static final int PAGE_SIZE=20;

    /**
     * 頁面首次加載資料調用,在方法裡調用接口,并通過callback.onResult()傳回給PagedList
     */
    @Override
    public void loadInitial(@NonNull LoadInitialParams params, @NonNull LoadInitialCallback<Article> callback) {
        int start = 0;
        //網絡請求采用Retrofit
        RetrofitClient.getInstance().getApi().articlesData(start).enqueue(new Callback<Articles>() {
            @Override
            public void onResponse(@NonNull Call<Articles> call, @NonNull Response<Articles> response) {
                if (response.body() != null) {

                    callback.onResult(response.body().data.datas, start, response.body().data.total);
                }
            }

            @Override
            public void onFailure(@NonNull Call<Articles> call, @NonNull Throwable t) {
                Log.e("yhj", "loadInitial: "+t.getMessage());
            }
        });
    }

    /**
     *負責每一頁資料的加載,當第一頁資料加載完成後,加載下一頁資料的工作,通過callback.onResult()回傳PagedList
     */
    @Override
    public void loadRange(@NonNull LoadRangeParams params, @NonNull LoadRangeCallback<Article> callback) {
    
        RetrofitClient.getInstance().getApi().articlesData(params.startPosition/PAGE_SIZE).enqueue(new Callback<Articles>() {
            @Override
            public void onResponse(@NonNull Call<Articles> call, @NonNull  Response<Articles> response) {
                if(response.body()!=null){
                    callback.onResult(response.body().data.datas);
                }
            }

            @Override
            public void onFailure(@NonNull Call<Articles> call, Throwable t) {
                Log.e("yhj", "loadRange: "+t.getMessage());

            }
        });
    }
}
           

接下來建立一個Factory類,負責建立DataSource,并使用LiveData包裝DataSource,暴露給ViewModel。

public class ArticlesDataSourceFactory extends DataSource.Factory<Integer, Article> {

    private MutableLiveData<ArticlesDataSource> liveDataSource = new MutableLiveData<>();

    @NonNull
    @Override
    public DataSource<Integer, Article> create() {

        ArticlesDataSource articlesDataSource = new ArticlesDataSource();
        liveDataSource.postValue(articlesDataSource);
        return articlesDataSource;
    }
}
           

在ViewModel中通過

LivePagedListBuilder

建立和配置

PagedList

,并使用LiveData包裝PagedList,暴露給

Activity

public class ArticlesViewModel extends ViewModel {

    public LiveData<PagedList<Article>> articlePagedList;

    PagedList.Config config = new PagedList.Config.Builder()
            //設定控件占位,預留位置。預設為true,如果設定為true,需要在DataSource中callback.onResult()的totalCount設定總數,否則會崩潰
            //使用此方法資料不宜太大,否則會消耗性能。
            .setEnablePlaceholders(true)
            //設定每頁的大小
            .setPageSize(ArticlesDataSource.PAGE_SIZE)
            //設定距離底部多少條資料加載下一頁資料
            .setPrefetchDistance(3)
            //設定首次加載的資料量,要求是PageSize的整數倍,預設為3倍。
            .setInitialLoadSizeHint(ArticlesDataSource.PAGE_SIZE * 4)
            //設定PagedList承受的最大數量,超過會有異常
            .setMaxSize(65536 * ArticlesDataSource.PAGE_SIZE)
            .build();


    public ArticlesViewModel() {
        articlePagedList = new LivePagedListBuilder<>(new ArticlesDataSourceFactory(), config).build();
    }
}
           

清單資料展示通過Adapter來進行展示。

public class ArticlesAdapter extends PagedListAdapter<Article, ArticlesAdapter.ArticlesViewHolder> {
    private Context context;
    private ArticleItemBinding binding;

    public ArticlesAdapter(Context context) {
        super(DIFF_CALLBACK);
        this.context = context;
    }

    /**
     * 用于計算清單中兩個非空項之間的差異的回調。
     * 之前資料更新了,需要通過notifyDataSetChanged()通知整個RecyclerView,效率不高
     * 使用DiffUtil隻會更新需要更新的Item,不需要重新整理整個RecyclerView,并且可以在Item删除的時候加上動畫效果
     * 原理使用的Myers差分算法,平常使用的版本控制工具git就是通過這種算法來比較檔案差異
     */
    private static DiffUtil.ItemCallback<Article> DIFF_CALLBACK = new DiffUtil.ItemCallback<Article>() {
        /**
         * 當DiffUtil想要檢測兩個對象是否代表同一個Item時,調用該方法進行判斷
         * */
        @Override
        public boolean areItemsTheSame(@NonNull Article oldItem, @NonNull Article newItem) {
            return oldItem.id == newItem.id;
        }

        /**
         * 當DiffUtil想要檢測兩個Item是否有一樣的資料時,調用該方法進行判斷
         * 内容如果更新了,展示給使用者看的東西可能也需要更新,是以需要這個判斷
         * */
        @SuppressLint("DiffUtilEquals")
        @Override
        public boolean areContentsTheSame(@NonNull Article oldItem, @NonNull Article newItem) {
            return oldItem.equals(newItem);
        }
    };

    @NonNull
    @Override
    public ArticlesViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        ArticleItemBinding binding = DataBindingUtil.inflate(LayoutInflater.from(context), R.layout.article_item, parent, false);
        return new ArticlesViewHolder(binding.getRoot());
    }

    @Override
    public void onBindViewHolder(@NonNull ArticlesViewHolder holder, int position) {

        binding = DataBindingUtil.getBinding(holder.itemView);
        Article article = getItem(position);
        if (article != null) {
            //資料綁定
            binding.setArticle(article);
        }
    }

    static class ArticlesViewHolder extends RecyclerView.ViewHolder {

        public ArticlesViewHolder(@NonNull View itemView) {
            super(itemView);
        }
    }
}
           

在Activity中将

RecycleView

和Adapter綁定,當資料發生變化時,通過LiveData傳遞,最後通過

Adapter.submitList()

重新整理資料。

ArticlesAdapter articlesAdapter = new ArticlesAdapter(this);
binding.recyclerView.setHasFixedSize(true);
ArticlesViewModel articlesViewModel = new ViewModelProvider(this).get(ArticlesViewModel.class);
friendsViewModel.articlePagedList.observe(this, new Observer<PagedList<Articles.DataBean.Article>>() {
       @Override
       public void onChanged(PagedList<Articles.DataBean.Article> friends) {
           articlesAdapter.submitList(friends);
       }
});
binding.recyclerView.setAdapter(articlesAdapter);
           

PageKeyedDataSource

仍然采用上面的接口,建立DataSource,繼承自

PageKeyedDataSource

,擷取資料。

public class ArticlePageDataSource extends PageKeyedDataSource<Integer, Articles.DataBean.Article> {

    public static final int FIRST_PAGE = 1;
    public static final int PAGE_SIZE = 20;

    @Override
    public void loadInitial(@NonNull LoadInitialParams<Integer> params, @NonNull final LoadInitialCallback<Integer, Articles.DataBean.Article> callback) {

        RetrofitClient.getInstance()
                .getApi()
                .articlesData(FIRST_PAGE)
                .enqueue(new Callback<Articles>() {
                    @Override
                    public void onResponse(@NonNull Call<Articles> call, @NonNull Response<Articles> response) {
                        if (response.body() != null) {
                            //previousPageKey:前一頁key,目前為第一頁,設定為null
                            //nextPageKey:下一頁key,目前頁+1
                            callback.onResult(response.body().data.datas, null, FIRST_PAGE + 1);
                        }
                    }

                    @Override
                    public void onFailure(@NonNull Call<Articles> call, @NonNull Throwable t) {

                    }
                });
    }

    /**
     *加載下一頁的工作,設定nextKey通過LoadParams傳遞過來
     * 設定下一頁的key,判斷是否有更多資料,沒有置null
     */
    @Override
    public void loadAfter(@NonNull final LoadParams<Integer> params, @NonNull final LoadCallback<Integer, Articles.DataBean.Article> callback) {
        RetrofitClient.getInstance()
                .getApi()
                .articlesData(params.key)
                .enqueue(new Callback<Articles>() {
                    @Override
                    public void onResponse(@NonNull Call<Articles> call, @NonNull Response<Articles> response) {
                        if (response.body() != null) {
                            Integer nextKey = null;
                            if (params.key * PAGE_SIZE < response.body().data.total) {
                                nextKey = params.key + 1;
                            }
                            callback.onResult(response.body().data.datas, nextKey);
                            Log.e("loadAfter()", " response:" + nextKey);
                        }
                    }

                    @Override
                    public void onFailure(@NonNull Call<Articles> call, @NonNull Throwable t) {

                    }
                });
    }

    @Override
    public void loadBefore(@NonNull final LoadParams<Integer> params, @NonNull final LoadCallback<Integer, Articles.DataBean.Article> callback) {
        Log.e("loadBefore()", String.valueOf(params.key));
    }
}
           

剩下的步驟和使用

PositionalDataSource

的一緻。

ItemKeyedDataSource

仍然使用上面的接口,建立DataSource,繼承自

ItemKeyedDataSource

,擷取資料。

public class ArticleItemDataSource extends ItemKeyedDataSource<Integer, Articles.DataBean.Article> {

    public static final int PAGE_SIZE = 20;

    @Override
    public void loadInitial(@NonNull LoadInitialParams<Integer> params, @NonNull final LoadInitialCallback<Articles.DataBean.Article> callback) {

        int since = 1;

        RetrofitClient.getInstance()
                .getApi()
                .articlesData(since)
                .enqueue(new Callback<Articles>() {
                    @Override
                    public void onResponse(@NonNull Call<Articles> call, @NonNull Response<Articles> response) {

                        if (response.body() != null) {

                            Log.e("loadInitial()", " response:" + response.body());
                            callback.onResult(response.body().data.datas);
                        }
                    }

                    @Override
                    public void onFailure(@NonNull Call<Articles> call, @NonNull Throwable t) {

                    }
                });
    }

    @Override
    public void loadAfter(@NonNull LoadParams<Integer> params, @NonNull final LoadCallback<Articles.DataBean.Article> callback) {
        RetrofitClient.getInstance()
                .getApi()
                .articlesData(params.key)
                .enqueue(new Callback<Articles>() {
                    @Override
                    public void onResponse(@NonNull Call<Articles> call, @NonNull Response<Articles> response) {
                        if (response.body() != null) {
                            callback.onResult(response.body().data.datas);
                            Log.e("yhj", "onResponse: " + params.key);
                        }
                    }

                    @Override
                    public void onFailure(@NonNull Call<Articles> call, @NonNull Throwable t) {

                    }
                });
    }
	//向上加載,如初始加載第三頁,向上可加載第二頁
    @Override
    public void loadBefore(@NonNull LoadParams<Integer> params, @NonNull LoadCallback<Articles.DataBean.Article> callback) {
    }

    /**
     * 設定作為下一頁請求的key,傳回即可
     */
    @NonNull
    @Override
    public Integer getKey(@NonNull Articles.DataBean.Article article) {

        return article.nextPage;
    }
}
           

剩下的步驟和使用

PositionalDataSource

的一緻。

使用Paging分頁請求網絡資料,各個類的關系如圖所示。

Android Jetpack元件(七)Paging

BoundaryCallback

在實際項目開發中,為了更好的使用者體驗,需要對資料進行緩存。加入緩存,資料源變成了網絡資料和本地資料組成的雙資料源。這會增加應用程式的複雜度,需要處理好資料的時效性及新舊資料的切換等問題。為此,Google在Paging中加入了BoundaryCallback,通過BoundaryCallback實作資料的單一來源架構,簡化應用的複雜度。

使用Room和BoundaryCallback來擷取公衆号曆史資料。

建立資料庫
@Database(entities = {Articles.DataBean.Article.class}, version = 1, exportSchema = false)
public abstract class ArticleDatabase extends RoomDatabase {
    private static final String DATABASE_NAME = "article_db";

    private static ArticleDatabase databaseInstance;

    public static synchronized ArticleDatabase getInstance(Context context) {
        if (databaseInstance == null) {
            databaseInstance = Room
                    .databaseBuilder(context.getApplicationContext(), ArticleDatabase.class, DATABASE_NAME)
                    .build();
        }
        return databaseInstance;
    }

    public abstract ArticleDao articleDao();
}
           
Model
@Entity
        public class Article {

            @ColumnInfo(typeAffinity = ColumnInfo.TEXT)
            public String author;
            @ColumnInfo(typeAffinity = ColumnInfo.TEXT)
            public String desc;
            @PrimaryKey()
            @ColumnInfo(name = "id", typeAffinity = ColumnInfo.INTEGER)
            public int id;
            @ColumnInfo(typeAffinity = ColumnInfo.TEXT)
            public String title;
        }
           
Dao
@Dao
public interface ArticleDao {
    /**
     * 插入資料
     */
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    void insertArticles(List<Article> article);

    /**
     * 清空資料
     */
    @Query("DELETE FROM article")
    void clear();

    /**
     * Room對Paging提供了原生支援,這裡直接傳回DataSource.Factory,
     * 以便LivePagedListBuilder在建立的時候使用。
     */
    @Query("SELECT * FROM article")
    DataSource.Factory<Integer, Article> getArticlesList();
}
           
BoundaryCallback
public class ArticleBoundaryCallback extends PagedList.BoundaryCallback<Article> {
    private String TAG = this.getClass().getName();
    private Application application;

    public ArticleBoundaryCallback(Application application) {
        this.application = application;
    }

    /**
     * 當資料庫為空時,回調該方法,請求第一頁資料
     */
    @Override
    public void onZeroItemsLoaded() {
        super.onZeroItemsLoaded();
        Log.e(TAG, "onZeroItemsLoaded()");
        getTopData();
    }

    /**
     * 加載資料庫資料
     * @param itemAtFront 資料庫中的第一條資料
     */
    @Override
    public void onItemAtFrontLoaded(@NonNull Article itemAtFront) {
        super.onItemAtFrontLoaded(itemAtFront);
        Log.e(TAG, "onItemAtFrontLoaded()");
    }

    /**
     * 請求下一頁資料,并且資料庫中資料全部加載完畢
     * @param itemAtEnd 資料庫中的最後一條資料
     */
    @Override
    public void onItemAtEndLoaded(@NonNull Article itemAtEnd) {
        super.onItemAtEndLoaded(itemAtEnd);
        Log.e(TAG, "onItemAtEndLoaded()");

        getTopAfterData(itemAtEnd);
    }

    /**
     * 沒有資料的時候,加載第一頁資料
     */
    private void getTopData() {
        int since = 0;
        RetrofitClient.getInstance()
                .getApi()
                .articlesData(ArticlesViewModel.PER_PAGE / 20)
                .enqueue(new Callback<Articles>() {
                    @Override
                    public void onResponse(@NonNull Call<Articles> call, @NonNull Response<Articles> response) {
                        if (response.body() != null) {
                            Log.e("getTopData()", " response:" + response.body());
                            insertArticles(response.body().data.datas);
                        }
                    }

                    @Override
                    public void onFailure(@NonNull Call<Articles> call, @NonNull Throwable t) {

                    }
                });
    }

    /**
     * 擷取下一頁資料
     */
    private void getTopAfterData(Article article) {

        RetrofitClient.getInstance()
                .getApi()
                .articlesData(ArticlesViewModel.PER_PAGE / 20)
                .enqueue(new Callback<Articles>() {
                    @Override
                    public void onResponse(@NonNull Call<Articles> call, @NonNull Response<Articles> response) {
                        if (response.body() != null) {
                            Log.e("getTopAfterData()", " response:" + response.body());
                            insertArticles(response.body().data.datas);
                        }
                    }

                    @Override
                    public void onFailure(@NonNull Call<Articles> call, @NonNull Throwable t) {

                    }
                });

    }

    /**
     * 插入資料
     */
    private void insertArticles(final List<Article> articles) {
        ArchTaskExecutor.getIOThreadExecutor().execute(new Runnable() {
            @Override
            public void run() {
                ArticleDatabase.getInstance(application).articleDao().insertArticles(articles);

            }
        });
    }
}
           
ViewModel
public ArticlesViewModel(Application application) {
        super(application);
        ArticleDatabase articleDatabase = ArticleDatabase.getInstance(application);

        articlePagedList = (new LivePagedListBuilder<>(articleDatabase.articleDao().getArticlesList(), ArticlesViewModel.PER_PAGE))
                .setBoundaryCallback(new ArticleBoundaryCallback(application))
                .build();
}
public void refresh(Context context) {

        ArchTaskExecutor.getIOThreadExecutor().execute(() -> ArticleDatabase.getInstance(context).articleDao().clear());

}
           

Room對Paging元件提供原生支援,是以

LivePagedListBuilder

建立PagedList時,可以直接将Room作為資料源。

PagedList實作增删改查

通過閱讀

PagedList

的源碼發現,它隻支援

get

(查詢)操作,不支援增、删和改,但在項目開發中有這樣的需求,我們對

PagedList

進行擴充。

建立DataSource資料源。

/**
 * 一個可變更的ItemKeyedDataSource 資料源
 * 工作原理是:我們知道DataSource是會被PagedList 持有的。
 * 一旦,我們調用了new PagedList.Builder<Key, Value>().build(); 那麼就會立刻觸發目前DataSource的loadInitial()方法,而且是同步
 * 詳情見ContiguousPagedList的構造函數,而我們在目前DataSource的loadInitial()方法中傳回了 最新的資料集合 data。
 * 一旦,我們再次調用PagedListAdapter#submitList()方法 就會觸發差分異計算 把新資料變更到清單之上了。
 */
public abstract class MutableItemKeyedDataSource<Key, Value> extends ItemKeyedDataSource<Key, Value> {
    private ItemKeyedDataSource mDataSource;

    public List<Value> data = new ArrayList<>();

    public PagedList<Value> buildNewPagedList(PagedList.Config config) {
        PagedList<Value> pagedList = new PagedList.Builder<Key, Value>(this, config)
                .setFetchExecutor(ArchTaskExecutor.getIOThreadExecutor())
                .setNotifyExecutor(ArchTaskExecutor.getMainThreadExecutor())
                .build();

        return pagedList;
    }

    public MutableItemKeyedDataSource(ItemKeyedDataSource dataSource) {

        mDataSource = dataSource;
    }

    @Override
    public void loadInitial(@NonNull LoadInitialParams<Key> params, @NonNull LoadInitialCallback<Value> callback) {
        callback.onResult(data);
    }

    @Override
    public void loadAfter(@NonNull LoadParams<Key> params, @NonNull LoadCallback<Value> callback) {
        if (mDataSource != null) {
            //一旦 和目前DataSource關聯的PagedList被送出到PagedListAdapter。那麼ViewModel中建立的DataSource 就不會再被調用了
            //我們需要在分頁的時候 代理一下 原來的DataSource,迫使其繼續工作
            mDataSource.loadAfter(params, callback);
        }
    }

    @Override
    public void loadBefore(@NonNull LoadParams<Key> params, @NonNull LoadCallback<Value> callback) {
        callback.onResult(Collections.emptyList());
    }

    @NonNull
    @Override
    public abstract Key getKey(@NonNull Value item);
}
           

添加

public void addAndRefreshList(Comment item) {
        PagedList<Comment> currentList = getCurrentList();
        MutableItemKeyedDataSource<Integer, Comment> mutableItemKeyedDataSource = new MutableItemKeyedDataSource<Integer, Comment>((ItemKeyedDataSource) currentList.getDataSource()) {
            @NonNull
            @Override
            public Integer getKey(@NonNull Comment item) {
                return item.id;
            }
        };
        mutableItemKeyedDataSource.data.add(item);
        mutableItemKeyedDataSource.data.addAll(currentList);
        PagedList<Comment> pagedList = mutableItemKeyedDataSource.buildNewPagedList(currentList.getConfig());
        submitList(pagedList);
}
           

删除

/**
  * 删除
  * @param item 删除的item
  */
public void deleteItem(Comment item) {
        MutableItemKeyedDataSource<Integer, Comment> dataSource = new MutableItemKeyedDataSource<Integer, Comment>((ItemKeyedDataSource) getCurrentList().getDataSource()) {
            @NonNull
            @Override
            public Integer getKey(@NonNull Comment item) {
                return item.id;
            }
        };
        PagedList<Comment> currentList = getCurrentList();
        for (Comment comment : currentList) {
            //曬選出不是要删除的item,添加到dataSource.data
            if (comment != item) {
                dataSource.data.add(comment);
            }
        }
        PagedList<Comment> pagedList = dataSource.buildNewPagedList(getCurrentList().getConfig());
        submitList(pagedList);
    }  
           

修改

public void updateItem(Comment item) {
        PagedList<Comment> currentList = getCurrentList();
        MutableItemKeyedDataSource<Integer, Comment> dataSource = new MutableItemKeyedDataSource<Integer, Comment>((ItemKeyedDataSource) currentList.getDataSource()) {
            @NonNull
            @Override
            public Integer getKey(@NonNull Comment item) {
                return item.id;
            }
        };

        for (Comment comment : currentList) {
            if (comment == item) {
                comment.setName("yhj");
            }
            break;
        }
        dataSource.data.addAll(currentList);
        PagedList<Comment> pagedList = dataSource.buildNewPagedList(currentList.getConfig());
        submitList(pagedList, new Runnable() {
            @Override
            public void run() {
                notifyDataSetChanged();
            }
        });
    }
           

Paging3

Paging3與舊版Paging存在很大差別,目前是beta版。

優勢

  • 為 Kotlin 協程和流程提供一流的支援。
  • 支援使用RxJava Single或Guava ListenableFuture基元進行異步加載。
  • 針對自适應界面設計的内置加載狀态和錯誤信号,包括重試和重新整理功能。
  • 改進了代碼庫層,包括取消支援和簡化的資料源界面。
  • 改進了示範層、清單分隔符、自定義頁面轉換和加載狀态頁眉和頁腳。

依賴

差別

Paging3向後相容,仍然可以使用,隻是标注已過時。

DataSource

Paing2中的DataSource有三種,Paging3中将它們合并到了

PagingSource

中,實作

load()

getRefreshKey()

,在Paging3中,所有加載方法參數被一個

LoadParams

密封類替代,該類中包含了每個加載類型所對應的子類。如果需要區分

load()

中的加載類型,請檢查傳入了

LoadParams

的哪個子類。

public class Articles3DataSource extends PagingSource<Integer,Article> {
    @Nullable
    @Override
    public Integer getRefreshKey(@NotNull PagingState<Integer, Article> pagingState) {
        return pagingState.getAnchorPosition();
    }

    @Nullable
    @Override
    public Object load(@NotNull LoadParams<Integer> loadParams, @NotNull Continuation<? super LoadResult<Integer, Article>> continuation) {
        .....
    }
}
           
PagedListAdapter

Adapter不在繼承

PagedListAdapter

,而是由

PagingDataAdapter

替代,其它不變。

public class Articles3Adapter extends PagingDataAdapter<Article, Articles3Adapter.ArticlesViewHolder> {
.......
}
           

繼續閱讀