Jetpack 架構組件 Paging 分頁加載 MD
Markdown版本筆記 | 我的GitHub首頁 | 我的博客 | 我的微信 | 我的郵箱 |
---|---|---|---|---|
MyAndroidBlogs | baiqiantao | baiqiantao | bqt20094 | [email protected] |
目錄
目錄
Paging
簡介
使用步驟
PageKeyedDataSource
Java版案例
PagingActivity
PagingAdapter
PagingViewModel
User
UserDao
UserDb
Paging
官方文檔
官方案例
簡介
Paging 是什麽?
Paging
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/
組成部分:
- DataSource:數據源,
數據的改變會驅動列表的更新
- PageList:核心類,它從數據源取出數據,同時,它負責控制
第一次默認加載多少數據
,之後每一次加載多少數據
,如何加載等等,並將數據的變更反映到UI上。 - PagedListAdapter:適配器,RecyclerView的適配器,通過分析數據是否發生了改變,負責處理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的作用:
- 從數據源取出數據
- 負責控制第一次默認加載多少數據,之後每一次加載多少數據,如何加載等等
- 將數據的變更反映到UI上
PageList提供了 PagedList.Config 類供我們進行實例化配置,其提供了5個可選配置:
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
});
PageKeyedDataSource
參考
基本結構:
//這個數據源主要需要傳遞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
。
- params包裝了分頁加載的參數:
- loadInitial中的params為
LoadInitialParams
包含了requestedLoadSize和placeholdersEnabled兩個屬性,requestedLoadSize為加載的數據量,placeholdersEnabled是是否顯示占位及當數據為null時顯示占位的view - loadBefore和loadAfter中的params為
LoadParams
包含了key和requestedLoadSize,key即為DataSource<Key, Value>
中的key,在這裏即為頁數
- loadInitial中的params為
- callback為數據加載完成的回調,loadInitial中調用調用IPVTApiPresenter加載數據,然後調用
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
Java版案例
參考 此博客,原文為Kotlin版案例,我將其轉為Java版實現,並在其基礎上添加了一些邏輯。
PagingActivity
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
});
}
}
PagingAdapter
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);
}
}
PagingViewModel
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();
}
}
User
@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;
}
}
UserDao
@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();
}
UserDb
@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
附件列表
Jetpack 架構組件 Paging 分頁加載 MD