BaseRecyclerViewAdapterHelper源码解读(二) 添加header和footer

BaseRecyclerViewAdapterHelper源码解读(二) 添加header和footer

原项目地址: https://github.com/CymChad/BaseRecyclerViewAdapterHelper,首先感谢开源,感谢无私奉献的人们.

首先,我们看到BaseQuickAdapter中醒目的两行代码;

    //header footer
    private LinearLayout mHeaderLayout;
    private LinearLayout mFooterLayout;

这意味着,它是将headerView和footerView添加到了对应LinearLayout中,然后相当于在显示的时候,上面是一个LinearLayout中间是普通item布局,底部是一个LinearLayout(后面也证实了,这个猜想是对的).

header和footer的数量

/**
     * if addHeaderView will be return 1, if not will be return 0
     * 添加了header则返回1   没有header则返回0
     */
    public int getHeaderLayoutCount() {
        if (mHeaderLayout == null || mHeaderLayout.getChildCount() == 0) {
            return 0;
        }
        return 1;
    }

    /**
     * if addFooterView will be return 1, if not will be return 0
     */
    public int getFooterLayoutCount() {
        if (mFooterLayout == null || mFooterLayout.getChildCount() == 0) {
            return 0;
        }
        return 1;
    }
    /**
     * Return root layout of header
     */

    public LinearLayout getHeaderLayout() {
        return mHeaderLayout;
    }

    /**
     * Return root layout of footer
     */
    public LinearLayout getFooterLayout() {
        return mFooterLayout;
    }

首先可以通过getHeaderLayoutCount()或者getFooterLayoutCount()获取是否添加了footer或者header,添加了就返回1.再通过getHeaderLayout()可以获取header的LinearLayout对象,再通过该对象的getChildCount()即可知道对应子View(也就是header数量)的数量.

getItemCount()

@Override
    public int getItemCount() {
        int count;
        //如果界面上显示了空布局
        if (getEmptyViewCount() == 1) {
            count = 1;
            //如果header可见  则只需要+1就行了,因为header的布局是LinearLayout
            if (mHeadAndEmptyEnable && getHeaderLayoutCount() != 0) {
                count++;
            }
            if (mFootAndEmptyEnable && getFooterLayoutCount() != 0) {
                count++;
            }
        } else {  //未显示空布局
            //         1 or 0                  数据项             1 or 0                 加载更多 1 or 0
            count = getHeaderLayoutCount() + mData.size() + getFooterLayoutCount() + getLoadMoreViewCount();
        }
        return count;
    }

getItemViewType()

@Override
    public int getItemViewType(int position) {
        if (getEmptyViewCount() == 1) {
            boolean header = mHeadAndEmptyEnable && getHeaderLayoutCount() != 0;
            switch (position) {
                case 0:
                    if (header) {
                        return HEADER_VIEW;
                    } else {
                        return EMPTY_VIEW;
                    }
                case 1:
                    if (header) {
                        return EMPTY_VIEW;
                    } else {
                        return FOOTER_VIEW;
                    }
                case 2:
                    return FOOTER_VIEW;
                default:
                    return EMPTY_VIEW;
            }
        }
        //1 or 0
        int numHeaders = getHeaderLayoutCount();
        if (position < numHeaders) {
            return HEADER_VIEW;
        } else {
            int adjPosition = position - numHeaders;
            int adapterCount = mData.size();

            //中间的数据项
            if (adjPosition < adapterCount) {
                return getDefItemViewType(adjPosition);
            } else {
                //剩下 footer   加载中布局
                adjPosition = adjPosition - adapterCount;
                int numFooters = getFooterLayoutCount();
                if (adjPosition < numFooters) {
                    return FOOTER_VIEW;
                } else {
                    return LOADING_VIEW;
                }
            }
        }
    }

    /**
     * 默认的type
     * @param position
     * @return
     */
    protected int getDefItemViewType(int position) {
        if (mMultiTypeDelegate != null) {
            return mMultiTypeDelegate.getDefItemViewType(mData, position);
        }
         //其实就是0
        return super.getItemViewType(position);
    }

