当前位置: 首页 > 工具软件 > paging > 使用案例 >

Android jetpack之Paging

景建业
2023-12-01

Paging就是Google为了方便Android开发者完成分页加载而设计的一个组件,分页加载是对数据进行按需加载,在不影响用户体验的同时,还能节省数据流量,提升应用的性能。

Paging的工作原理大致分为6个步骤

1.在RecyclerView的滑动过程中,会触发PagedListAdapter类中的onBindViewHolder()方法。数据与RecycleView Item布局中的UI控件正是在该方法中进行绑定的。
2.当RecyclerView滑动到底部时,在onBindViewHolder()方法中所调用的getItem()方法会通知PagedList,当前需要载入更多数据。
3.接着,PagedList会根据PageList.Config中的配置通知DataSource执行具体的数据获取工作。
4.DataSource从网络/本地数据库取得数据后,交给PagedList,PagedList将持有这些数据。
5.PagedList将数据交给PagedListAdapter中的DiffUtil进行比对和处理。
6.数据在经过处理后,交由RecyclerView进行展示。

Paging的3个核心类

  • PagedListAdapter
    RecyclerView的Adapter继承自PagedListAdapter。
  • PagedList
    PagedList负责通知DataSource何时获取数据,以及如何获取数据。例如,何时加载第一页/下一页、第一页加载的数量、提前多少条数据开始执行预加载等。需要注意的是,从DataSource获取的数据将存储在PagedList中。
  • DataSource
    在DataSource中执行具体的数据载入工作。注意,数据的载入需要在工作线程中进行。数据可以来自网络,也可以来自本地数据库,如Room。根据分页机制的不同,Paging为我们提供了3种DataSource。

本文介绍的是以页为数据源的Paging框架

  • 抖音开放平台获取粉丝列表api中cursor是分页游标, 第一页请求cursor是0, response中会返回下一页请求用到的cursor, 同时response还会返回has_more来表明是否有更多的数据。
  • 网络请求和Model类本文就不再赘述

DataSource类

public class FansDataSource extends PageKeyedDataSource<Integer, Fans.DataBean.ListBean> {
    public static final int FIRST_PAGE =0;
    public static final int PER_PAGE=20;

    @Override
    public void loadInitial(@NonNull LoadInitialParams<Integer> params, @NonNull LoadInitialCallback<Integer, Fans.DataBean.ListBean> callback) {
        MyApplication.getApiService().getfans("application/json",MyApplication.accesstoken,MyApplication.open_id.getValue(),FIRST_PAGE,PER_PAGE).enqueue(new Callback<Fans>() {
            @Override
            public void onResponse(Call<Fans> call, Response<Fans> response) {
                if(response.body()!=null)
                {
                    callback.onResult(response.body().getData().getList(),null,FIRST_PAGE+response.body().getData().getCursor());
                }
            }
            @Override
            public void onFailure(Call<Fans> call, Throwable t) {
            }
        });

    }

    @Override
    public void loadBefore(@NonNull LoadParams<Integer> params, @NonNull LoadCallback<Integer, Fans.DataBean.ListBean> callback) {
    }

    @Override
    public void loadAfter(@NonNull LoadParams<Integer> params, @NonNull LoadCallback<Integer, Fans.DataBean.ListBean> callback) {

        MyApplication.getApiService().getfans("application/json",MyApplication.accesstoken,MyApplication.open_id.getValue(),params.key,PER_PAGE).enqueue(new Callback<Fans>() {
            @Override
            public void onResponse(Call<Fans> call, Response<Fans> response) {
                if(response.body()==null)
                {return;}
                    Integer nextKey=response.body().getData().isHas_more()?response.body().getData().getCursor():null;
                    callback.onResult(response.body().getData().getList(),nextKey);
            }

            @Override
            public void onFailure(Call<Fans> call, Throwable t) {

            }
        });

    }
}

loadInitial()

当页面首次加载数据时会调用loadInitial()方法。在该方法内调用API接口,加载第一页的数据。加载成功后,通过callback.onResult()方法将数据返回给PagedList。

onResult()

第1个参数是加载得到的数据,将其交给PagedList。第2个参数是上一页的Pagekey。在此,由于当前加载的是第一页,不存在上一页,所以设置为null。第3个参数为下一页的Pagekey,即当前页key的值加上第一次请求返回的cursor,若不存在下一页,则设置为null。

loadAfter()

加载下一页的工作在该方法内进行。需要注意的是LoadParamsparams参数,我们在loadInitial()方法中设置的nextPageKey,正是通过LoadParams传递过来的。LoadParams.key得到的是下一页的key,通过这个key,我们可以请求下一页。请求下一页成功后,同样也是通过callback.onResult()方法将数据返回给PagedList,同时再设置下一页的key。注意:在设置下一页之前,需要判断是否还有更多的数据,若没有数据,则将下一页的key设置为null,表示所有数据请求完毕。

FansDataSourceFactory

FansDataSourceFactory负责创建FansDataSource,并使用LiveData包装FansDataSource,将其暴露给FansViewModel。

