1、简介
RecyclerView的刷新与加载样式用到第三方库,添加依赖:
implementation 'com.jcodecraeer:xrecyclerview:1.5.9'
在使用RecyclerView来展示网络请求的数据的时候,常用到下拉刷新和加载更多,直接上效果图:
- 项目中使用的接口来源:http://gank.io/api
- 以下介绍并非图中体现的完整实现,部分Activity与Fragment等代码未贴出
- 项目源码地址:https://github.com/fr1014/LoadAndRefresh
- 项目实战:https://github.com/fr1014/konwledge
2、布局
2.1 gson数据解析
public class ItemBean {
/**
* _id : 5d2f24d19d2122031ea5223f
* createdAt : 2019-07-17T13:38:25.504Z
* desc : 状态切换,让View状态的切换和Activity彻底分离开。用builder模式来自由的添加需要的状态View,可以设置有数据,数据为空,加载数据错误,网络错误,加载中等多种状态,并且支持自定义状态的布局。
* images : ["http://img.gank.io/4b63f35c-f631-417b-9d88-916e70901634","http://img.gank.io/b7de0a96-0023-4c4c-b120-9fcbf8c6046c","http://img.gank.io/45f8f9b5-0be1-410b-a50d-b8d72046abb2","http://img.gank.io/89a0e411-1049-4322-80a1-9bd8d6dfb35e","http://img.gank.io/f3e3b597-0317-45cd-8288-d866888ce297"]
* publishedAt : 2019-07-17T13:40:33.502Z
* source : web
* type : Android
* url : https://github.com/yangchong211/YCStateLayout/blob/master/README_CH.md
* used : true
* who : 潇湘剑雨
*/
private String _id;
private String createdAt;
private String desc;
private String publishedAt;
private String source;
private String type;
private String url;
private boolean used;
private String who;
private List images;
public String get_id() {
return _id;
}
public void set_id(String _id) {
this._id = _id;
}
public String getCreatedAt() {
return createdAt;
}
public void setCreatedAt(String createdAt) {
this.createdAt = createdAt;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
public String getPublishedAt() {
return publishedAt;
}
public void setPublishedAt(String publishedAt) {
this.publishedAt = publishedAt;
}
public String getSource() {
return source;
}
public void setSource(String source) {
this.source = source;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public boolean isUsed() {
return used;
}
public void setUsed(boolean used) {
this.used = used;
}
public String getWho() {
return who;
}
public void setWho(String who) {
this.who = who;
}
public List getImages() {
return images;
}
public void setImages(List images) {
this.images = images;
}
}
public class CategoryListBean {
private boolean isError;
private List results;
public boolean isError() {
return isError;
}
public List getResults() {
return results;
}
public void setResults(List results) {
this.results = results;
}
}
2.2 数据源处理
public class CategoryBean extends BaseObservable {
private ResultsBean bean;
public ObservableField imageViewVisibility;
private int mPosition;
public CategoryBean(ResultsBean bean, int position) {
this.bean = bean;
this.mPosition = position;
initData();
}
public String getDesc() {
return bean.getDesc();
}
public List getImageUrls() {
return bean.getImages();
}
public String getImageUrl() {
if (bean.getImages()!=null)
return bean.getImages().get(0);
else
imageViewVisibility.set(View.GONE);
return null;
}
public Date getDate(){
return Utils.StrToDate(bean.getPublishedAt());
}
public String getUrl(){
return bean.getUrl();
}
public String getType(){
return bean.getType();
}
private void initData() {
imageViewVisibility = new ObservableField<>();
imageViewVisibility.set(View.VISIBLE);
}
}
2.3 RecyclerView的布局
fragment_classified_child.xml
2.4 RecyclerView中的item布局
item_categorybean.xml
3、接口实现
3.1 监听网络请求
在请求网络时,负责将各种状态下的信息传出
public interface BaseLoadListener {
/**
* 加载数据成功
*
* @param list
*/
void loadSuccess(List list);
/**
* 加载失败
*
* @param message
*/
void loadFailure(String message);
/**
* 开始加载
*/
void loadStart();
/**
* 加载结束
*/
void loadComplete();
}
3.2 UI消息展示
负责将加载状态展示给用户
public interface IBaseView {
/**
* 开始加载
*
* @param loadType 加载的类型 0:第一次记载 1:下拉刷新 2:上拉加载更多
*/
void loadStart(int loadType);
/**
* 加载完成
*/
void loadComplete();
/**
* 加载失败
*
* @param message
*/
void loadFailure(String message);
}
4、适配器
4.1 适配器的基类实现
RecyclerView的ViewHolder基类
public class BaseViewHolder extends RecyclerView.ViewHolder {
private VB mBinding;
public BaseViewHolder(VB binding) {
super(binding.getRoot());
this.mBinding = binding;
}
public VB getBinding(){
return mBinding;
}
}
RecyclerView适配器的通用基类
public abstract class RVBaseAdapter extends RecyclerView.Adapter {
protected Context mContext;
protected List mList; // 数据源
protected LayoutInflater inflater;
public RVBaseAdapter(Context context) {
this.mContext = context;
this.mList = new ArrayList<>();
inflater = LayoutInflater.from(context);
}
@Override
public int getItemCount() {
return mList.size();
}
@NotNull
@Override
public VH onCreateViewHolder(@NotNull ViewGroup parent, int viewType) {
return onCreateVH(parent, viewType);
}
@Override
public void onBindViewHolder(@NotNull VH holder, int position) {
onBindVH(holder, position);
}
/**
* 创建 View Holder
*
* @param parent parent
* @param viewType item type
* @return view holder
*/
public abstract VH onCreateVH(ViewGroup parent, int viewType);
/**
* 绑定 View Holder
*
* @param holder view holder
* @param position position
*/
public abstract void onBindVH(VH holder, int position);
/**
* 刷新数据
*
* @param data 数据源
*/
public void refreshData(List data) {
mList.clear();
mList.addAll(data);
notifyDataSetChanged();
}
/**
* 加载更多
*
* @param data 加载的新数据
*/
public void loadMoreData(List data) {
mList.addAll(data);
notifyDataSetChanged();
}
}
4.2 适配器的实现
RecyclerView的适配器
public class RVCategoryAdapter extends RVBaseAdapter> {
public RVCategoryAdapter(Context context) {
super(context);
}
@Override
public BaseViewHolder onCreateVH(ViewGroup parent, int viewType) {
ViewDataBinding dataBinding = DataBindingUtil.inflate(inflater, R.layout.item_categorybean,parent,false);
return new BaseViewHolder<>((ItemCategorybeanBinding) dataBinding);
}
@Override
public void onBindVH(BaseViewHolder holder, int position) {
ItemCategorybeanBinding binding = holder.getBinding();
CategoryBean bean = new CategoryBean(mList.get(position), position);
binding.setVariable(BR.categorybean,bean);
binding.executePendingBindings();
}
}
5、model类
网络请求的部分封装这里就不详写了,文末有项目源码地址,仅供参考。
//获取分类干货
@GET("data/{category}/{count}/{page}")
Observable>> getCategoryBean(@Path("category") String category, @Path("count") int count, @Path("page") int page);
public class CategoryModel {
public static void getCategoryListBean(String category, int count, int page, BaseLoadListener loadListener) {
RetrofitManager
.getRequest()
.getCategoryBean(category, count, page)
.compose(SchedulerProvider.getInstance().applySchedulers())
.subscribe(new DisposableObserver>() {
@Override
protected void onStart() {
loadListener.loadStart();
}
@Override
public void onNext(Response categoryListBeanResponse) {
if (categoryListBeanResponse != null && !categoryListBeanResponse.body().isError()){
loadListener.loadSuccess(categoryListBeanResponse.body().getResults());
}
}
@Override
public void onError(Throwable e) {
loadListener.loadFailure(e.getMessage());
}
@Override
public void onComplete() {
loadListener.loadComplete();
}
});
}
}
5、ViewModel类
5.1 常量
public class MainConstant {
public class LoadData{
public static final int FIRST_LOAD = 0; //首次加载
public static final int REFRESH = 1; //下拉刷新
public static final int LOAD_MORE = 2; //上拉加载更多
}
}
5.2 ViewModel基类
public abstract class BaseViewModel {
protected T adapter;
public BaseViewModel(T adapter){
this.adapter = adapter;
}
protected abstract void getData();
}
5.3 ViewModel的实现
继承实现网络请求监听接口,判断UI的不同状态,做出第一次进入界面、刷新数据、加载更多的不同操作。
public class CategoryViewModel extends BaseViewModel implements BaseLoadListener {
private ICategoryView mICategoryView;
private String mCategory;
private int mCurrPage = 1; //当前页面
private int mLoadType; //加载数据的类型
public CategoryViewModel(ICategoryView iCategoryView,RVCategoryAdapter adapter,String category) {
super(adapter);
this.mICategoryView = iCategoryView;
this.mCategory = category;
getData();
}
/**
* 第一次获取分类数据
*/
@Override
protected void getData() {
mLoadType = MainConstant.LoadData.FIRST_LOAD;
CategoryModel.getCategoryListBean(mCategory,10,mCurrPage,this);
}
public void loadRefreshData(){
mLoadType = MainConstant.LoadData.REFRESH;
mCurrPage = 1;
CategoryModel.getCategoryListBean(mCategory,30,mCurrPage,this);
}
public void loadMoreData(){
mLoadType = MainConstant.LoadData.LOAD_MORE;
mCurrPage++;
CategoryModel.getCategoryListBean(mCategory,30,mCurrPage,this);
}
@Override
public void loadSuccess(List list) {
if (mCurrPage > 1){
//上拉加载的数据
adapter.loadMoreData(list);
}else {
//第一次加载或者下拉刷新的数据
adapter.refreshData(list);
}
}
@Override
public void loadFailure(String message) {
//加载失败后的提示
if (mCurrPage > 1){
//加载失败需要回到加载前的页面
mCurrPage--;
}
mICategoryView.loadFailure(message);
}
@Override
public void loadStart() {
mICategoryView.loadStart(mLoadType);
}
@Override
public void loadComplete() {
mICategoryView.loadComplete();
}
}
6、Fragment
6.1 Fragment基类
public abstract class BaseFragment extends Fragment {
protected V binding;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
binding = DataBindingUtil.inflate(inflater, initContentView(inflater, container, savedInstanceState), container, false);
return binding.getRoot();
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
initViewDataBinding();
//页面数据初始化方法
initData();
}
public abstract void initData();
private void initViewDataBinding() {
}
public abstract int initContentView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState);
}
6.2、Fragment的实现
这里的仅为ClassifiedChildFragment的代码,Activity及ClassifiedChildFragment的父布局Fragment都未贴出。
public class ClassifiedChildFragment extends BaseFragmentVM implements ICategoryView, XRecyclerView.LoadingListener{
private String mClassified;
public ClassifiedChildFragment(String classified){
this.mClassified = classified;
}
@Override
public int initContentView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return R.layout.fragment_classified_child;
}
@Override
public void initData() {
binding.recyclerView.setRefreshProgressStyle(ProgressStyle.BallClipRotate); //设置下拉刷新的样式
binding.recyclerView.setLoadingMoreProgressStyle(ProgressStyle.BallClipRotate); //设置上拉加载更多的样式
binding.recyclerView.setArrowImageView(R.mipmap.pull_down_arrow);
binding.recyclerView.setLoadingListener(this);
LinearLayoutManager layoutManager = new LinearLayoutManager(getContext());
binding.recyclerView.setLayoutManager(layoutManager);
RVCategoryAdapter mRVCategoryAdapter = new RVCategoryAdapter(getContext());
binding.recyclerView.setAdapter(mRVCategoryAdapter);
viewModel = new CategoryViewModel(this, mRVCategoryAdapter, mClassified);
}
@Override
public void onRefresh() {
//下拉刷新
viewModel.loadRefreshData();
}
@Override
public void onLoadMore() {
//上拉加载更多
viewModel.loadMoreData();
}
@Override
public void loadStart(int loadType) {
if (loadType == FIRST_LOAD) {
DialogHelper.getInstance().show(getContext(), "加载中...");
}
}
@Override
public void loadComplete() {
DialogHelper.getInstance().close();
binding.recyclerView.refreshComplete();
binding.recyclerView.loadMoreComplete();
}
@Override
public void loadFailure(String message) {
DialogHelper.getInstance().close();
binding.recyclerView.loadMoreComplete();
binding.recyclerView.refreshComplete();
Utils.ToastShort(getContext(), message);
}
}
6.3 DialogHelper
public class DialogHelper {
public static DialogHelper getInstance() {
return LoadDialogHolder.instance;
}
private static class LoadDialogHolder {
static DialogHelper instance = new DialogHelper();
}
/**
* 展示加载框
*
* @param context context
* @param msg 加载信息
*/
public void show(Context context, String msg) {
close();
createDialog(context, msg);
if (progressDialog != null && !progressDialog.isShowing()) {
progressDialog.show();
}
}
/**
* 关闭加载框
*/
public void close() {
if (progressDialog != null && progressDialog.isShowing()) {
progressDialog.dismiss();
}
}
/**
* progressDialog
*/
private ProgressDialog progressDialog;
/**
* 创建加载框
*
* @param context context
* @param msg msg
*/
private void createDialog(Context context, String msg) {
progressDialog = new ProgressDialog(context);
progressDialog.setCancelable(false);
progressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
progressDialog.setMessage(msg);
progressDialog.setOnCancelListener(dialog -> progressDialog = null);
}
}
7、项目源码地址
https://github.com/fr1014/LoadAndRefresh