• Jetpack 架构组件 Paging 分页加载 MD


    Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱
    MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina.com

    目录

    Paging

    官方文档
    官方案例

    简介

    Paging 是什么?
    Paging 可以使开发者更轻松在 RecyclerView分页加载数据

    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,在这里即为页数
    • 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

    附件列表

    • 相关阅读:
      CodeForce VKcup A
      CNN卷积神经网络
      神经网络
      我的机器学习之路
      [OPENCV] 第一个程序 识别颜色
      Android 登录界面与首页的设计
      go web的基本原理
      Go语言标准库之http/template
      吞吐量(TPS)、QPS、并发数、响应时间(RT)概念
      数据库恢复技术
    • 原文地址:https://www.cnblogs.com/baiqiantao/p/10668184.html
    Copyright © 2020-2023  润新知