Java中最常见的装饰者模式应用就是IO流的设计了。
先简单回顾一下装饰者模式:
class Father{
void show(){}
}
class A extends Father{
void show(){}
}
class Wrapper extends Father{
Father a;
Wrapper(Father a){
this.a = a;
}
void show(){
//同时在这里动态增加新逻辑
a.show();
}
}
//使用
Father f = new Wrapper(new A());
f.show();
装饰者模式的思想就是在被包装者原有的逻辑上,动态的添加新逻辑
实现和代理模式有些相似,但思想侧重不一样。代理模式侧重控制对被代理者的访问,装饰者模式侧重给被包装者添加新的功能
这里实现一个包装类LoadMoreWrapperAdapter ,给原来的adapter添加自动加载和显示加载状态的功能
该类的功能是
- 加载时,底部增加显示加载中的View
- 加载出错或者没有更多数据时,底部显示加载出错和没有更多数据的View,出错时点击错误状态View可以重试
- 可以自定义上面三个状态的View,实现该类的内部类LoadStatusViewHolder ,并且set进去就好,否则显示默认的
后面会给出自定义状态view的例子,里面还添加了动画
这里的实现主要根据了鸿洋大大的实现来修改,原来实现的地址,接下来我们开始自己实现
一、首先是实现对RecyclerView的滑动到底部的监听,和ListView不同,需要我们自己实现这个LoadMoreScrollListener
public abstract class LoadMoreScrollListener extends RecyclerView.OnScrollListener {
//针对官方给出的三种LayoutManager的类型的标识
private static final int TYPE_LINEAR_LAYOUT = 11;
private static final int TYPE_GRID_LAYOUT = 12;
private static final int TYPE_STAGGERED_GRID_LAYOUT = 13;
private int layoutManagerType = -1;
private int lastVisibleItemPosition;
private int[] lastPositions;
//上次加载开始前的RecyclerView包含数据的数目
private int previousTotal;
private boolean isLoading = true;
public abstract void loadMore();
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
/*
* 其实GridLayoutManager就是继承自LinearLayoutManager,两类的处理可以合并成一起,
* 但便于理解和可读性,进行了分开处理
* */
if(layoutManagerType == -1){
//记录下LayoutManager的类型
if(layoutManager instanceof GridLayoutManager){
layoutManagerType = TYPE_GRID_LAYOUT;
}else if(layoutManager instanceof LinearLayoutManager){
layoutManagerType = TYPE_LINEAR_LAYOUT;
}else if(layoutManager instanceof StaggeredGridLayoutManager){
layoutManagerType = TYPE_STAGGERED_GRID_LAYOUT;
}else {
throw new RuntimeException("Unsupported LayoutManager used. Valid ones are LinearLayoutManager, GridLayoutManager and StaggeredGridLayoutManager");
}
}
//根据LayoutManager的类型找出显示的最后的位置
switch (layoutManagerType){
case TYPE_LINEAR_LAYOUT:
lastVisibleItemPosition = ((LinearLayoutManager)layoutManager).findLastVisibleItemPosition();
break;
case TYPE_GRID_LAYOUT:
lastVisibleItemPosition = ((GridLayoutManager)layoutManager).findLastVisibleItemPosition();
break;
case TYPE_STAGGERED_GRID_LAYOUT:
StaggeredGridLayoutManager staggeredGridLayoutManager = (StaggeredGridLayoutManager) layoutManager;
if(lastPositions == null){
lastPositions = new int[staggeredGridLayoutManager.getSpanCount()];
}
//针对瀑布流布局,需要遍历每一列最后一个,寻找最后的位置
staggeredGridLayoutManager.findLastVisibleItemPositions(lastPositions);
lastVisibleItemPosition = findMax(lastPositions);
break;
}
}
private int findMax(int[] lastPositions){
int max = lastPositions[0];
for (int value : lastPositions){
if(value > max) max = value;
}
return max;
}
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
int visibleItemCount = layoutManager.getChildCount();
int totalItemCount = layoutManager.getItemCount();
if (isLoading) {
//和之前数据的数目进行比较,判断是否加载完毕,重置加载状态
if (totalItemCount > previousTotal) {//加载更多结束
isLoading = false;
previousTotal = totalItemCount;
} else if (totalItemCount < previousTotal) {//用户刷新结束
previousTotal = totalItemCount;
isLoading = false;
}else {
//TODO 目前此类无法对加载失败进行自动处理,需要外部动作
}
}
//RecyclerView滑动停止,若显示到最后的位置,则开始加载
if (!isLoading
&& visibleItemCount > 0
&& totalItemCount - 1 == lastVisibleItemPosition
&& newState == RecyclerView.SCROLL_STATE_IDLE) {
loadMore();
}
}
}
二、接下来就是重点,包装类的实现了,先介绍下实现的逻辑
- 同样继承RecyclerView.Adapter,并且在构造器里传入原来用于显示内容数据的InnerAdapter
- getItemViewType里增加返回加载状态View的类型
- onCreateViewHolder里增加了返回加载状态View的ViewHolder
- onBindViewHolder里面增加了对错误状态view的点击动作,使其点击调用重试的方法
- onAttachedToRecyclerView里面给recyclerView添加滑动状态的监听,就是前面实现的LoadMoreScrollListener
- 还要在getItemCount里根据是否有加载状态的View来返回不同的Item个数
- 给出一个OnLoadListener 接口,里面包含加载更多onLoadMore和重试onRetry的两个方法
- 给出了一个LoadStatusViewHolder 类,要自定义加载状态的View,继承此类就行
- 另外还要让加载状态View的显示独占一行,但StaggeredGridLayoutManager和GridLayoutManager的实现不同,需要分别去处理,具体见代码
具体代码如下:
public class LoadMoreWrapperAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
//数字大点,避免和被包装的adapter里的出现重复
public static final int ITEM_TYPE_LOAD_FAILED_VIEW = Integer.MAX_VALUE - 1;
public static final int ITEM_TYPE_NO_MORE_VIEW = Integer.MAX_VALUE - 2;
public static final int ITEM_TYPE_LOAD_MORE_VIEW = Integer.MAX_VALUE - 3;
public static final int ITEM_TYPE_NO_VIEW = Integer.MAX_VALUE - 4;//不展示footer view
private RecyclerView.Adapter<RecyclerView.ViewHolder> mInnerAdapter;
private RecyclerView.ViewHolder mLoadMoreHolder,mLoadFailedHolder,mLoadNoMoreHolder;
private int mCurrentItemType = ITEM_TYPE_LOAD_MORE_VIEW;
private LoadMoreScrollListener mLoadMoreScrollListener;
private boolean isLoadError = false;
private boolean isHaveStatesView = true;
private boolean isLoadComplete = false;
private boolean isLoading = true;
public LoadMoreWrapperAdapter(RecyclerView.Adapter<RecyclerView.ViewHolder> adapter) {
this.mInnerAdapter = adapter;
//创建滑动到底部的加载监听,在onAttachedToRecyclerView里给RecyclerView添加上
mLoadMoreScrollListener = new LoadMoreScrollListener() {
@Override
public void loadMore() {
if (mOnLoadListener != null) {
if (!isLoadError && !isLoadComplete && !isLoading) {
showLoadMore();
mOnLoadListener.onLoadMore();
}
}
}
};
}
public void showLoadMore() {
mCurrentItemType = ITEM_TYPE_LOAD_MORE_VIEW;
isLoadError = false;
isHaveStatesView = true;
isLoading = true;
notifyItemChanged(getItemCount());
}
public void showLoadError() {
mCurrentItemType = ITEM_TYPE_LOAD_FAILED_VIEW;
isLoadError = true;
isHaveStatesView = true;
isLoading = false;
notifyItemChanged(getItemCount());
}
public void showLoadComplete() {
mCurrentItemType = ITEM_TYPE_NO_MORE_VIEW;
isLoadError = false;
isHaveStatesView = true;
isLoadComplete = true;
isLoading = false;
notifyItemChanged(getItemCount());
}
public void disableLoadMore() {
mCurrentItemType = ITEM_TYPE_NO_VIEW;
isHaveStatesView = false;
isLoading = false;
notifyDataSetChanged();
}
public void setLoadStatusViewHolder(RecyclerView.ViewHolder loadMore, RecyclerView.ViewHolder loadFailed, RecyclerView.ViewHolder loadNoMore){
if(loadMore != null) mLoadMoreHolder = loadMore;
if(loadFailed != null) mLoadFailedHolder = loadFailed;
if(loadNoMore != null) mLoadNoMoreHolder = loadNoMore;
}
private RecyclerView.ViewHolder getLoadMoreViewHolder(Context context) {
if(mLoadMoreHolder == null){
mLoadMoreHolder = LoadStatusViewHolder.getDefaultHolder(context,"正在加载中");
}
return mLoadMoreHolder;
}
private RecyclerView.ViewHolder getLoadFailedViewHolder(Context context) {
if(mLoadFailedHolder == null){
mLoadFailedHolder = LoadStatusViewHolder.getDefaultHolder(context,"加载出错,点击重试");
}
return mLoadFailedHolder;
}
private RecyclerView.ViewHolder getNoMoreViewHolder(Context context) {
if(mLoadNoMoreHolder == null){
mLoadMoreHolder = LoadStatusViewHolder.getDefaultHolder(context,"——end——");
}
return mLoadNoMoreHolder;
}
@Override
public int getItemViewType(int position) {
if (position == getItemCount() - 1 && isHaveStatesView) {
return mCurrentItemType;
}
return mInnerAdapter.getItemViewType(position);
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == ITEM_TYPE_NO_MORE_VIEW) {
return getNoMoreViewHolder(parent.getContext());
} else if (viewType == ITEM_TYPE_LOAD_MORE_VIEW) {
return getLoadMoreViewHolder(parent.getContext());
} else if (viewType == ITEM_TYPE_LOAD_FAILED_VIEW) {
return getLoadFailedViewHolder(parent.getContext());
}
return mInnerAdapter.onCreateViewHolder(parent, viewType);
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (holder.getItemViewType() == ITEM_TYPE_LOAD_FAILED_VIEW
&& holder instanceof LoadStatusViewHolder) {
((LoadStatusViewHolder)holder).itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mOnLoadListener != null) {
mOnLoadListener.onRetry();
showLoadMore();
}
}
});
return;
}
if (!isFooterType(holder.getItemViewType())){
mInnerAdapter.onBindViewHolder(holder, position);
}
}
@Override
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
mInnerAdapter.onAttachedToRecyclerView(recyclerView);
//注意 recyclerView setAdapter的时候就会回调这方法,要是setLayoutManager不在之前设置,这里getLayoutManager就为null
RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
if(layoutManager instanceof GridLayoutManager){
//针对GridLayoutManager 的设置,让加载状态的View独占一行
final GridLayoutManager gridLayoutManager = (GridLayoutManager)layoutManager;
final GridLayoutManager.SpanSizeLookup oldSizeLookup = gridLayoutManager.getSpanSizeLookup();
gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
if(position == getItemCount() - 1 && isHaveStatesView){
return gridLayoutManager.getSpanCount();
}
if(oldSizeLookup != null){
return oldSizeLookup.getSpanSize(position);
}
return 1;
}
});
}
recyclerView.addOnScrollListener(mLoadMoreScrollListener);
}
@Override
public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) {
mInnerAdapter.onViewAttachedToWindow(holder);
if (holder.getLayoutPosition() == getItemCount() - 1 && isHaveStatesView) {
//针对StaggeredGridLayoutManager的设置,让加载状态的View独占一行
ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
if (lp != null
&& lp instanceof StaggeredGridLayoutManager.LayoutParams) {
StaggeredGridLayoutManager.LayoutParams p = (StaggeredGridLayoutManager.LayoutParams) lp;
p.setFullSpan(true);
}
}
}
@Override
public int getItemCount() {
return mInnerAdapter.getItemCount() + (isHaveStatesView ? 1 : 0);
}
public boolean isFooterType(int type) {
return type == ITEM_TYPE_NO_VIEW
|| type == ITEM_TYPE_LOAD_FAILED_VIEW
|| type == ITEM_TYPE_NO_MORE_VIEW
|| type == ITEM_TYPE_LOAD_MORE_VIEW;
}
public interface OnLoadListener {
void onRetry();
void onLoadMore();
}
private OnLoadListener mOnLoadListener;
public LoadMoreWrapperAdapter setOnLoadListener(OnLoadListener onLoadListener) {
mOnLoadListener = onLoadListener;
return this;
}
/*
*加载状态的ViewHolder的父类,需要自定义加载状态的viewHolder请继承该类
*
**/
public static class LoadStatusViewHolder extends RecyclerView.ViewHolder{
View itemView;
public LoadStatusViewHolder(View itemView) {
super(itemView);
this.itemView = itemView;
}
public static RecyclerView.ViewHolder getDefaultHolder(Context context,String text){
TextView view = new TextView(context);
view.setLayoutParams(new ViewGroup.LayoutParams(MATCH_PARENT, WRAP_CONTENT));
view.setPadding(20, 20, 20, 20);
view.setText(text);
view.setGravity(Gravity.CENTER);
return new LoadStatusViewHolder(view);
}
}
}
三、自定义加载状态的View
public class LoadMoreViewHolder extends LoadMoreWrapperAdapter.LoadStatusViewHolder {
private LoadMoreViewHolder(View view) {
super(view);
}
public static LoadMoreViewHolder newInstance(Context context){
View view = LayoutInflater.from(context).inflate(R.layout.holder_load_more,null,false);
//因为inflater时传了parent为null,match_parent的属性失效了,在这里设置一遍
view.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
//添加一个旋转动画
final ObjectAnimator animator = ObjectAnimator.ofFloat(view.findViewById(R.id.iv_load),"rotation",0f,360f);
animator.setDuration(1000);
animator.setInterpolator(new LinearInterpolator());
animator.setRepeatMode(ValueAnimator.RESTART);
animator.setRepeatCount(ValueAnimator.INFINITE);
view.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
@Override
public void onViewAttachedToWindow(View v) {
if(!animator.isRunning())animator.start();
}
@Override
public void onViewDetachedFromWindow(View v) {
//当view离开屏幕后,停止动画,防止内存泄漏
if(animator.isRunning())animator.cancel();
}
});
return new LoadMoreViewHolder(view);
}
}
四、简单的使用例子,这里给的是思路,是代码片段
//ContentAdapter,一般展示数据的adapter
moreAdapter = new LoadMoreWrapperAdapter(new ContentAdapter(mItems));
moreAdapter.setLoadStatusViewHolder(LoadMoreViewHolder.newInstance(view.getContext()),null,null);
moreAdapter.setOnLoadListener(new LoadMoreWrapperAdapter.OnLoadListener() {
@Override
public void onRetry() {
//重试加载数据的逻辑
loadData(pageNo);
}
@Override
public void onLoadMore() {
//加载数据的逻辑
loadData(pageNo);
}
});
//先设置LayoutManager,否则在adapter的onAttachedToRecyclerView里,recyclerView.getLayoutManager()会为空
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
recyclerView.setAdapter(moreAdapter);
//这里是配合着Rxjava来用,方法的调用顺序是onSubscribe->onNext->onError/onComplete
private void loadData(final int page){
MainApplication.getInstance().getPresenter(DataPresenter.class).loadCategoryContents(new Observer<List<Item>>() {
@Override
public void onSubscribe(Disposable d) {
pageNo++//增加页码
}
@Override
public void onNext(List<Item> items) {
if(items == null || items.size() == 0 ){
moreAdapter.showLoadComplete();//数据为空,没有更多数据了,显示没有更多数据的状态View
}else {
mItems.addAll(items);
moreAdapter.disableLoadMore();//加载完成,隐藏掉加载时的状态View
}
}
@Override
public void onError(Throwable e) {
pageNo--;//加载出错,页码还原
moreAdapter.showLoadError();//加载出错,显示加载出错的状态View
}
@Override
public void onComplete() {
}
},size,page);
}