美文网首页recycleview读书
Android RecyclerView添加头部和尾部

Android RecyclerView添加头部和尾部

作者: Android高级工程师 | 来源:发表于2019-05-07 17:15 被阅读50次

    前言:

    在使用RecyclerView替换之前常用的ListView开发的时候,我们会发现一个问题,RecyclerView中没有提供给我们添加头部尾部的方法,那么我们就可以参考ListView的实现方式来为RecyclerView扩展,使其支持添加头部和添加尾部。

    一、 最终效果

    我们希望RecyclerView提供如下两个方法,addHeaderView(View view); addFooterView(View view);先来瞄一眼最终的效果:


    image.png

    二、 ListView实现方案

    既然我们是看ListView提供了这个方法才决定对RecyclerView进行改造的,那么ListView中是怎么处理的呢?

    public void addHeaderView(View v) {
        addHeaderView(v, null, true);
    }
    
    

    在addHeaderView(View v)方法中调用了一个三参数的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;
     
        // 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();
            }
        }
    }
    

    可以看到这里使用了一个叫 HeaderViewListAdapter的包装类,并且使用了一个FixedViewInfo的内部类来保存添加的信息。既然是一个ListView的Adapter那我们自然最关心的就是该Adapter中的getCount()、getView()等方法:

    public int getCount() {
        if (mAdapter != null) {
            return getFootersCount() + getHeadersCount() + mAdapter.getCount();
        } else {
            return getFootersCount() + getHeadersCount();
        }
    }
    
    public View getView(int position, View convertView, ViewGroup parent) {
        // Header (negative positions will throw an IndexOutOfBoundsException)
        int numHeaders = getHeadersCount();
        if (position < numHeaders) {
            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;
    }
    
    

    通过以上简单的分析,我们基本就知道了ListView是如何实现添加头部和添加尾部的,就是给ListView的Adapter使用了一个包装类,对头部和尾部条目做了处理,其余还是使用的被包装类的方法。

    三、WrapAdapter包装类编写

    既然了解了ListView添加头部和尾部的实现方案,那么我们就按照该方案来实现自己的Recycler.Adapter的包装类吧。
    我们知道ListView的Adapter中我们通常关系的是getCount(),getView()等方法,那么Recycler.Adapter中我们关心哪些方法呢?没错就是onCreateViewHolder()、onBindViewHolder()、getItemCount()、getItemViewType(),那么只需要对这几个方法包装下就好了。
    1. 添加View描述类创建
    把ListView中描述头部尾部的FixedViewInfo类拿过来稍作修改变为我们的描述类;

    /**
     * A class that represents a fixed view in a list, for example a header at the top
     * or a footer at the bottom.
     */
    public class FixedViewInfo {
        /** The view to add to the list */
        public View view;
        /** The data backing the view. This is returned from {RecyclerView.Adapter#getItemViewType(int)}. */
        public int viewType;
    }
    
    

    2. 创建存储头部尾部集合

    private ArrayList<FixedViewInfo> mHeaderViewInfos = new ArrayList<>();
    private ArrayList<FixedViewInfo> mFooterViewInfos = new ArrayList<>();
    

    3. 修改getItemCount()方法

    @Override
    public int getItemCount() {
        return mHeaderViewInfos.size() + mRealAdapter.getItemCount() + mFooterViewInfos.size();
    }
    

    比较简单,只需要把真正的AdapterView的个数加上头部和尾部的个数就可以啦。
    4. 修改getItemViewType()方法

    @Override
    public int getItemViewType(int position) {
        if (isHeaderPosition(position)) {
            return mHeaderViewInfos.get(position).viewType;
     
        } else if (isFooterPosition(position)) {
            return mFooterViewInfos.get(position - mHeaderViewInfos.size()
                    - mRealAdapter.getItemCount()).viewType;
     
        } else {
            return mRealAdapter.getItemViewType(position - mHeaderViewInfos.size());
        }
    }
    

    这里只需要对头部和尾部做相应处理即可,那么怎么判断是头部或者尾部呢?对头,就是通过该方法传入的position参数来判断的:

    private boolean isHeaderPosition(int position) {
        return position < mHeaderViewInfos.size();
    }
    
    
    private boolean isFooterPosition(int position) {
        return position >= mHeaderViewInfos.size() + mRealAdapter.getItemCount();
    }
    

    5. 修改onCreateViewHolder()方法
    把两个软柿子挑出来搞完之后我们还是要面对比较复杂的onCreateViewHolder(),这里我们也和getItemViewType类似,只有针对头部和尾部处理即可。

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
        if (isHeader(viewType)) {
            int whichHeader = Math.abs(viewType - BASE_HEADER_VIEW_TYPE);
            View headerView = mHeaderViewInfos.get(whichHeader).view;
            return createHeaderFooterViewHolder(headerView);
     
        } else if (isFooter(viewType)) {
            int whichFooter = Math.abs(viewType - BASE_FOOTER_VIEW_TYPE);
            View footerView = mFooterViewInfos.get(whichFooter).view;
            return createHeaderFooterViewHolder(footerView);
     
        } else {
            return mRealAdapter.onCreateViewHolder(viewGroup, viewType);
        }
    }
    

    这里把创建头部和尾部ViewHolder封装到一个createHeaderFooterViewHolder()方法中:

    private RecyclerView.ViewHolder createHeaderFooterViewHolder(View view) {
        if (isStaggeredGrid) {
            StaggeredGridLayoutManager.LayoutParams params = new StaggeredGridLayoutManager.LayoutParams(
                    StaggeredGridLayoutManager.LayoutParams.MATCH_PARENT, StaggeredGridLayoutManager.LayoutParams.WRAP_CONTENT);
            params.setFullSpan(true);
            view.setLayoutParams(params);
        }
        return new RecyclerView.ViewHolder(view) {
        };
    }
    

    这里为什么判断 isStaggeredGrid我们稍后再讲解,除了这一点外,还是比较简单的,创建了一个Recycler.ViewHolder。
    6. 修改onBindViewHolder()方法

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
        if (position < mHeaderViewInfos.size()) {
            // Headers don't need anything special
     
        } else if (position < mHeaderViewInfos.size() + mRealAdapter.getItemCount()) {
            // This is a real position, not a header or footer. Bind it.
            mRealAdapter.onBindViewHolder(viewHolder, position - mHeaderViewInfos.size());
     
        } else {
            // Footers don't need anything special
        }
    }
    

    我们只需要处理真正的item数据即可,头部和尾部不做处理。

    OK,那我们就修改好了,那么来运行一把吧。


    image.png

    什么鬼,怎么添加的头部不是占据一行呢?就是要在我们跳过的createHeaderFooterViewHolder()中isStaggeredGrid来处理的,就是设置如果配置的为StaggeredGridLayoutManager则要占据一行,但是还有一个问题那就是如果是GridLayoutManager也是要占据一行的,这里提供了一个方法来处理占据一行的问题:

    /**
     * adjust the LayoutManager SpanSize
     *
     * @param recycler
     * @version 1.0
     */
    public void adjustSpanSize(RecyclerView recycler) {
        if(recycler.getLayoutManager() instanceof GridLayoutManager) {
            final GridLayoutManager layoutManager = (GridLayoutManager) recycler.getLayoutManager();
            layoutManager.setSpanSizeLookup(new SpanSizeLookup() {
     
                @Override
                public int getSpanSize(int position) {
                    boolean isHeaderOrFooter =
                            isHeaderPosition(position) || isFooterPosition(position);
                    return isHeaderOrFooter ? layoutManager.getSpanCount() : 1;
                }
     
            });
        }
     
        if(recycler.getLayoutManager() instanceof StaggeredGridLayoutManager) {
            this.isStaggeredGrid = true;
        }
    }
    

    这么一来,无论是StaggeredGridLayoutManager还是GridLayoutManager添加的头部和尾部都会占据一行啦。

    四、WrapRecyclerView包装类编写

    其实第三步骤,我们就完成了封装,但是为了使用方便,这里再提供一个对RecyclerView的封装,其实比较简单,主要的在setAdapter中,其他的都是对WrapAdapter的简单代理。

    @Override
    public void setAdapter(Adapter adapter) {
        if(adapter instanceof WrapAdapter) {
            mWrapAdapter = (WrapAdapter) adapter;
            super.setAdapter(adapter);
        } else {
            mWrapAdapter = new WrapAdapter(adapter);
            super.setAdapter(mWrapAdapter);
        }
     
        if(shouldAdjustSpanSize) {
            mWrapAdapter.adjustSpanSize(this);
        }
    }
    
    /**
     * Adds a header view
     *
     * @param view
     * @version 1.0
     */
    public void addHeaderView(View view) {
        if (null == view) {
            throw new IllegalArgumentException("the view to add must not be null!");
        } else if(mWrapAdapter == null) {
            throw new IllegalStateException("You must set a adapter before!");
        } else {
            mWrapAdapter.addHeaderView(view);
        }
    }
    
    /**
     * Adds a footer view
     *
     * @param view
     * @version 1.0
     */
    public void addFooterView(View view) {
        if (null == view) {
            throw new IllegalArgumentException("the view to add must not be null!");
        } else if(mWrapAdapter == null) {
            throw new IllegalStateException("You must set a adapter before!");
        } else {
            mWrapAdapter.addFooterView(view);
        }
    }
    

    喜欢点击+关注哦

    相关文章

      网友评论

        本文标题:Android RecyclerView添加头部和尾部

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