[Android开发]Android里一个装饰者模式的应用场景——给RecyclerView添加自动加载的状态View

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添加自动加载和显示加载状态的功能
该类的功能是

  1. 加载时,底部增加显示加载中的View
  2. 加载出错或者没有更多数据时,底部显示加载出错和没有更多数据的View,出错时点击错误状态View可以重试
  3. 可以自定义上面三个状态的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();
        }
    }
}

二、接下来就是重点,包装类的实现了,先介绍下实现的逻辑

  1. 同样继承RecyclerView.Adapter,并且在构造器里传入原来用于显示内容数据的InnerAdapter
  2. getItemViewType里增加返回加载状态View的类型
  3. onCreateViewHolder里增加了返回加载状态View的ViewHolder
  4. onBindViewHolder里面增加了对错误状态view的点击动作,使其点击调用重试的方法
  5. onAttachedToRecyclerView里面给recyclerView添加滑动状态的监听,就是前面实现的LoadMoreScrollListener
  6. 还要在getItemCount里根据是否有加载状态的View来返回不同的Item个数
  7. 给出一个OnLoadListener 接口,里面包含加载更多onLoadMore和重试onRetry的两个方法
  8. 给出了一个LoadStatusViewHolder 类,要自定义加载状态的View,继承此类就行
  9. 另外还要让加载状态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);
    }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,539评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,911评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,337评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,723评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,795评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,762评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,742评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,508评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,954评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,247评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,404评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,104评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,736评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,352评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,557评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,371评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,292评论 2 352

推荐阅读更多精彩内容