美文网首页Android开发经验谈
Android分页组件Paging简单使用

Android分页组件Paging简单使用

作者: 月塘路 | 来源:发表于2018-11-17 16:19 被阅读9次

title: Android分页组件Paging简单使用
date: 2018-10-10 17:03:23
tags: Paging


1.简介:

Paging组件是Google新推出的分页组件,可以轻松帮助开发者实现RecyclerView中分页加载功能。
本文先开坑,等以后用到在详细写明。
推荐博客:https://www.jianshu.com/p/1bfec9b9612c

2.Paging库引入:

implementation 'android.arch.paging:runtime:1.0.0'
刚引入就遇到了错误,真是一个不友好的开始:

Error:Execution failed for task ':app:transformDexArchiveWithExternalLibsDexMergerForDebug'.
> java.lang.RuntimeException: java.lang.RuntimeException: com.android.builder.dexing.DexArchiveMergerException: Unable to merge dex

一查资料,是导入重复的包,这个出错的地方还是google自己的东西,这岂不是
“大水冲了龙王庙,自家人打自家人”?
compile 'com.android.support:design:26.1.0'
google了一下,在stackoverflow中找到了解决办法:
https://stackoverflow.com/questions/49028119/multiple-dex-files-define-landroid-support-design-widget-coordinatorlayoutlayou
即用编译版本改成27,并且把对应的库版本也改为27
implementation 'com.android.support:appcompat-v7:27.1.1'
compile 'com.android.support:design:27.1.1'
问题了解决了,接下来我们看看他的工作原理

3.工作原理

PagingWork.gif

这是官方提供的原理图,写的很清楚从DataSource到PagedList, PagedListAdapter最后是我们的recyclerview,我们先眼熟这几个名字,下文会常常出现。

3.1 先来看Adapter

public class AdapterPaging extends PagedListAdapter<VideoInfo, AdapterPaging.ViewHolder> {

    private Context mContext;

    public static final DiffUtil.ItemCallback<VideoInfo> mDiffCallback = new AdapterPaging.VideoInfoItemCallback();

    protected AdapterPaging() {
        super(mDiffCallback);
    }

    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        return null;
    }

    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
        VideoInfo videoInfo = getItem(position);
    }
    
    static class ViewHolder extends RecyclerView.ViewHolder{

        private LinearLayout ll_videoInfo;

        public ViewHolder(View itemView) {
            super(itemView);
            ll_videoInfo = itemView.findViewById(R.id.ll_video_info);
        }
    }

    
    private static class VideoInfoItemCallback extends DiffUtil.ItemCallback<VideoInfo>{
        @Override
        public boolean areItemsTheSame(VideoInfo oldItem, VideoInfo newItem) {
            return oldItem.getUrl() == newItem.getUrl();
        }

        @Override
        public boolean areContentsTheSame(VideoInfo oldItem, VideoInfo newItem) {
            return (oldItem == newItem);
        }
    }
}

大致的4个不同如下:

  1. Adapter不再继承自RecyclerView.Adapter,改为继承自PagedListAdapter,因为PagedListAdapter就是RecyclerView.Adapter的一个子类。
  2. 定义内部回调接口VideoInfoItemCallback继承自DiffUtil.ItemCallback<VideoInfo>,并且实例化一个父类引用指向子类(VideoInfoItemCallback)对象
    public static final DiffUtil.ItemCallback<VideoInfo> mDiffCallback = new AdapterPaging.VideoInfoItemCallback();
  3. 重写构造方法,无需参数传入,调用父类构造方法将mDiffCallback传入。
  4. 通onBindViewHolder中过调用getItem(position);获得指定位置的数据对象。
    因为Adapter中不再需要维护一个数据List了,PagedListAdapter中已经维护有,并且提供getItem()方法访问。

3.2 在Activity中的使用

