美文网首页Android-RecyclerViewandroid实用技术recyclerView
RecyclerView更全解析之 - 为它优雅的添加头部和底部

RecyclerView更全解析之 - 为它优雅的添加头部和底部

作者: 红橙Darren | 来源:发表于2017-01-16 09:32 被阅读2956次

    1.概述


    上一期的RecyclerView更全解析之 - 打造通用的万能Adapter,解决了几个坑。那么这一期我们来动态为RecyclerView去加载头部和底部,为上一期的RecyclerView列表数据添加广告轮播图,至于广告轮播大家可以看一下这一期 Android无限广告轮播 - 自定义BannerView,这里我就不多讲了,直接拿过来用。
      
      视频讲解http://pan.baidu.com/s/1slo5u3b

    相关文章:
      
      RecyclerView更全解析之 - 基本使用和分割线解析
      
      RecyclerView更全解析之 - 打造通用的万能Adapter
      
      RecyclerView更全解析之 - 为它优雅的添加头部和底部
      
      RecyclerView更全解析之 - 打造通用的下拉刷新上拉加载
      
      RecyclerView更全解析之 - 仿支付宝侧滑删除和拖动排序
      
      
      

    这里写图片描述

    2.基本思路


    我们开始接触RecyclerView的时候肯定接触过ListView,这个我们再熟悉不过了。后来我们用着用着RecyclerView发现它可能有很多坑的地方可能大家觉得它不如ListView,其实我们发现后来出的这些新的控件其实给了用户更多的自定义,更多的完全由开发者去实现这其实也是有利于扩展的。我们自己写代码理因也如此,到后面大家也会发现我们要做一些高级功能如仿QQ侧滑删除淘宝拖拽排序会 so easy,我们到后面再唠。

    为了RecyclerView添加头部和底部,网上很多可以说是各显神通千奇百怪,其实我们ListView就有addHeaderView(View view)方法。所以我也想用recyclerView.addHeaderView(),但是锤子发现并没有这个方法直接就报错,所以只好决定仿照Google的ListView的源码去写了。

    3.基本实现


    3.1 瞅瞅ListView的addHeaderView

    public void addHeaderView(View v, Object data, boolean isSelectable) {
            // 一些基本信息封装
            final FixedViewInfo info = new FixedViewInfo();
            info.view = v;
            info.data = data;
            info.isSelectable = isSelectable;
            mHeaderViewInfos.add(info);
            mAreAllItemsSelectable &= isSelectable;
            
            // 首先判断是不是空,我所以前如果没设置Adapter就是添加不了头部咯  Soga
            // Wrap the adapter if it wasn't already wrapped.
            if (mAdapter != null) {
                // 判断有没有被包裹过
                if (!(mAdapter instanceof HeaderViewListAdapter)) {
                    mAdapter = new HeaderViewListAdapter(mHeaderViewInfos,
                        mFooterViewInfos, mAdapter);
                }
    
                // In the case of re-adding a header view, or adding one later on,
                // we need to notify the observer.
                if (mDataSetObserver != null) {
                    // 观察者模式不多写了
                    mDataSetObserver.onChanged();
                }
            }
        }
    

    接下来我就挑一下关键代码,某些就省略了,强迫症自己去阅读源码吧

    public class HeaderViewListAdapter implements WrapperListAdapter, Filterable {
        
        private final ListAdapter mAdapter;
    
        
        // These two ArrayList are assumed to NOT be null.
        // They are indeed created when declared in ListView and then shared.
        // 存放头部和底部集合
        ArrayList<ListView.FixedViewInfo> mHeaderViewInfos;
        ArrayList<ListView.FixedViewInfo> mFooterViewInfos;
    
        public HeaderViewListAdapter(ArrayList<ListView.FixedViewInfo> headerViewInfos,
                                     ArrayList<ListView.FixedViewInfo> footerViewInfos,
                                     ListAdapter adapter) {
            // 这才是最原始的列表Adapter
            mAdapter = adapter;
            // ......
        }
    
        // 获取条数
        public int getCount() {
            if (mAdapter != null) {
                // 三者相加 = 底部条数 + 头部条数 + Adapter的条数
                return getFootersCount() + getHeadersCount() + mAdapter.getCount();
            } else {
                return getFootersCount() + getHeadersCount();
            }
        }
    
        // getView方法这个应该都很熟悉
        public View getView(int position, View convertView, ViewGroup parent) {
            // Header (negative positions will throw an IndexOutOfBoundsException)
            // 根据当前位置判断是不是头部
            int numHeaders = getHeadersCount();
            if (position < numHeaders) {
                // 如果是头部直接返回传递过来的View
                return mHeaderViewInfos.get(position).view;
            }
    
            // Adapter部分
            final int adjPosition = position - numHeaders;
            int adapterCount = 0;
            if (mAdapter != null) {
                adapterCount = mAdapter.getCount();
                if (adjPosition < adapterCount) {
                    return mAdapter.getView(adjPosition, convertView, parent);
                }
            }
            
            // 底部部分
            // Footer (off-limits positions will throw an IndexOutOfBoundsException)
            return mFooterViewInfos.get(adjPosition - adapterCount).view;
        }
    
        // 获取View的类型这个RecyclerView也雷同
        public int getItemViewType(int position) {
            int numHeaders = getHeadersCount();
            if (mAdapter != null && position >= numHeaders) {
                int adjPosition = position - numHeaders;
                int adapterCount = mAdapter.getCount();
                if (adjPosition < adapterCount) {
                    return mAdapter.getItemViewType(adjPosition);
                }
            }
            return AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER;
        }
    }   
    

    其实关键源码也不多,还有英文注释不像上一次读源码完全没有注释,感觉那Google工程师有点打酱油。到这里也知道了,其实ListView并不是支持直接添加头部和底部,而是在内部写了一个包裹类,做了一系列的处理才可以,那么接下来我们也就参照这种方式,因为这估计是最权威的代码了就模仿你了。

    3.2 先构建WrapRecyclerAdapter

    /**
     * Created by Darren on 2016/12/29.
     * Email: 240336124@qq.com
     * Description: 可以添加头部和底部的Adapter
     */
    public class WrapRecyclerAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
        private final static String TAG = "WrapRecyclerAdapter";
        // 用来存放底部和头部View的集合  比Map要高效一些
        // 可以点击进入看一下官方的解释
        /**
         * SparseArrays map integers to Objects.  Unlike a normal array of Objects,
         * there can be gaps in the indices.  It is intended to be more memory efficient
         * than using a HashMap to map Integers to Objects, both because it avoids
         * auto-boxing keys and its data structure doesn't rely on an extra entry object
         * for each mapping.
         */
        private SparseArray<View> mHeaderViews;
        private SparseArray<View> mFooterViews;
    
        // 基本的头部类型开始位置  用于viewType
        private static int BASE_ITEM_TYPE_HEADER = 10000000;
        // 基本的底部类型开始位置  用于viewType
        private static int BASE_ITEM_TYPE_FOOTER = 20000000;
    
        // 列表的Adapter
        private RecyclerView.Adapter mAdapter;
    
        public WrapRecyclerAdapter(RecyclerView.Adapter adapter) {
            this.mAdapter = adapter;
            mHeaderViews = new SparseArray<>();
            mFooterViews = new SparseArray<>();
        }
    
        @Override
        public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    
            // viewType 可能就是 SparseArray 的key
            if (isHeaderViewType(viewType)) {
                View headerView = mHeaderViews.get(viewType);
                return createHeaderFooterViewHolder(headerView);
            }
    
            if (isFooterViewType(viewType)) {
                View footerView = mFooterViews.get(viewType);
                return createHeaderFooterViewHolder(footerView);
            }
            return mAdapter.onCreateViewHolder(parent, viewType);
        }
    
        /**
         * 是不是底部类型
         */
        private boolean isFooterViewType(int viewType) {
            int position = mFooterViews.indexOfKey(viewType);
            return position >= 0;
        }
    
        /**
         * 创建头部或者底部的ViewHolder
         */
        private RecyclerView.ViewHolder createHeaderFooterViewHolder(View view) {
            return new RecyclerView.ViewHolder(view) {
    
            };
        }
    
        /**
         * 是不是头部类型
         */
        private boolean isHeaderViewType(int viewType) {
            int position = mHeaderViews.indexOfKey(viewType);
            return position >= 0;
        }
    
        @Override
        public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
            if (isHeaderPosition(position) || isFooterPosition(position)) {
                return;
            }
            // 计算一下位置
            position = position - mHeaderViews.size();
            mAdapter.onBindViewHolder(holder, position);
        }
    
        @Override
        public int getItemViewType(int position) {
            if (isHeaderPosition(position)) {
                // 直接返回position位置的key
                return mHeaderViews.keyAt(position);
            }
            if (isFooterPosition(position)) {
                // 直接返回position位置的key
                position = position - mHeaderViews.size() - mAdapter.getItemCount();
                return mFooterViews.keyAt(position);
            }
            // 返回列表Adapter的getItemViewType
            position = position - mHeaderViews.size();
            return mAdapter.getItemViewType(position);
        }
    
        /**
         * 是不是底部位置
         */
        private boolean isFooterPosition(int position) {
            return position >= (mHeaderViews.size() + mAdapter.getItemCount());
        }
    
        /**
         * 是不是头部位置
         */
        private boolean isHeaderPosition(int position) {
            return position < mHeaderViews.size();
        }
    
        @Override
        public int getItemCount() {
            // 条数三者相加 = 底部条数 + 头部条数 + Adapter的条数
            return mAdapter.getItemCount() + mHeaderViews.size() + mFooterViews.size();
        }
    
        /**
         * 获取列表的Adapter
         */
        private RecyclerView.Adapter getAdapter() {
            return mAdapter;
        }
    
        // 添加头部
        public void addHeaderView(View view) {
            int position = mHeaderViews.indexOfValue(view);
            if (position < 0) {
                mHeaderViews.put(BASE_ITEM_TYPE_HEADER++, view);
            }
            notifyDataSetChanged();
        }
    
        // 添加底部
        public void addFooterView(View view) {
            int position = mFooterViews.indexOfValue(view);
            if (position < 0) {
                mFooterViews.put(BASE_ITEM_TYPE_FOOTER++, view);
            }
            notifyDataSetChanged();
        }
    
        // 移除头部
        public void removeHeaderView(View view) {
            int index = mHeaderViews.indexOfValue(view);
            if (index < 0) return;
            mHeaderViews.removeAt(index);
            notifyDataSetChanged();
        }
    
        // 移除底部
        public void removeFooterView(View view) {
            int index = mFooterViews.indexOfValue(view);
            if (index < 0) return;
            mFooterViews.removeAt(index);
            notifyDataSetChanged();
        }
    
        /**
         * 解决GridLayoutManager添加头部和底部不占用一行的问题
         *
         * @param recycler
         * @version 1.0
         */
        public void adjustSpanSize(RecyclerView recycler) {
            if (recycler.getLayoutManager() instanceof GridLayoutManager) {
                final GridLayoutManager layoutManager = (GridLayoutManager) recycler.getLayoutManager();
                layoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
                    @Override
                    public int getSpanSize(int position) {
                        boolean isHeaderOrFooter =
                                isHeaderPosition(position) || isFooterPosition(position);
                        return isHeaderOrFooter ? layoutManager.getSpanCount() : 1;
                    }
                });
            }
        }
    }
    
    

    接下来我们直接在上一期的列表基础上加两个头部和底部测试一下

    这里写图片描述

    那赶紧把轮播图加载进来吧,千万别。还有事情没做,最忌讳的就是过度设计谁也看不懂一层套一层,还有就是半吊子总感觉少了点什么,顺便说个题外话刚才群里有人说看看博客装起B来一套一套的,哈哈。

    3.3 先构建WrapRecyclerView

    我们最好还是模仿ListView的结构搞就搞到西,自定义一个WrapRecyclerView,可以添加删除头部和底部View,这个就比较简单了

    /**
     * Created by Darren on 2016/12/29.
     * Email: 240336124@qq.com
     * Description: 可以添加头部和底部的RecyclerView
     */
    public class WrapRecyclerView extends RecyclerView {
        // 包裹了一层的头部底部Adapter
        private WrapRecyclerAdapter mWrapRecyclerAdapter;
        // 这个是列表数据的Adapter
        private RecyclerView.Adapter mAdapter;
    
        public WrapRecyclerView(Context context) {
            super(context);
        }
    
        public WrapRecyclerView(Context context, @Nullable AttributeSet attrs) {
            super(context, attrs);
        }
    
        public WrapRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
            super(context, attrs, defStyle);
        }
    
        @Override
        public void setAdapter(Adapter adapter) {
            // 为了防止多次设置Adapter
            if (mAdapter != null) {
                mAdapter.unregisterAdapterDataObserver(mDataObserver);
                mAdapter = null;
            }
    
            this.mAdapter = adapter;
    
            if (adapter instanceof WrapRecyclerAdapter) {
                mWrapRecyclerAdapter = (WrapRecyclerAdapter) adapter;
            } else {
                mWrapRecyclerAdapter = new WrapRecyclerAdapter(adapter);
            }
    
            super.setAdapter(mWrapRecyclerAdapter);
    
            // 注册一个观察者
            mAdapter.registerAdapterDataObserver(mDataObserver);
    
            // 解决GridLayout添加头部和底部也要占据一行
            mWrapRecyclerAdapter.adjustSpanSize(this);
        }
    
        // 添加头部
        public void addHeaderView(View view) {
            // 如果没有Adapter那么就不添加,也可以选择抛异常提示
            // 让他必须先设置Adapter然后才能添加,这里是仿照ListView的处理方式
            if (mWrapRecyclerAdapter != null) {
                mWrapRecyclerAdapter.addHeaderView(view);
            }
        }
    
        // 添加底部
        public void addFooterView(View view) {
            if (mWrapRecyclerAdapter != null) {
                mWrapRecyclerAdapter.addFooterView(view);
            }
        }
    
        // 移除头部
        public void removeHeaderView(View view) {
            if (mWrapRecyclerAdapter != null) {
                mWrapRecyclerAdapter.removeHeaderView(view);
            }
        }
    
        // 移除底部
        public void removeFooterView(View view) {
            if (mWrapRecyclerAdapter != null) {
                mWrapRecyclerAdapter.removeFooterView(view);
            }
        }
    
        private AdapterDataObserver mDataObserver = new AdapterDataObserver() {
            @Override
            public void onChanged() {
                if (mAdapter == null) return;
                // 观察者  列表Adapter更新 包裹的也需要更新不然列表的notifyDataSetChanged没效果
                if (mWrapRecyclerAdapter != mAdapter)
                    mWrapRecyclerAdapter.notifyDataSetChanged();
            }
    
            @Override
            public void onItemRangeRemoved(int positionStart, int itemCount) {
                if (mAdapter == null) return;
                // 观察者  列表Adapter更新 包裹的也需要更新不然列表的notifyDataSetChanged没效果
                if (mWrapRecyclerAdapter != mAdapter)
                    mWrapRecyclerAdapter.notifyItemRemoved(positionStart);
            }
    
            @Override
            public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
                if (mAdapter == null) return;
                // 观察者  列表Adapter更新 包裹的也需要更新不然列表的notifyItemMoved没效果
                if (mWrapRecyclerAdapter != mAdapter)
                    mWrapRecyclerAdapter.notifyItemMoved(fromPosition, toPosition);
            }
    
            @Override
            public void onItemRangeChanged(int positionStart, int itemCount) {
                if (mAdapter == null) return;
                // 观察者  列表Adapter更新 包裹的也需要更新不然列表的notifyItemChanged没效果
                if (mWrapRecyclerAdapter != mAdapter)
                    mWrapRecyclerAdapter.notifyItemChanged(positionStart);
            }
    
            @Override
            public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
                if (mAdapter == null) return;
                // 观察者  列表Adapter更新 包裹的也需要更新不然列表的notifyItemChanged没效果
                if (mWrapRecyclerAdapter != mAdapter)
                    mWrapRecyclerAdapter.notifyItemChanged(positionStart,payload);
            }
    
            @Override
            public void onItemRangeInserted(int positionStart, int itemCount) {
                if (mAdapter == null) return;
                // 观察者  列表Adapter更新 包裹的也需要更新不然列表的notifyItemInserted没效果
                if (mWrapRecyclerAdapter != mAdapter)
                    mWrapRecyclerAdapter.notifyItemInserted(positionStart);
            }
        };
    }
    
    

    就不测试了,相信我是测试了没问题才贴的代码,还是那就话简简单单几行代码so easy,接下来直接整合轮播图。

    3.4 实战整合轮播图

    这里写图片描述

    无限轮播是用的之前博客写好的Android无限广告轮播 - 自定义BannerView,大家可以下载源码看看,既然效果都出来了就不多说了直接:

    附视频地址:http://pan.baidu.com/s/1slo5u3b

    相关文章

      网友评论

      • 灰灰灰2048:你好,有个地方不太明白,BASE_ITEM_TYPE_HEADER和BASE_ITEM_TYPE_FOOTER为什么要用静态类型呢,不用静态类型是不是也可以呢
        红橙Darren:@灰灰灰2048 可以,这两个变量也可以不要
      • Smile等待_def6:跟着大佬撸完代码,十分感谢!!
      • 异想天开的骑士:您好,怎么在Adapter中给HeaderView赋值呢?
        Smile等待_def6:跟着大佬全部敲完,前来拜谢!!!
        红橙Darren:@愁容 文章末尾有视频讲解地址
      • qishui:还有事情没做最忌讳的就是过度设计谁也看不懂一层套一层还有就是半吊子总感觉少了点什么,顺便说个题外话刚才群里有人说看看博客装起B来一套一套的,哈哈。
        我想起了鸿洋大佬~~~~
      • 30d510f59682:添加头部后,item点击事件向下偏移一个的
        红橙Darren:@cbbs 是的,因为添加了头部,点击事件我应该要重新写一下
      • 容华谢后:学习了
      • 大姨夫斯基:感觉标题不太够你们用了 下次我写的文章标题或许是《xxx更更最全解析》
      • yaoTongxue:不错,学习了

      本文标题:RecyclerView更全解析之 - 为它优雅的添加头部和底部

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