这里有空布局的逻辑一起混起的,干脆一起分析,反正不是很难.

  • 当有空布局的时候,position的值只可能为0,1,2;
    再根据是否显示了header,即可判断出当前position的type应该是什么.
  • 再看没有显示空布局的情况
    • header类型 索引:< 1
    • 中间的数据项类型 需要减去header的数量(1 or 0) ;这里面牵涉到了item多种类型的逻辑,如果是多种类型的则交由mMultiTypeDelegate去处理,如果不是,则默认返回super.getItemViewType(position);
    • footer类型 减去header的数量(1 or 0),再减去中间数据项的数量
    • 加载中类型 剩下的

onCreateViewHolder()

@Override
    public K onCreateViewHolder(ViewGroup parent, int viewType) {
        K baseViewHolder = null;
        this.mContext = parent.getContext();
        this.mLayoutInflater = LayoutInflater.from(mContext);
        switch (viewType) {
            case LOADING_VIEW:
                baseViewHolder = getLoadingView(parent);
                break;
            case HEADER_VIEW:
                baseViewHolder = createBaseViewHolder(mHeaderLayout);
                break;
            case EMPTY_VIEW:
                baseViewHolder = createBaseViewHolder(mEmptyLayout);
                break;
            case FOOTER_VIEW:
                baseViewHolder = createBaseViewHolder(mFooterLayout);
                break;
            default:
                baseViewHolder = onCreateDefViewHolder(parent, viewType);
                bindViewClickListener(baseViewHolder);
        }
        baseViewHolder.setAdapter(this);
        return baseViewHolder;

    }

    /**
     * 创建默认的ViewHolder  即中间的数据项的ViewHolder
     * @param parent
     * @param viewType
     * @return
     */
    protected K onCreateDefViewHolder(ViewGroup parent, int viewType) {
        int layoutId = mLayoutResId;
        if (mMultiTypeDelegate != null) {  //不同的类型的布局
            layoutId = mMultiTypeDelegate.getLayoutId(viewType);
        }
        return createBaseViewHolder(parent, layoutId);
    }

    protected K createBaseViewHolder(ViewGroup parent, int layoutResId) {
        return createBaseViewHolder(getItemView(layoutResId, parent));
    }

    /**
     * @param layoutResId ID for an XML layout resource to load
     * @param parent      Optional view to be the parent of the generated hierarchy or else
     *                    simply an object that
     *                    provides a set of LayoutParams values for root of the returned
     *                    hierarchy
     * @return view will be return
     */
    protected View getItemView(@LayoutRes int layoutResId, ViewGroup parent) {
        return mLayoutInflater.inflate(layoutResId, parent, false);
    }

不管是什么类型,最终都会创建BaseViewHolder的对象.
然后通过createBaseViewHolder()方法去创建.我们稍后再来看看createBaseViewHolder()方法.先看看onCreateDefViewHolder()方法.它是数据项创建ViewHolder,比较”特殊”.

如果是默认类型的,则调用onCreateDefViewHolder(),判断是否是多布局,然后走下面的逻辑.

  • 多布局->则交给多布局的逻辑去获取layoutId
  • 不是多布局,通过注解拿来用

最后需要调用getItemView()方法来创建一个View,
其实就是通过LayoutInflater来创建View,这和我们平时一样嘛,哈哈.

/**
     * if you want to use subclass of BaseViewHolder in the adapter,
     * you must override the method to create new ViewHolder.
     * 如果要在适配器中使用BaseViewHolder的子类,
     *       *您必须覆盖该方法才能创建新的ViewHolder。
     *
     * @param view view
     * @return new ViewHolder
     */
    @SuppressWarnings("unchecked")
    protected K createBaseViewHolder(View view) {
        Class temp = getClass();
        Class z = null;
        while (z == null && null != temp) {
            //判断z是否是BaseViewHolder的子类或接口  不是则返回null
            z = getInstancedGenericKClass(temp);
            //返回超类
            temp = temp.getSuperclass();
        }
        K k;
        // 泛型擦除会导致z为null
        if (z == null) {
            //为null则说明z不是BaseViewHolder的子类或接口 则创建一个BaseViewHolder
            k = (K) new BaseViewHolder(view);
        } else {
            //尝试创建z的实例   利用反射
            k = createGenericKInstance(z, view);
        }
        return k != null ? k : (K) new BaseViewHolder(view);
    }

