Android RecyclerView添加头部和尾部

前言:

在使用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 mHeaderViewInfos = new ArrayList<>();
private ArrayList 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添加头部和尾部)