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