美文网首页recyclerView
CRecyclerView使用指南--HeaderView、Em

CRecyclerView使用指南--HeaderView、Em

作者: 颤抖的闪电 | 来源:发表于2017-09-18 16:42 被阅读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(空布局)

    相关文章

      网友评论

        本文标题:CRecyclerView使用指南--HeaderView、Em

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