/**
     * get generic parameter K
     * 判断z是否是BaseViewHolder的子类或接口
     *
     * @param z
     * @return
     */
    private Class getInstancedGenericKClass(Class z) {
        //getGenericSuperclass()获得带有泛型的父类
        //Type是 Java 编程语言中所有类型的公共高级接口。它们包括原始类型、参数化类型、数组类型、类型变量和基本类型。
        Type type = z.getGenericSuperclass();
        if (type instanceof ParameterizedType) {
            Type[] types = ((ParameterizedType) type).getActualTypeArguments();
            for (Type temp : types) {
                //判断tempClass是否是BaseViewHolder类型相同或具有相同的接口
                if (temp instanceof Class) {
                    Class tempClass = (Class) temp;
                    if (BaseViewHolder.class.isAssignableFrom(tempClass)) {
                        return tempClass;
                    }
                } else if (temp instanceof ParameterizedType) {
                    Type rawType = ((ParameterizedType) temp).getRawType();
                    if (rawType instanceof Class && BaseViewHolder.class.isAssignableFrom(
                            (Class<?>) rawType)) {
                        return (Class<?>) rawType;
                    }
                }
            }
        }
        return null;
    }

    /**
     * try to create Generic K instance
     * 尝试创建Generic K实例
     *
     * @param z
     * @param view
     * @return
     */
    @SuppressWarnings("unchecked")
    private K createGenericKInstance(Class z, View view) {
        try {
            Constructor constructor;
            // inner and unstatic class
            //成员类&&非静态类
            if (z.isMemberClass() && !Modifier.isStatic(z.getModifiers())) {
                //获取z的构造函数
                constructor = z.getDeclaredConstructor(getClass(), View.class);
                //禁止java语言访问检查
                constructor.setAccessible(true);
                //通过构造方法构造z对象
                return (K) constructor.newInstance(this, view);
            } else {
                constructor = z.getDeclaredConstructor(View.class);
                constructor.setAccessible(true);
                return (K) constructor.newInstance(view);
            }
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        return null;
    }

其实这一大段代码其实是在创建BaseViewHolder的实例,首先是判断是否是BaseViewHolder的子类,如果不是则直接创建BaseViewHolder;如果是则通过反射去创建BaseViewHolder实例.

添加header

/**
     * Append header to the rear of the mHeaderLayout.
     * 默认添加header到headerLayout的底部(索引最大的那个位置)
     *
     * @param header
     */
    public int addHeaderView(View header) {
        return addHeaderView(header, -1);
    }

    /**
     * Add header view to mHeaderLayout and set header view position in mHeaderLayout.
     * When index = -1 or index >= child count in mHeaderLayout,
     * the effect of this method is the same as that of {@link #addHeaderView(View)}.
     *
     * @param header
     * @param index  the position in mHeaderLayout of this header.
     *               When index = -1 or index >= child count in mHeaderLayout,
     *               the effect of this method is the same as that of {@link #addHeaderView(View)}.
     */
    public int addHeaderView(View header, int index) {
        return addHeaderView(header, index, LinearLayout.VERTICAL);
    }

    /**
     * @param header
     * @param index
     * @param orientation
     */
    public int addHeaderView(View header, int index, int orientation) {

        // 如果为空 则创建头布局
        if (mHeaderLayout == null) {
            mHeaderLayout = new LinearLayout(header.getContext());
            // 方向  LayoutParams设置
            if (orientation == LinearLayout.VERTICAL) {
                mHeaderLayout.setOrientation(LinearLayout.VERTICAL);
                mHeaderLayout.setLayoutParams(new LayoutParams(MATCH_PARENT, WRAP_CONTENT));
            } else {
                mHeaderLayout.setOrientation(LinearLayout.HORIZONTAL);
                mHeaderLayout.setLayoutParams(new LayoutParams(WRAP_CONTENT, MATCH_PARENT));
            }
        }


        final int childCount = mHeaderLayout.getChildCount();
        // 如果index不合法,则添加到索引最大的位置
        if (index < 0 || index > childCount) {
            index = childCount;
        }
        mHeaderLayout.addView(header, index);   //就是添加到LinearLayout中嘛,哈哈,被我猜中啦

        /*
        如果头布局(LinearLayout)中子View(header的item)的数量等于1
         说明这是第一次添加headerLayout进RecyclerView
         需要进行通知刷新操作  告知RecyclerView第一个索引处更新啦
         */
        if (mHeaderLayout.getChildCount() == 1) {
            int position = getHeaderViewPosition();
            if (position != -1) {
                notifyItemInserted(position); //告知RecyclerView第一个索引处更新啦
                // 这时RecyclerView中的第一项已经是headerLayout(LinearLayout)了
            }
        }
        return index;
    }

    public int setHeaderView(View header) {
        return setHeaderView(header, 0, LinearLayout.VERTICAL);
    }

    public int setHeaderView(View header, int index) {
        return setHeaderView(header, index, LinearLayout.VERTICAL);
    }

    public int setHeaderView(View header, int index, int orientation) {
        if (mHeaderLayout == null || mHeaderLayout.getChildCount() <= index) {
            return addHeaderView(header, index, orientation);
        } else {
            mHeaderLayout.removeViewAt(index);
            mHeaderLayout.addView(header, index);
            return index;
        }
    }

别被吓到了,这次的代码有点多,我们一个一个来分析.添加headrView最终都会到达addHeaderView(View header, int index, int orientation)方法,所以我们直接来分析该方法即可.

1.首先是判断是否初始化了mHeaderLayout(header的布局容器LinearLayout,所有的headerView都放在这里的),没有初始化则创建mHeaderLayout

  • 设置LinearLayout方向
  • 设置LayoutParams

2.判断headerView的插入位置是否合法

  • 合法:插入到应该在的位置
  • 不合法:插入到索引最大的位置

3.判断是否是第一次添加headerView

  • 如果头布局(LinearLayout)中子View(header的item)的数量等于1,
    说明这是第一次添加headerLayout进RecyclerView;
    需要进行通知刷新操作,告知RecyclerView第一个索引处更新啦;
  • 如若不是第一次添加headerView,则添加到LinearLayout中相应位置就不用管了,因为整个LinearLayout在RecyclerView只占用一个位置,不用通知RecyclerView进行刷新操作.

4.后面的setXXX()方法,其实没什么用吧,差不多就是对前面方法改个名字.

添加footer

/**
     * Append footer to the rear of the mFooterLayout.
     *
     * @param footer
     */
    public int addFooterView(View footer) {
        return addFooterView(footer, -1, LinearLayout.VERTICAL);
    }

    public int addFooterView(View footer, int index) {
        return addFooterView(footer, index, LinearLayout.VERTICAL);
    }

    /**
     * Add footer view to mFooterLayout and set footer view position in mFooterLayout.
     * When index = -1 or index >= child count in mFooterLayout,
     * the effect of this method is the same as that of {@link #addFooterView(View)}.
     *
     * @param footer
     * @param index  the position in mFooterLayout of this footer.
     *               When index = -1 or index >= child count in mFooterLayout,
     *               the effect of this method is the same as that of {@link #addFooterView(View)}.
     */
    public int addFooterView(View footer, int index, int orientation) {
        if (mFooterLayout == null) {
            mFooterLayout = new LinearLayout(footer.getContext());
            if (orientation == LinearLayout.VERTICAL) {
                mFooterLayout.setOrientation(LinearLayout.VERTICAL);
                mFooterLayout.setLayoutParams(new LayoutParams(MATCH_PARENT, WRAP_CONTENT));
            } else {
                mFooterLayout.setOrientation(LinearLayout.HORIZONTAL);
                mFooterLayout.setLayoutParams(new LayoutParams(WRAP_CONTENT, MATCH_PARENT));
            }
        }
        final int childCount = mFooterLayout.getChildCount();
        if (index < 0 || index > childCount) {
            index = childCount;
        }
        mFooterLayout.addView(footer, index);
        if (mFooterLayout.getChildCount() == 1) {
            int position = getFooterViewPosition();
            if (position != -1) {
                notifyItemInserted(position);
            }
        }
        return index;
    }

    public int setFooterView(View header) {
        return setFooterView(header, 0, LinearLayout.VERTICAL);
    }

    public int setFooterView(View header, int index) {
        return setFooterView(header, index, LinearLayout.VERTICAL);
    }

    public int setFooterView(View header, int index, int orientation) {
        if (mFooterLayout == null || mFooterLayout.getChildCount() <= index) {
            return addFooterView(header, index, orientation);
        } else {
            mFooterLayout.removeViewAt(index);
            mFooterLayout.addView(header, index);
            return index;
        }
    }

这里的代码和前面添加header的逻辑一样的嘛,这里就不多说了.

移除header和footer

/**
     * remove header view from mHeaderLayout.
     * When the child count of mHeaderLayout is 0, mHeaderLayout will be set to null.
     *
     * @param header
     */
    public void removeHeaderView(View header) {
        if (getHeaderLayoutCount() == 0) return;

        mHeaderLayout.removeView(header);
        //如果mHeaderLayout已经没有子View,则直接将mHeaderLayout从RecyclerView中移除
        if (mHeaderLayout.getChildCount() == 0) {
            int position = getHeaderViewPosition();
            if (position != -1) {
                notifyItemRemoved(position);
            }
        }
    }

    /**
     * remove footer view from mFooterLayout,
     * When the child count of mFooterLayout is 0, mFooterLayout will be set to null.
     *
     * @param footer
     */
    public void removeFooterView(View footer) {
        if (getFooterLayoutCount() == 0) return;

        mFooterLayout.removeView(footer);
        //如果mFooterLayout已经没有子View,则直接将mHeaderLayout从RecyclerView中移除
        if (mFooterLayout.getChildCount() == 0) {
            int position = getFooterViewPosition();
            if (position != -1) {
                notifyItemRemoved(position);
            }
        }
    }

    /**
     * remove all header view from mHeaderLayout and set null to mHeaderLayout
     * 移除所有header view
     */
    public void removeAllHeaderView() {
        if (getHeaderLayoutCount() == 0) return;

        mHeaderLayout.removeAllViews();
        int position = getHeaderViewPosition();
        if (position != -1) {
            notifyItemRemoved(position);
        }
    }

    /**
     * remove all footer view from mFooterLayout and set null to mFooterLayout
     * 移除所有footer view
     */
    public void removeAllFooterView() {
        if (getFooterLayoutCount() == 0) return;

        mFooterLayout.removeAllViews();
        int position = getFooterViewPosition();
        if (position != -1) {
            notifyItemRemoved(position);
        }
    }

    /**
     * 返回HeaderView在RecyclerView中的位置
     * @return 0 or -1
     */
    private int getHeaderViewPosition() {
        //Return to header view notify position
        if (getEmptyViewCount() == 1) {
            //有空布局 并且 头布局可见
            if (mHeadAndEmptyEnable) {
                return 0;
            }
        } else {
            //没有空布局   返回0
            return 0;
        }
        return -1;
    }

    private int getFooterViewPosition() {
        //Return to footer view notify position
        if (getEmptyViewCount() == 1) {
            int position = 1;
            if (mHeadAndEmptyEnable && getHeaderLayoutCount() != 0) {
                //空布局可见 并且 头布局可见
                position++;
            }
            if (mFootAndEmptyEnable) {
                //尾布局可见
                return position;
            }
        } else {
            //头布局有无:0 or 1              正常项的大小
            return getHeaderLayoutCount() + mData.size();
        }
        return -1;
    }

这里的逻辑相对比较简单,就是将header(footer) view从mHeaderLayout(mFooterLayout)中移除,如果全部都移出去了,则通知RecyclerView刷新.
这里最后2个方法是用于返回headerLayout和footerLayout在RecyclerView中的位置,在计算位置的时候需要加入对空布局的判断.

总结

好了,差不多就这些了,如果有不足的,之后再来补充.其实花点时间看源码还是挺有趣的嘛,感觉整个人神清气爽啊,哈哈.最开始我有点心理恐惧,怕看不懂,现在好一些了,看源码需要克服心理恐惧.年轻人,不要怕,就是干.看开源项目的源码,有很多好处,我就不一一列举了,反正就是好.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值