美文网首页Android知识手机移动程序开发Android进阶之旅
Android里一个装饰者模式的应用场景——给RecyclerV

Android里一个装饰者模式的应用场景——给RecyclerV

作者: clam314 | 来源:发表于2017-04-02 02:20 被阅读854次

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);
    }

相关文章

网友评论

    本文标题:Android里一个装饰者模式的应用场景——给RecyclerV

    本文链接:https://www.haomeiwen.com/subject/kppwottx.html