Markdown版本筆記 | 個人GitHub首頁 | 個人博客 | 個人微信 | 個人郵箱 |
---|---|---|---|---|
MyAndroidBlogs | baiqiantao | baiqiantao | bqt20094 | baiqiantao@sina.com |
Paging 是什麼?
Paging 可使開發者更輕鬆在 RecyclerView 中 分頁加載數據。android
implementation "android.arch.paging:runtime:1.0.1" //Paging implementation "android.arch.paging:rxjava2:1.0.1" //Paging對RxJava2的支持
原理示意圖:https://upload-images.jianshu.io/upload_images/7293029-27facf0a399c66b8.gif?imageMogr2/auto-orient/git
組成部分:github
數據的改變會驅動列表的更新
,所以,數據源是很重要的第一次默認加載多少數據
,以後每一次加載多少數據
,如何加載等等,並將數據的變動反映到UI上。建立數據源
在Paging中,數據源被抽象爲 DataSource , 其獲取須要依靠 DataSource 的內部工廠類 DataSource.Factory
,經過create()方法就能夠得到DataSource 的實例:數據庫
public abstract static class Factory<Key, Value> { public abstract DataSource<Key, Value> create(); }
數據源通常有兩種選擇,遠程服務器請求或者讀取本地持久化數據,這些並不重要,本文咱們以Room數據庫爲例:服務器
@Query("SELECT * FROM table_user") DataSource.Factory<Integer, User> getAllUserDataSource();
DataSource.Factory<Integer, User> factory = UserDb.get(getApplication()).userDao().getAllUserDataSource();
Paging能夠得到Room的原生支持,所以做爲示例很是合適,固然咱們更多獲取數據源是經過API網絡請求,其實現方式能夠參考 官方Sample。微信
PS:若是經過API網絡請求獲取DataSource,相比使用Room來講要麻煩不少網絡
配置PageList
PageList的做用:app
PageList提供了 PagedList.Config 類供咱們進行實例化配置,其提供了5個可選配置:ide
public static final class Builder { // 省略Builder其餘內部方法 private int mPageSize = -1; //每次加載多少數據 private int mPrefetchDistance = -1; //距底部還有幾條數據時,加載下一頁數據 private int mInitialLoadSizeHint = -1; //第一次加載多少數據,必須是分頁加載數量的倍數 private boolean mEnablePlaceholders = true; //是否啓用佔位符,若爲true,則視爲固定數量的item private int mMaxSize = MAX_SIZE_UNBOUNDED; //默認Integer.MAX_VALUE,Defines how many items to keep loaded at once. }
配置Adapter
就像咱們平時配置 RecyclerView 差很少,咱們配置了 ViewHolder 和 RecyclerView.Adapter,略微不一樣的是,咱們須要繼承PagedListAdapter
,而且咱們須要傳一個 DifffUtil.ItemCallback 的實例。
DifffUtil.ItemCallback的意義是,我須要知道怎麼樣的比較,才意味着數據源的變化,並根據變化再進行的UI刷新操做。
監聽數據源的變動,並響應在UI上
這個就很簡單了
//每當觀察到數據源中數據的變化,咱們就把最新的數據交給Adapter去展現 viewModel.getRefreshLiveData().observe(this, pagedList -> { Log.i("bqt", "【數據發生改變】" + pagedList.size() + " " + pagedList.getPositionOffset() + " " + pagedList.getLoadedCount() + " " + pagedList.getLastKey() + " " + pagedList.isImmutable() + " " + pagedList.isDetached()); adapter.submitList(pagedList); //將數據的變化反映到UI上 Set the new list to be displayed });
基本結構:
//這個數據源主要須要傳遞Int型的PageNum做爲參數實現每一頁數據的請求 public class PagingDataSource extends PageKeyedDataSource<Integer, User> { @Override public void loadInitial(@NonNull LoadInitialParams<Integer> params, @NonNull LoadInitialCallback<Integer, User> callback) { //requestedLoadSize爲加載的數據量,placeholdersEnabled是是否顯示佔位;callback爲數據加載完成的回調 //LoadInitialCallback的onResult方法有三個參數,第一個爲數據,後面兩個即爲上一頁和下一頁 Log.i("bqt", "【loadInitial】" + params.requestedLoadSize + " " + params.placeholdersEnabled);//初始加載數據 //if(知足條件) 請求一批數據,數據處理後經過callback返回 //callback.onResult(List<Value> data, Key previousPageKey, Key nextPageKey); } @Override public void loadBefore(@NonNull LoadParams<Integer> params, @NonNull LoadCallback<Integer, User> callback) { Log.i("bqt", "【loadBefore】" + params.key + " " + params.requestedLoadSize);//向前分頁加載數據 //key即爲DataSource<Key, Value>中的key,在這裏即爲頁數;一樣,callback爲數據加載完成的回調 //LoadParams中的key即爲咱們要加載頁的數據,加載完後回調中告知下一次加載數據頁數+1或者-1 } @Override public void loadAfter(@NonNull LoadParams<Integer> params, @NonNull LoadCallback<Integer, User> callback) { Log.i("bqt", "【loadAfter】" + params.key + " " + params.requestedLoadSize);//向後分頁加載數據 //if(知足條件) 再請求一批數據,數據處理後經過callback返回 //callback.onResult(List<Value> data, Key adjacentPageKey); } }
繼承自PageKeyedDataSource
後須要實現如下三個方法:
loadInitial
初始加載數據loadAfter
向後分頁加載數據loadBefore
向前分頁加載數據這三個方法都有兩個參數,一個params
和一個callback
。
LoadInitialParams
包含了requestedLoadSize和placeholdersEnabled兩個屬性,requestedLoadSize爲加載的數據量,placeholdersEnabled是是否顯示佔位及當數據爲null時顯示佔位的viewLoadParams
包含了key和requestedLoadSize,key即爲DataSource<Key, Value>
中的key,在這裏即爲頁數callback.onResult
告訴調用者數據加載完成。onResult有三個參數,第一個爲數據,後面兩個即爲上一頁和下一頁。
若是咱們當前頁爲第一頁即沒有上一頁,則上一頁爲null,下一頁爲2,此時加載的時候會加載當前頁和調用loadAfter加載第二頁,但不會調用loadBefore,由於沒有上一頁,即previousPageKey爲null不會加載上一頁
若是咱們初始加載的是第三頁,則上一頁是2,下一頁是4,此時加載的時候會加載當前頁和調用loadAfter加載第4頁,調用loadBefore加載第二頁
分頁加載的時候會將previousPageKey或nextPageKey傳遞到loadAfter或loadBefore中的params.key
loadAfter 、loadBefore中的params中的key即爲咱們要加載頁的數據,加載完後回調中告知下一次加載數據頁數+1或者-1
參考 此博客,原文爲Kotlin版案例,我將其轉爲Java版實現,並在其基礎上添加了一些邏輯。
public class PagingActivity extends AppCompatActivity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_paging); RecyclerView recyclerView = findViewById(R.id.recyclerView); DiffUtil.ItemCallback<User> itemCallback = new DiffUtil.ItemCallback<User>() { @Override public boolean areItemsTheSame(@NonNull User oldItem, @NonNull User newItem) { return oldItem.uid == newItem.uid; } @Override public boolean areContentsTheSame(@NonNull User oldItem, @NonNull User newItem) { return oldItem == newItem; } }; PagingAdapter adapter = new PagingAdapter(itemCallback); UserDao dao = UserDb.get(this).userDao(); adapter.setOnClick((user, position) -> { Log.i("bqt", "【position】" + position); new Thread(() -> { if (position % 2 == 0) dao.deleteUser(user); else dao.insertUser(new User("insert")); }).start(); }); recyclerView.setAdapter(adapter); recyclerView.setLayoutManager(new LinearLayoutManager(this)); dao.getAllUser().observe(this, users -> Log.i("bqt", "【數據發生改變】" + users.size())); PagingViewModel viewModel = ViewModelProviders.of(this).get(PagingViewModel.class); //每當觀察到數據源中數據的變化,咱們就把最新的數據交給Adapter去展現 viewModel.getRefreshLiveData().observe(this, pagedList -> { Log.i("bqt", "【數據發生改變】" + pagedList.size() + " " + pagedList.getPositionOffset() + " " + pagedList.getLoadedCount() + " " + pagedList.getLastKey() + " " + pagedList.isImmutable() + " " + pagedList.isDetached()); adapter.submitList(pagedList); //將數據的變化反映到UI上 Set the new list to be displayed }); } }
public class PagingAdapter extends PagedListAdapter<User, PagingAdapter.MyViewHolder> { PagingAdapter(DiffUtil.ItemCallback<User> itemCallback) { super(itemCallback); } @Override public void onCurrentListChanged(@Nullable PagedList<User> previousList, @Nullable PagedList<User> currentList) { super.onCurrentListChanged(previousList, currentList); Log.i("bqt", "【onCurrentListChanged】"); } @NonNull @Override public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_student, parent, false); return new MyViewHolder(view); } @Override public void onBindViewHolder(@NonNull MyViewHolder holder, int position) { Log.i("bqt", "【onBindViewHolder】" + position); User user = getItem(position); //items might be null if they are not paged in yet. PagedListAdapter will re-bind the ViewHolder when Item is loaded. if (user != null) { holder.nameView.setText(user.name); holder.nameView.setOnClickListener(v -> { if (onClick != null) { onClick.onClick(user, position); } }); } } class MyViewHolder extends RecyclerView.ViewHolder { TextView nameView; MyViewHolder(View view) { super(view); nameView = view.findViewById(R.id.name); } } private OnClick onClick; void setOnClick(OnClick onClick) { this.onClick = onClick; } interface OnClick { void onClick(User user, int position); } }
public class PagingViewModel extends AndroidViewModel { public PagingViewModel(@NonNull Application application) { super(application); } public LiveData<PagedList<User>> getRefreshLiveData() { DataSource.Factory<Integer, User> dataSourceFactory = UserDb.get(getApplication()).userDao().getAllUserDataSource(); PagedList.Config config = new PagedList.Config.Builder() .setInitialLoadSizeHint(10) //第一次加載多少數據,必須是分頁加載數量的倍數 .setPageSize(5) //每次加載多少數據 .setMaxSize(Integer.MAX_VALUE) //Defines how many items to keep loaded at once. .setPrefetchDistance(5) //距底部還有幾條數據時,加載下一頁數據 .setEnablePlaceholders(true) //是否啓用佔位符,若爲true,則視爲固定數量的item .build(); LivePagedListBuilder<Integer, User> livePagedListBuilder = new LivePagedListBuilder<>(dataSourceFactory, config) .setFetchExecutor(Executors.newSingleThreadExecutor()) //設置獲取數據源的線程 .setInitialLoadKey(0) //可經過 pagedList.getLastKey() 獲取此值,默認值固然爲 Key(這裏爲Integer)類型的初始化值()這裏爲0 .setBoundaryCallback(new PagedList.BoundaryCallback<User>() { @Override public void onZeroItemsLoaded() { //沒有數據被加載 super.onZeroItemsLoaded(); Log.i("bqt", "【onZeroItemsLoaded】"); } @Override public void onItemAtFrontLoaded(@NonNull User itemAtFront) { //加載第一個 super.onItemAtFrontLoaded(itemAtFront); Log.i("bqt", "【onItemAtFrontLoaded】" + itemAtFront.name); } @Override public void onItemAtEndLoaded(@NonNull User itemAtEnd) { //加載最後一個 super.onItemAtEndLoaded(itemAtEnd); Log.i("bqt", "【onItemAtEndLoaded】" + itemAtEnd.name); } }); return livePagedListBuilder.build(); } }
@Entity(tableName = "table_user") public class User { @PrimaryKey(autoGenerate = true) public int uid; @ColumnInfo(name = "user_name") public String name = "包青天"; public User(String name) { this.name = name; } }
@Dao public interface UserDao { @Insert List<Long> insertUser(User... users); @Insert List<Long> insertUser(List<User> users); @Delete int deleteUser(User user); @Query("SELECT * FROM table_user") LiveData<List<User>> getAllUser(); @Query("SELECT * FROM table_user") DataSource.Factory<Integer, User> getAllUserDataSource(); }
@Database(entities = {User.class}, version = 1) public abstract class UserDb extends RoomDatabase { public abstract UserDao userDao(); //沒有參數的抽象方法,返回值所表明的類必須用@Dao註解 private static UserDb db; public static UserDb get(Context context) { if (db == null) { db = Room.databaseBuilder(context.getApplicationContext(), UserDb.class, "dbname") .addCallback(new RoomDatabase.Callback() { @Override public void onCreate(@NonNull SupportSQLiteDatabase database) { super.onCreate(database); Log.i("bqt", "【onCreate】"); new Thread(() -> { List<User> users = new ArrayList<>(); for (int i = 0; i < 50; i++) { users.add(new User("bqt" + i)); } get(context).userDao().insertUser(users); }).start(); } }) .build(); } return db; } }
2019-4-7