在使用Paging后,我们无需向Adapter中在传入数据源List,我们需要构造LiveData。
LiveData需要DataSource.Factory对象和PagedList.Config对象,只是实例化DataSource.Factory对象需要额外两个步骤
DataSource.Factory是一个抽象类,实例化时需要实现create()函数,这个函数返回值是一个DataSource类对象
DataSource是一个抽象类,他有三个实现子类:(详细参考原博客:https://www.jianshu.com/p/95d44c5338fd)
(1)PageKeyedDataSource 按页加载,如请求数据时传入page页码。
(2)ItemKeyedDataSource 按条目加载,即请求数据需要传入其它item的信息,如加载第n+1项的数据需传入第n项的id。
(3)PositionalDataSource 按位置加载,如加载指定从第n条到n+20条。
所以我们捋一下思路,首先要定义一个MyDataSource继承自DataSource的三个子类之一,
再定义一个MyDataSourceFactory继承自DataSource.Factory,返回值是MyDataSource
然后实例化PagedList.Config,这个类提供有Builder(),比较简单。
最后将MyDataSourceFactory对象和PagedList.Config对象传入new LivePagedListBuilder()中得到liveData数据源。
将liveData数据源和Adapter绑定是通过观察者模式实现,调用liveData.observe()。

//定义MyDataSource类,继承自DataSource三个子类之一
    private class MyDataSource extends PositionalDataSource<Movie.SubjectsBean>{

        @Override
        public void loadInitial(@NonNull LoadInitialParams params, @NonNull LoadInitialCallback<Movie.SubjectsBean> callback) {
            callback.onResult(loadData(0, 10), 0, 10);//loadData(0, 10)是我封装的加载数据方法,网络请求在loadData中,0是初始位置,10是请求10条数据
        }

        @Override
        public void loadRange(@NonNull LoadRangeParams params, @NonNull LoadRangeCallback<Movie.SubjectsBean> callback) {
            callback.onResult(loadData(params.startPosition, count));
        }
    }
    //定义MyDataSourceFactory,是DataSource.Factory的实现类
    private class MyDataSourceFactory extends DataSource.Factory<Integer, Movie.SubjectsBean>{

        @Override
        public DataSource<Integer, Movie.SubjectsBean> create() {
            return new MyDataSource();
        }
    }
    //将生产config和liveData的代码封装在这个方法中
    private void initPaging(){
        PagedList.Config config = new PagedList.Config.Builder()
                .setPageSize(10)    //每页显示的词条数
                .setEnablePlaceholders(false)
                .setInitialLoadSizeHint(10) //首次加载的数据量
                .setPrefetchDistance(5)     //距离底部还有多少条数据时开始预加载
                .build();

        /**
         * LiveData用LivePagedListBuilder生成
         * LivePagedListBuilder 构造方法需要 DataSource.Factory和PagedList.Config
         */
        LiveData<PagedList<Movie.SubjectsBean>> liveData = new LivePagedListBuilder(new MyDataSourceFactory(), config)
                .build();
        //观察者模式,将Adapter注册进去,当liveData发生改变事通知Adapter
        liveData.observe(this, new Observer<PagedList<Movie.SubjectsBean>>() {
            @Override
            public void onChanged(@Nullable PagedList<Movie.SubjectsBean> subjectsBeans) {
                adapterHomeInfo.submitList(subjectsBeans);
            }
        });
    }

从上面的代码我们可以看到,数据的入口在MyDataSource的两个重写方法里,里面的loadData()就是从外部获取数据的方法,可以按自己想需求来封装。但是数据加载位于这个位置,就要求我们初始化的时候使用的是本地数据,而来不及进行网络请求。

4.加载网络数据

这么好的控件,我无论如何都想用于纯网络加载。想这么做也很简单,因为我们已经把与初始化相关的代码封装到initPaging()中了,
我们只需要在合适的时候进行初始化,就能达到延迟加载的目的了。比如,我们可以用handler和网络请求实现消息异步处理,在handler的
中调用initPaging(),这样就可以不需要本地数据进行初始化了。

private boolean isDataFresh = false;
private boolean isInitPaging = false;
private Movie movie;
private MyHandler handler = new MyHandler(this);
//定义一个MyHandler,用弱引用防止内存泄漏
private static class MyHandler<T> extends Handler {
    WeakReference<ActivityHome> weakReference;

    public MyHandler(ActivityHome activityHome){
        weakReference = new WeakReference<ActivityHome>(activityHome);
    }

    @Override
    public void handleMessage(Message msg) {
        ActivityHome activity = weakReference.get();
        //网络状况不好时,返回obj为null
        if(null == msg.obj){
            Toast.makeText(activity, "获取数据失败,请检查网络", Toast.LENGTH_SHORT).show();
            return;
        }
        switch (msg.what){
            case Constants._MOVIE_REQUEST:
                activity.movie = NetWorkUtil.parseJsonWithGson(msg.obj.toString(), Movie.class);
                activity.isDataFresh = true;
                if (!activity.isInitPaging) {
                    activity.initPaging();
                    activity.isInitPaging = true;
                }
                break;

            default:
                break;
        }

    }
}
/**
 * 进行网络请求
 *
 * @param startPosition
 * @param count
 * @return
 */
private List<Movie.SubjectsBean> loadData(int startPosition, int count){
    List<Movie.SubjectsBean> subjectsBeanList = new ArrayList<>();
    if (true == isDataFresh) {
        subjectsBeanList = movie.getSubjects();
        //显示数据之后,将标志设置为false,等待下一次网络请求,数据更新完成
        isDataFresh = false;
        //启动下一次数据刷新
        requestData(handler);
    }
    return subjectsBeanList;
}

这里我用了两个标志位isDataFresh和isInitPaging,
isInitPaging是确保只在初始化时调用initPaging(),
isDataFresh是确保当前网络请求完成后,才会发送下一次请求,防止同时发送多个网络请求。

相关文章

网友评论

    本文标题:Android分页组件Paging简单使用

    本文链接:https://www.haomeiwen.com/subject/bddktqtx.html