public class FansDataSourceFactory extends DataSource.Factory<Integer, Fans.DataBean.ListBean> {
    private MutableLiveData<FansDataSource>fansDataSourceMutableLiveData=new MutableLiveData<>();
    @NonNull
    @Override
    public DataSource<Integer, Fans.DataBean.ListBean> create() {
        FansDataSource dataSource=new FansDataSource();
        fansDataSourceMutableLiveData.postValue(dataSource);

        return dataSource;
    }
}

FansViewModel

在FansViewModel中通过LivePagedListBuilder创建和配置PagedList,并使用LiveData包装PagedList,将其暴露给Activity。

public class FansViewModel extends ViewModel {
    public LiveData<PagedList<Fans.DataBean.ListBean>> fansPagedList;
    public FansViewModel()
    {
         config=(new PagedList.Config.Builder())
                .setEnablePlaceholders(false)
                .setPageSize(FansDataSource.PER_PAGE)
                .setPrefetchDistance(40)
                .setMaxSize(1000*FansDataSource.PER_PAGE)
                .build();


    }
    public LiveData<PagedList<Fans.DataBean.ListBean>> getFansPagedList()
    {
        fansPagedList=(new LivePagedListBuilder<>(new FansDataSourceFactory(),config)).build();
        return  fansPagedList;
    }

关于PagedList.Config中的几个重要方法说明如下。
● setEnablePlaceholders:用于设置控件占位,上文已详细介绍过。
● setPageSize:设置每页的大小,该值通常与DataSource中请求数据的参数值保持一致。
● setPrefetchDistance:设置当距离底部还有多少条数据时开始加载下一页数据。
● setInitialLoadSizeHint:设置首次加载数据的数量。该值要求是PageSize的整数倍。若未设置,则默认是PageSize的3倍。
● setMaxSize:设置PagedList所能承受的最大数量,一般来说是PageSize的许多倍,超过该值可能会出现异常。

FansPagedListAdapter

列表数据通过FansPagedListAdapter进行展示

public class FansPagedListAdapter extends PagedListAdapter<Fans.DataBean.ListBean,FansPagedListAdapter.FansViewHolder> {

   private Context context;
   public FansPagedListAdapter(Context context)
    {
        super(DIFF_CALLBACK);
        this.context=context;
    }
    private  static DiffUtil.ItemCallback<Fans.DataBean.ListBean> DIFF_CALLBACK=new DiffUtil.ItemCallback<Fans.DataBean.ListBean>() {
        @Override
        public boolean areItemsTheSame(@NonNull Fans.DataBean.ListBean oldItem, @NonNull Fans.DataBean.ListBean newItem) {
            return oldItem.getOpen_id()==newItem.getOpen_id();
        }
        @SuppressLint("DiffUtilEquals")
        @Override
        public boolean areContentsTheSame(@NonNull Fans.DataBean.ListBean oldItem, @NonNull Fans.DataBean.ListBean newItem) {
            return oldItem.equals(newItem);
        }
    };
    @NonNull
    @Override
    public FansPagedListAdapter.FansViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        FragmentFansListBinding fragmentFansListBinding= DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()), R.layout.fragment_fans_list,parent,false);
        return new FansViewHolder(fragmentFansListBinding);
    }
    @Override
    public void onBindViewHolder(@NonNull FansPagedListAdapter.FansViewHolder holder, int position) {
        Fans.DataBean.ListBean listBean=getItem(position);
        if(listBean!=null)
        holder.fragmentFansListBinding.setFans(listBean);
    }
    class FansViewHolder extends RecyclerView.ViewHolder
    {
        FragmentFansListBinding fragmentFansListBinding;
        public FansViewHolder(@NonNull FragmentFansListBinding itemView) {

            super(itemView.getRoot());
            fragmentFansListBinding=itemView;
        }
    }
}

FansPagedListAdapter需要继承自PagedListAdapter。在onBindViewHolder()方法中调用getItem()方法。若当前有数据,则直接将数据与UI控件进行绑定;若没有数据,则getItem()会通知PagedList去获取下一页的数据,PagedList收到通知后,让DataSource执行具体的数据获取工作。

Databinding的使用

在onCreateViewHolder根据布局文件获取到相对应的databinding类在,onBindViewHolder直接将拿到的list通过databinding类传入布局文件,在布局文件就可以直接将传递过来的list进行展示。

DiffUtil

DiffUtil的使用主要涉及两个方法,我们需要覆盖这两个方法。正是这两个方法,让更新数据变得更高效。
● areItemsTheSame:当DiffUtil想要检测两个对象是否代表同一个Item时,调用该方法进行判断。
● areContentsTheSame:当DiffUtil想要检测两个Item是否存在不一样的数据时,调用该方法进行判断。

最后在Activity中我们只要将recyclerview获取到为其设定对应的适配器,并观察fansViewModel中数据的变化,若数据发生变化,则通过PagedListAdapter.submitList()方法刷新数据。

 final FansPagedListAdapter fansPagedListAdapter = new FansPagedListAdapter(getContext());
            fansViewModel.getFansPagedList().observe(getViewLifecycleOwner(), new Observer<PagedList<Fans.DataBean.ListBean>>() {
                @Override
                public void onChanged(PagedList<Fans.DataBean.ListBean> listBeans) {
                    fansPagedListAdapter.submitList(listBeans);
                    Log.d(TAG, listBeans.toString());
                }
            });
 类似资料: