recyclerView

CRecyclerView使用指南--HeaderView、Em

2017-09-18  本文已影响48人  颤抖的闪电

前言:这两天借鉴了几位大牛的文章,自己撸了一个极度简便的RecyclerView的控件,包括了HeaderView、EmptyView、FootView三个功能,核心类只有一个,如下。按照普通的RecyclerView使用即可.
改版:1、头部、脚部的计数方式改为按可见状态下的计算。
2、增加了内部类WrapContentLinearLayoutManager。

一、核心类介绍

/**
 * @desc
 * @auth 方毅超
 * @time 2017/9/16 16:48
 */

public class CRecyclerView extends RecyclerView {
    HeaderAndFooterWrapAdapter mHeaderAndFooterWrapper;//与头部、脚部相关
    private View emptyView;  //与空布局相关
    //数据观察者,与空布局相关
    final private AdapterDataObserver observer = new AdapterDataObserver() {
        @Override
        public void onChanged() {
            checkIfEmpty();
        }

        @Override
        public void onItemRangeInserted(int positionStart, int itemCount) {
            checkIfEmpty();
        }

        @Override
        public void onItemRangeRemoved(int positionStart, int itemCount) {
            checkIfEmpty();
        }
    };

    public CRecyclerView(Context context) {
        super(context);
    }

    public CRecyclerView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public CRecyclerView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    /**
     * 添加头部
     *
     * @param view
     */
    public void addHeaderView(View view) {
        if (mHeaderAndFooterWrapper == null)
            throw new UnsupportedOperationException("设置头部前,请先setAdapter!");
        mHeaderAndFooterWrapper.addHeaderView(view);
    }

    /**
     * 添加底部
     *
     * @param view
     */
    public void addFootView(View view) {
        if (mHeaderAndFooterWrapper == null)
            throw new UnsupportedOperationException("设置底部前,请先setAdapter!");
        mHeaderAndFooterWrapper.addFootView(view);
    }

    /**
     * 移除头部,0<=position<=头部的个数
     *
     * @param position
     */
    public void removeHeaderView(int position) {
        mHeaderAndFooterWrapper.removeHeaderView(position);
    }

    /**
     * 移除底部,0<=position<=底部的个数
     *
     * @param position
     */
    public void removeFootView(int position) {
        mHeaderAndFooterWrapper.removeFootView(position);
    }

    /**
     * 获取头部
     * @param position
     * @return
     */
    public View getHeaderView(int position) {
        return mHeaderAndFooterWrapper.getHeaderView(position);
    }

    /**
     * 获取脚部
     * @param position
     * @return
     */
    public View getFootView(int position) {
        return mHeaderAndFooterWrapper.getFootView(position);
    }

    /**
     * 设置头部可见状态
     *
     * @param position
     * @param visibility
     */
    public void setHeaderViewVisibility(int position, int visibility) {
        mHeaderAndFooterWrapper.setHeaderViewVisibility(position, visibility);
    }

    /**
     * 设置脚部可见状态
     *
     * @param position
     * @param visibility
     */
    public void setFootViewVisibility(int position, int visibility) {
        mHeaderAndFooterWrapper.setFootViewVisibility(position, visibility);
    }

/*----以上为关于headerView和footView的部分--------------------------------------------------------------------------------------*/

/*----以下为关于emptyView的部分--------------------------------------------------------------------------------------*/

    /**
     * 检查数据是否为空,是否展示emptyView
     */
    private void checkIfEmpty() {
        if (emptyView != null && getAdapter() != null) {
            final boolean emptyViewVisible = getAdapter().getItemCount() == 0;
            emptyView.setVisibility(emptyViewVisible ? VISIBLE : GONE);
            setVisibility(emptyViewVisible ? GONE : VISIBLE);
        }
    }

    //设置没有内容时,提示用户的空布局
    public void setEmptyView(View emptyView) {
        this.emptyView = emptyView;
//        checkIfEmpty();
    }

    /**
     * 返回空布局
     * @return
     */
    public View getEmptyView(){
        return this.emptyView;
    }

    @Override
    public void setAdapter(Adapter adapter) {
        final Adapter oldAdapter = getAdapter();
        if (oldAdapter != null) {
            oldAdapter.unregisterAdapterDataObserver(observer);
        }
        mHeaderAndFooterWrapper = new HeaderAndFooterWrapAdapter(adapter);
        super.setAdapter(mHeaderAndFooterWrapper);
        if (adapter != null) {
            adapter.registerAdapterDataObserver(observer);
        }
        checkIfEmpty();
//        observer.onChanged();
    }

    /**
     * 装饰者模式,
     * 用于处理headerView和FootView,
     * 内部类,也可以将该类独立出来.
     *
     * @param <T>
     */
    private class HeaderAndFooterWrapAdapter<T> extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
        private static final int BASE_ITEM_TYPE_HEADER = 100000;
        private static final int BASE_ITEM_TYPE_FOOTER = 200000;

        private SparseArrayCompat<View> mHeaderViews = new SparseArrayCompat<>();
        private SparseArrayCompat<View> mFootViews = new SparseArrayCompat<>();

        private RecyclerView.Adapter mInnerAdapter;

        public HeaderAndFooterWrapAdapter(RecyclerView.Adapter adapter) {
            mInnerAdapter = adapter;
        }

        private boolean isHeaderViewPos(int position) {
            return position < getVisibleHeadersCount();
        }

        private boolean isFooterViewPos(int position) {
            return position >= getVisibleHeadersCount() + getRealItemCount();
        }


        public void addHeaderView(View view) {
            mHeaderViews.put(mHeaderViews.size() + BASE_ITEM_TYPE_HEADER, view);
        }

        public void addFootView(View view) {
            mFootViews.put(mFootViews.size() + BASE_ITEM_TYPE_FOOTER, view);
        }

        public void removeHeaderView(int position) {
            mHeaderViews.remove(position + BASE_ITEM_TYPE_HEADER);
        }

        public void removeFootView(int position) {
            mFootViews.remove(position + BASE_ITEM_TYPE_FOOTER);
        }

        public View getHeaderView(int position) {
            return mHeaderViews.get(position + BASE_ITEM_TYPE_HEADER, null);
        }

        public View getFootView(int position) {
            return mFootViews.get(position + BASE_ITEM_TYPE_FOOTER, null);
        }

        public void setHeaderViewVisibility(int position, int visibility) {
            if (mFootViews.get(position + BASE_ITEM_TYPE_HEADER) != null) {
                mFootViews.get(position + BASE_ITEM_TYPE_HEADER).setVisibility(visibility);
            }
        }

        public void setFootViewVisibility(int position, int visibility) {
            if (mFootViews.get(position + BASE_ITEM_TYPE_FOOTER) != null) {
                mFootViews.get(position + BASE_ITEM_TYPE_FOOTER).setVisibility(visibility);
            }
        }

//        public int getHeadersCount() {
//            return mHeaderViews.size();
//        }
//
//        public int getFootersCount() {
//            return mFootViews.size();
//        }

        /**
         * 获取可见的头部条数
         *
         * @return
         */
        private int getVisibleHeadersCount() {
            int size = mHeaderViews.size();
            if (size <= 0) return size;
            int visiSize = 0;
            for (int i = 0; i < size; i++) {
                View view = mHeaderViews.get(i + BASE_ITEM_TYPE_HEADER);
                if (view.getVisibility() == View.VISIBLE) visiSize++;
            }
            return visiSize;
        }

        /**
         * 获取可见的脚部条数
         *
         * @return
         */
        private int getVisibleFootersCount() {
            int size = mFootViews.size();
            if (size <= 0) return size;
            int visiSize = 0;
            for (int i = 0; i < size; i++) {
                View view = mFootViews.get(i + BASE_ITEM_TYPE_FOOTER);
                if (view.getVisibility() == View.VISIBLE) visiSize++;
            }
            return visiSize;
        }

        @Override
        public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            if (mHeaderViews.get(viewType) != null) {
                return new SimpleViewHolder(mHeaderViews.get(viewType));

            } else if (mFootViews.get(viewType) != null) {
                return new SimpleViewHolder(mFootViews.get(viewType));
            }
            return mInnerAdapter.onCreateViewHolder(parent, viewType);
        }

        @Override
        public int getItemViewType(int position) {
            if (isHeaderViewPos(position)) {
                return mHeaderViews.keyAt(position);
            } else if (isFooterViewPos(position)) {
                return mFootViews.keyAt(position - getVisibleHeadersCount() - getRealItemCount());
            }
            return mInnerAdapter.getItemViewType(position - getVisibleHeadersCount());
        }

        private int getRealItemCount() {
            return mInnerAdapter.getItemCount();
        }


        @Override
        public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
            if (isHeaderViewPos(position)) {
                return;
            }
            if (isFooterViewPos(position)) {
                return;
            }
            mInnerAdapter.onBindViewHolder(holder, position - getVisibleHeadersCount());
        }

        @Override
        public int getItemCount() {
//            return getHeadersCount() + getFootersCount() + getRealItemCount();
            return getVisibleHeadersCount() + getVisibleFootersCount() + getRealItemCount();

        }

        private class SimpleViewHolder extends RecyclerView.ViewHolder {
            public SimpleViewHolder(View itemView) {
                super(itemView);
            }
        }

        /**
         * 针对GridLayoutManager
         *
         * @param recyclerView
         */
        @Override
        public void onAttachedToRecyclerView(RecyclerView recyclerView) {
            mInnerAdapter.onAttachedToRecyclerView(recyclerView);

            RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
            if (layoutManager instanceof GridLayoutManager) {
                final GridLayoutManager gridLayoutManager = (GridLayoutManager) layoutManager;
                final GridLayoutManager.SpanSizeLookup spanSizeLookup = gridLayoutManager.getSpanSizeLookup();

                gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
                    @Override
                    public int getSpanSize(int position) {
                        int viewType = getItemViewType(position);
                        if (mHeaderViews.get(viewType) != null) {
                            return gridLayoutManager.getSpanCount();
                        } else if (mFootViews.get(viewType) != null) {
                            return gridLayoutManager.getSpanCount();
                        }
                        if (spanSizeLookup != null)
                            return spanSizeLookup.getSpanSize(position);
                        return 1;
                    }
                });
                gridLayoutManager.setSpanCount(gridLayoutManager.getSpanCount());
            }
        }

        /**
         * 对于StaggeredGridLayoutManager
         *
         * @param holder
         */
        @Override
        public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) {
            mInnerAdapter.onViewAttachedToWindow(holder);
            int position = holder.getLayoutPosition();
            if (isHeaderViewPos(position) || isFooterViewPos(position)) {
                ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();

                if (lp != null && lp instanceof StaggeredGridLayoutManager.LayoutParams) {

                    StaggeredGridLayoutManager.LayoutParams p =
                            (StaggeredGridLayoutManager.LayoutParams) lp;

                    p.setFullSpan(true);
                }
            }
        }
    }

    /**
     * 据说
     * RecyclerView Bug:IndexOutOfBoundsException: Inconsistency detected. Invalid view holder adapter的解决方案
     */
    public class WrapContentLinearLayoutManager extends LinearLayoutManager {
        public WrapContentLinearLayoutManager(Context context) {
            super(context);
        }

        public WrapContentLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
            super(context, orientation, reverseLayout);
        }

        public WrapContentLinearLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
            super(context, attrs, defStyleAttr, defStyleRes);
        }

        @Override
        public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
            try {
                super.onLayoutChildren(recycler, state);
            } catch (IndexOutOfBoundsException e) {
                e.printStackTrace();
            }
        }
    }
}

二、使用介绍:

1,布局,(emptyView可以自己定义,也可以是简单的TextView、ImageView等)

<LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical"
            android:background="@color/transparent"
            app:layout_behavior="@string/appbar_scrolling_view_behavior">

            <com.csair.staffservice.view.CRecyclerView
                android:id="@+id/msgRecyclerView"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:background="#f5f5f5"
                android:overScrollMode="never" />

            <com.csair.staffservice.view.EmptyView
                android:id="@+id/msgEmpty"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:background="#f5f5f5"
                android:overScrollMode="never" />
        </LinearLayout>

2,代码使用

 mRecyclerView = (CRecyclerView) view.findViewById(R.id.msgRecyclerView);
 mEmptyView = (View) view.findViewById(R.id.msgEmpty);

//设置layoutManager
mRecyclerView.setLayoutManager(mRecyclerView.new WrapContentLinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, false));

//设置空布局
 mRecyclerView.setEmptyView(mEmptyView);
//设置头部
mRecyclerView.addHeaderView(new HeaderView(getContext()));
//设置脚部
mRecyclerView.addFootView(new NoDataFooterView(getContext()));

这几个布局都可以自由发挥

三、bug介绍

删除元素出现的bug及解决方案:

1、(亲测,不可靠) RecyclerView 删除元素后,点击报 IndexOutOfBoundsException 解决方法,

onBindViewHolder() 方法中的位置参数 position 不是实时更新的,所以在我们删除元素后,item 的 position 没有改变。为了实时获取元素的位置,RecyclerView 为我们提供了 ViewHolder.getAdapterPosition() 方法。
当把上面奔溃的代码中的 position 换成 holder.getAdapterPosition() 就解决了问题。

2、(亲测,不可靠)IndexOutOfBoundsException: Inconsistency detected. Invalid view holder adapter的解决方案,

其实也不是什么解决方法,只是把这个异常捕获了,不让他奔溃了,这个问题的终极解决方案还是得让google去修复。
a、创建一个类LinearLayoutManagerWrapper继承LinearLayoutManager,重写onLayoutChildren方法

public class WrapContentLinearLayoutManager extends LinearLayoutManager {  
    public WrapContentLinearLayoutManager(Context context) {  
        super(context);  
    }  
  
    public WrapContentLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {  
        super(context, orientation, reverseLayout);  
    }  
  
    public WrapContentLinearLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {  
        super(context, attrs, defStyleAttr, defStyleRes);  
    }  
  
    @Override  
    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {  
        try {  
            super.onLayoutChildren(recycler, state);  
        } catch (IndexOutOfBoundsException e) {  
            e.printStackTrace();  
        }  
    }  
}  

b、设置RecyclerView的布局管理为WrapContentLinearLayoutManager对象

mRecyclerView.setLayoutManager(new WrapContentLinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)); 
3、(亲测,相当可靠)positionViewHolder{a1bbfa3 position=2 id=-1, oldPos=-1, pLpos:-1 no parent}

网上查阅下说是原生bug,自定义了线性layout,重写LinearLayoutManagerWrapper.结果发现还是会报红,干脆在clear数据时,recycler_user.removeAllViews();就没遇到报的问题了.在删除元素时,我的真正有效的解决方法是:

try {
        StaffMsgBean.DataBean data = datas.remove(position);
        if (datas.size() <= 0) {
              crv.setFootViewVisibility(0, View.GONE);
        }
        //解决bug的核心
        crv.removeAllViews();//移除所有views
        notifyDataSetChanged();//刷新数据,会触发CRecyclerView中observer#onChanged()
        crv.scrollToPosition(position);//滚动至具体条目位置
        StaffRepo.getInstance().init(act).delete(data.getId());
     } catch (Exception e) {
          // e.printStackTrace();
          ToastUtil.showShort(act, "删除失败!");
     }

最后附上大牛文章
Android 优雅的为RecyclerView添加HeaderView和FooterView
RecyclerView添加EmptyView(空布局)

上一篇下一篇

猜你喜欢

热点阅读