许多应用程序可以处理大量数据,但只需要随时加载和显示一小部分数据。一个应用程序可能有数千个可能显示的项目,但它可能只需要一次访问几十个项目。如果应用程序不小心,它可能最终会请求它实际上不需要的数据,从而给设备和网络带来性能负担。如果数据与远程数据库存储或同步,则这也会降低应用速度并浪费用户的数据计划。
而谷歌推出的paging library 可以让 app 进行大数据查询的时候,在不过多增加设备负担或者等待时间的情况下,让渐进的从数据源加载数据变得更加简单。
官方文档:https://developer.android.com/topic/libraries/architecture/paging.html#java
中文版:http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2017/0920/8533.html
网上可以搜到很多介绍Paging框架的文章,可是真正介绍其怎么使用的却是少之又少,今天就给大家展示一下如何使用Paging分页从网络中加载数据。
谷歌官方提供的paging使用Demo:https://github.com/googlesamples/android-architecture-components
本文参考一:https://blog.csdn.net/alphathink/article/details/83380739
本文参考二:https://juejin.im/post/5a066be36fb9a045167ca8c7
本次提供的是java版本的Android Paging Library按页获取网络数据实例,如果想学习kotlin版本的Android Paging Library按页获取网络数据实例,请参考http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2017/0922/8539.html
1.添加依赖
我们来到maven中的paging库https://mvnrepository.com/artifact/android.arch.paging
三个库的区别在于:
序号 | 名称 | 描述 |
1 | Runtime | 是通过普通方式 使用paging |
2 | Common | 也是通过普通方式使用paging但是不提供测试依赖,官方这样描述:alternatively - without Android dependencies for testing |
3 | RxJava2 | 是通过RxJava2使用paging |
0.添加依赖 找到paging的依赖方式
在app的build.gradle中添加如下依赖
implementation 'android.arch.paging:runtime:1.0.0'
我添加1.0.1的依赖时提示下载失败,因此暂且用1.0.0版本
而paging需要标准的lifecycle依赖,因此还需要加上
annotationProcessor "android.arch.lifecycle:compiler:1.0.0"
为了大家对使用paging的整个流程有个清晰的认识,我们先放一张官网的流程图
1.DataSource 数据的来源
Paging提供了三种不同的分页方式
序号 | 名称 | 描述 |
---|---|---|
1 | PageKeyedDataSource | 通常使用在论坛,贴吧中。根据传入的页面num进行获取某一页的数据,比如获取第10页。 |
2 | PositionalDataSource | Android开发中经常使用的分页,例如,每页10条,默认显示第一页。 CONTENT_LENGTH=10、mPage=1。 |
3 | ItemKeyedDataSource | 使用上一条data对象里某一个值作为下一页请求的关键字。这种情况多出现在服务端已经完成了分页效果,在某一个对象添加如“下一页”这样的标识数据。 |
我们选择PositionalDataSource来构造我们的DataSource,并没有什么需要理解的东西,照着写就行
public class PageDataSourceFactory extends DataSource.Factory {
public PositionalDataSource<RawBean> mPositionalDataSource;
public PageDataSourceFactory(PositionalDataSource<RawBean> positionalDataSource) {
this.mPositionalDataSource = positionalDataSource;
}
@Override
public DataSource create() {
return mPositionalDataSource;
}
}
其中的RawBean是我们从网络中加载的数据元(javabean,包含名字,id等信息),替换为你们自己的数据元就好。关于其具体结构我放在文章的最后面。
2.PagedList 配置取数据方式
我以注释的方式将每一步的作用及原理都写在代码中了
public class MyViewModel extends AndroidViewModel {
//每页需要加载的数量
private static final int NUM_PER_PAGE = 15;
//第一页
private static final int PAGE_FIRST = 1;
//当前页码数
private int mPage = PAGE_FIRST;
//列表数据
private LiveData<PagedList<RawBean>> mLiveData;
//用来存储网络返回的信息类
private ResultBean resultBean;
//默认的构造方法
public MyViewModel(@NonNull Application application) {
super(application);
}
public LiveData<PagedList<RawBean>> getLiveData() {
initPagedList();//初始化PagedList
return mLiveData;
}
/**
* 初始化PageList
*/
private void initPagedList() {
final PositionalDataSource<RawBean> positionalDataSource = new PositionalDataSource<RawBean>() {
List<RawBean> list = new ArrayList<>();
/**
* recyclerView第一次加载时自动调用
* @param params 包含当前加载的位置position、下一页加载的长度count
* @param callback 将数据回调给UI界面使用callback.onResult
*/
@Override
public void loadInitial(@NonNull LoadInitialParams params, @NonNull LoadInitialCallback<RawBean> callback) {
//计算显示到第几条数据
final int position = computeInitialLoadPosition(params, NUM_PER_PAGE);
//recyclerView第一次加载时我们调用OkHttp进行数据的加载
//add后面都是post请求所需要的字段及值,根据请求数据的不同有所改变
RequestBody body = new FormBody.Builder()
.add("searchValue","最好的我们")
.add("rows","10")
.add("page",String.valueOf(mPage))//1
.build();
Request request = new Request.Builder()
.url(Constant.mainUrl + Constant.srearchUrl)
.post(body)
.build();
try {
Response response = HttpUtils.getClient().newCall(request).execute();
String strResponse = response.body().string();
//使用Gson将返回的json数据解析为javabean
Gson gson = new Gson();
//从javabean中取出我们需要的bean
resultBean = gson.fromJson(strResponse,new TypeToken<ResultBean>(){}.getType());
list.addAll(resultBean.getRows());
//最重要的一步,paging是基于观察者模式,我们在这里调用callback.onResult();
//会直接将数据list返回到UI层,等下面接受到这个list数据的数据的时候我会提醒大家
//如果设置占位符需要调用三个参数的onResult()方法,最后一个参数为每页的总数据量
//我们没有设置占位符,因此调用两个参数的方法
callback.onResult(list,position);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 当用户滑动recyclerView到下一屏的时候自动调用,这里我们自动加载下一页的数据
* @param params 包含当前加载的位置position、下一页加载的长度count
* @param callback 将数据回调给UI界面使用callback.onResult
*/
@Override
public void loadRange(@NonNull LoadRangeParams params, @NonNull LoadRangeCallback<RawBean> callback) {
//每次加载到分页条目时页数变为下一页
mPage++;
//使用OkHttp加载下一页数据
RequestBody body = new FormBody.Builder()
.add("searchValue","最好的我们")
.add("rows","10")
.add("page",String.valueOf(mPage))
.build();
Request request = new Request.Builder()
.url(Constant.mainUrl + Constant.srearchUrl)
.post(body)
.build();
try {
Response response = HttpUtils.getClient().newCall(request).execute();
String strResponse = response.body().string();
//使用Gson解析数据
Gson gson = new Gson();
resultBean = gson.fromJson(strResponse,new TypeToken<ResultBean>(){}.getType());
list.addAll(resultBean.getRows());
callback.onResult(list);
} catch (IOException e) {
e.printStackTrace();
}
}
};
// 构建LiveData
mLiveData = new LivePagedListBuilder(new PageDataSourceFactory(positionalDataSource)//自己定义
, new PagedList.Config.Builder().setPageSize(NUM_PER_PAGE)//每次加载的数据数量15
//距离本页数据几个时候开始加载下一页数据(例如现在加载10个数据,设置prefetchDistance为2,则滑到第八个数据时候开始加载下一页数据).
.setPrefetchDistance(NUM_PER_PAGE)//15
//这里设置是否设置PagedList中的占位符,如果设置为true,我们的数据数量必须固定,由于网络数据数量不固定,所以设置false.
.setEnablePlaceholders(false).setInitialLoadSizeHint(NUM_PER_PAGE)//15
.build()).build();
}
}
3.PagedListAdapter 数据和UI配合的适配器
稍微了解Paging的小伙伴肯定都知道它是配合RecyclerView使用的,这里我们要继承PagedListAdapter,和RecyclerView.Adapter使用方式类似。
首先实现我们的ViewHolder继承RecyclerView.ViewHolder,因为findViewByid()比较耗时,因此我们在ViewHolder的构造方法中获取控件实例,并将其缓存在其中。
class ViewHolder extends RecyclerView.ViewHolder{
TextView tvResourceName;
TextView tvResourceMagnet;
public ViewHolder(@NonNull View itemView) {
super(itemView);
tvResourceName = itemView.findViewById(R.id.resource_name);
tvResourceMagnet = itemView.findViewById(R.id.resource_magnet);
}
}
list_item.xml中的代码如下,主要是展示电影的名称和磁力链接
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="4dp">
<TextView
android:id="@+id/resource_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="start"
android:textColor="#000000"
android:paddingTop="4dp"
android:paddingBottom="4dp"
android:textSize="16sp"
android:textStyle="bold"
android:text="名称"/>
<TextView
android:id="@+id/resource_magnet"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="start"
android:paddingBottom="4dp"
android:layout_marginLeft="20dp"
android:text="magnet:?xt=urn:sha1:AAAAAAAAAAAAAAAAAAAAAAAA"/>
</LinearLayout>
实现ViewHolder之后就要实现onCreateViewHolder()和onBindViewHolder()这两个方法,相信使用过recyclerView的小伙伴对这两个方法都非常熟悉,我在此就不详细介绍了。
重点就是构造方法,我们在调用的时候要实现一个callback。也许大家会很奇怪,怎么找了半天也找不到这个适配器的数据源在哪?其实数据源的加载就是利用这个callback和第一步实现的MyViewModel共同实现的。
public class MyPagedListAdapter extends PagedListAdapter<RawBean,MyPagedListAdapter.MyViewHolder> {
public MyPagedListAdapter(@NonNull DiffUtil.ItemCallback<RawBean> diffCallback) {
super(diffCallback);
}
@NonNull
@Override
public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int position) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item,parent,false);
MyViewHolder holder = new MyViewHolder(view);
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ClipboardManager cm = (ClipboardManager) parent.getContext()
.getSystemService(Context.CLIPBOARD_SERVICE);
ClipData mClipData = ClipData.newPlainText("Label",
"magnet:?xt=urn:sha1:" + getItem(position).getId());
cm.setPrimaryClip(mClipData);
Toast.makeText(parent.getContext(),"复制成功",Toast.LENGTH_SHORT).show();
}
});
return holder;
}
@Override
public void onBindViewHolder(@NonNull MyViewHolder baseViewHolder, int position) {
RawBean bean = getItem(position);
baseViewHolder.tvResourceName.setText(bean.getHighLightName());
baseViewHolder.tvResourceMagnet.setText("magnet:?xt=urn:sha1:" + bean.getId());
}
class MyViewHolder extends RecyclerView.ViewHolder {
TextView tvResourceName;
TextView tvResourceMagnet;
public MyViewHolder(@NonNull View itemView) {
super(itemView);
tvResourceName = itemView.findViewById(R.id.resource_name);
tvResourceMagnet = itemView.findViewById(R.id.resource_magnet);
}
}
}
4.UI展示
在xml布局中放置一个recyclerView
<android.support.v7.widget.RecyclerView
android:id="@+id/rv_search_result"
app:layout_constraintTop_toBottomOf="@id/ll_top_search"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
然后在java文件中获取其实例
private RecyclerView lvSearchResult;
lvSearchResult = view.findViewById(R.id.rv_search_result);
LinearLayoutManager manager = new LinearLayoutManager(mContext);
lvSearchResult.setLayoutManager(manager);
下面adapter的实现和平时不太一样,其中重写的两个方法主要是为了判断
private MyPagedListAdapter adapter;
adapter = new MyPagedListAdapter(new DiffUtil.ItemCallback<RawBean>(){
/**
*
* @param rawBean rawBean The item in the old list.
* @param t1 t1 The item in the new list.
* @return True if the two items represent the same object or false if they are different.
*/
@Override
public boolean areItemsTheSame(@NonNull RawBean rawBean, @NonNull RawBean t1) {
return rawBean.getId().equals(t1.getId());
}
/**
*
* @param rawBean rawBean The item in the old list.
* @param t1 t1 The item in the new list
* @return True if the contents of the items are the same or false if they are different.
*/
@Override
public boolean areContentsTheSame(@NonNull RawBean rawBean, @NonNull RawBean t1) {
return rawBean.equals(t1);
}
});
lvSearchResult.setAdapter(adapter);
你以为已经可以成功显示数据了?不可能的。我们的数据源还没有设置呢。
private MyViewModel myViewModel;
//官网写法MyViewModel viewModel = ViewModelProviders.of(this).get(MyViewModel.class);
//但是我找不到这个ViewModelProviders
//将但是我找不到这个ViewModelProviders变成ViewModelProvider又没有of()方法,因此用下面是方法来实现
myViewModel = ViewModelProvider.AndroidViewModelFactory.getInstance(getActivity().getApplication()).create(MyViewModel.class);
myViewModel.getLiveData().observe(this, new Observer<PagedList<RawBean>>() {
@Override
public void onChanged(@Nullable PagedList<RawBean> rawBeans) {
//调用的submit方法就是PagedListAdapter中的方法。它来对数据进行更新显示。
adapter.submitList(rawBeans);
}
});
这个submitList是最大的两点,adapte中的数据元就是从这里加载的。而且在MyViewModel中调用callback.onResult()方法中的list也是传到这了。怎么样?是不是恍然大悟了。
最后跟大家详细讲解一下我们的是数据源。
通过OkHttp请求,返回的数据格式如下:
使用过Gson解析Json数据的小伙伴一定对这个javabean的结构了如执掌了吧。类中的属性名要和json中的标签名一直,其次每个javabean中都要有getter、setter方法。不同的小伙伴请自行搜索如何利用Gson解析Json数据。
序号 | javabean | json标签 |
---|---|---|
1 | ResultBean | json总标签 |
2 | List<RawBean> | rows |
3 | RawBean | rows中的每一组数据 |
4 |