Android-优雅地为RecyclerView添加HeaderView和FooterView
怎么给RecycerView添加Header/FooterView?答案在网上一搜一大把,实现原理大体相同。我第一次实现这种功能,参考了ListView的方式,使用代理模式设计一个代理类,代理RecyclerView.Adapter的所有行为。并且添加Header和Footer的功能都在代理类里面实现。
新建Adapter代理类ProxyAdapter,继承RecyclerView.Adapter,将Adapter通过构造方法传递给ProxyAdapter。
public class ProxyAdapter extends RecyclerView.Adapter { final RecyclerView.Adapter mAdapter; // ...... public ProxyAdapter(RecyclerView.Adapter adapter) { if (adapter == null) { throw new IllegalArgumentException(); } mAdapter = adapter; } }
创建两个数组容器分别存储HeaderView和FooterView,并增加add/remove方法用来添加和删除Header/FooterView。
List<View> mHeaderViews = new ArrayList<>(); List<View> mFooterViews = new ArrayList<>(); public void addHeaderView(View view) { if (mHeaderViews.add(view)) { mAdapter.notifyDataSetChanged(); } } public void removeHeaderView(View view) { if (mHeaderViews.remove(view)) { mAdapter.notifyDataSetChanged(); } } public void addFooterView(View view) { if (mFooterViews.add(view)) { mAdapter.notifyDataSetChanged(); } } public void removeFooterView(View view) { if (mFooterViews.remove(view)) { mAdapter.notifyDataSetChanged(); } }
重写ProxyAdapter的getItemCount()和getItemViewType(int position)方法, 为HeaderView和FooterView分配独立的ViewType。如果ItemView的当前位置position小于headerView的数量时,ItemView为HEADER类型。如果position大于等于headerView数量和adapter的item数量时,ItemView为FOOTER类型。通过ViewTypeSpec工具,将算出来的ItemView类型和Header/FooterView的索引位置(在容器中的位置)打包成一个整型结果,作为ItemViewType返回。
@Override public int getItemCount() { return mHeaderViews.size() + mFooterViews.size() + mAdapter.getItemCount(); } @Override public int getItemViewType(int position) { final int numHeaderView = mHeaderViews.size(); // final int numFooterView = mFooterViewInfos.size(); if (position < numHeaderView) return ViewTypeSpec.makeItemViewTypeSpec(position, ViewTypeSpec.HEADER); final int adjPosition = position - numHeaderView; final int itemCount = mAdapter.getItemCount(); if (adjPosition >= itemCount) return ViewTypeSpec.makeItemViewTypeSpec(adjPosition - itemCount, ViewTypeSpec.FOOTER); int itemViewType = mAdapter.getItemViewType(adjPosition); if (itemViewType < 0 || itemViewType > (1 << ViewTypeSpec.TYPE_SHIFT) - 1) { throw new IllegalArgumentException("Invalid item view type: RecyclerView.Adapter.getItemViewType return " + itemViewType); } return itemViewType; }
接下来重写onCreateViewHolder方法。需要实现的逻辑是通过ViewTypeSpec工具分离viewType,得到ItemView的类型(type)和索引位置(value)。根据类型type创建匹配的ViewHolder,根据索引位置从数组容器中获取Header/FooterView
@Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { RecyclerView.ViewHolder viewHolder; final int type = ViewTypeSpec.getType(viewType); final int value = ViewTypeSpec.getValue(viewType); if (type == ViewTypeSpec.HEADER) { viewHolder = new FixedViewHolder(mHeaderViews.get(value)); } else if (type == ViewTypeSpec.FOOTER) { viewHolder = new FixedViewHolder(mFooterViews.get(value)); } else { viewHolder = mAdapter.onCreateViewHolder(parent, viewType); } return viewHolder; }
大致实现流程就是这样,下面是ProxyAdapter的使用。
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);
// ......
mRecyclerView = (RecyclerView) findViewById(R.id.recycler_view); ContactAdapter adapter = new ContactAdapter(); // ...... ProxyAdapter proxyAdapter = new ProxyAdapter(adapter); mRecyclerView.setAdapter(proxyAdapter)
}
最后需要注意的是,如果列表数据需要刷新,调用的是ContactAdapter的notifyDataSetChanged()而不是ProxyAdapter的notifyDataSetChanged()。否则不会有刷新效果。
全部代码:
package cncoderx.myapplication.recyclerviewhelper; import android.support.annotation.IntDef; import android.support.annotation.IntRange; import android.support.v7.widget.RecyclerView; import android.view.View; import android.view.ViewGroup; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.List; /** * @author cncoderx */ public class ProxyAdapter extends RecyclerView.Adapter { List<View> mHeaderViews = new ArrayList<>(); List<View> mFooterViews = new ArrayList<>(); final RecyclerView.Adapter mAdapter; public ProxyAdapter(RecyclerView.Adapter adapter) { if (adapter == null) { throw new IllegalArgumentException(); } mAdapter = adapter; setHasStableIds(adapter.hasStableIds()); } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { RecyclerView.ViewHolder viewHolder; final int type = ViewTypeSpec.getType(viewType); final int value = ViewTypeSpec.getValue(viewType); if (type == ViewTypeSpec.HEADER) { viewHolder = new FixedViewHolder(mHeaderViews.get(value)); } else if (type == ViewTypeSpec.FOOTER) { viewHolder = new FixedViewHolder(mFooterViews.get(value)); } else { viewHolder = mAdapter.onCreateViewHolder(parent, viewType); } return viewHolder; } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { if (holder instanceof FixedViewHolder) { ((FixedViewHolder) holder).onBind(); } else { int adjPosition = position - mHeaderViews.size(); mAdapter.onBindViewHolder(holder, adjPosition); } } @Override public int getItemCount() { return mHeaderViews.size() + mFooterViews.size() + mAdapter.getItemCount(); } @Override public int getItemViewType(int position) { final int numHeaderView = mHeaderViews.size(); // final int numFooterView = mFooterViewInfos.size(); if (position < numHeaderView) return ViewTypeSpec.makeItemViewTypeSpec(position, ViewTypeSpec.HEADER); final int adjPosition = position - numHeaderView; final int itemCount = mAdapter.getItemCount(); if (adjPosition >= itemCount) return ViewTypeSpec.makeItemViewTypeSpec(adjPosition - itemCount, ViewTypeSpec.FOOTER); int itemViewType = mAdapter.getItemViewType(adjPosition); if (itemViewType < 0 || itemViewType > (1 << ViewTypeSpec.TYPE_SHIFT) - 1) { throw new IllegalArgumentException("Invalid item view type: RecyclerView.Adapter.getItemViewType return " + itemViewType); } return itemViewType; } @Override public void onDetachedFromRecyclerView(RecyclerView recyclerView) { mAdapter.onDetachedFromRecyclerView(recyclerView); } @Override public void onAttachedToRecyclerView(RecyclerView recyclerView) { mAdapter.onAttachedToRecyclerView(recyclerView); } @Override public void unregisterAdapterDataObserver(RecyclerView.AdapterDataObserver observer) { mAdapter.unregisterAdapterDataObserver(observer); } @Override public void registerAdapterDataObserver(RecyclerView.AdapterDataObserver observer) { mAdapter.registerAdapterDataObserver(observer); } @Override public void onViewDetachedFromWindow(RecyclerView.ViewHolder holder) { if (holder instanceof FixedViewHolder) return; mAdapter.onViewDetachedFromWindow(holder); } @Override public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) { if (holder instanceof FixedViewHolder) return; mAdapter.onViewAttachedToWindow(holder); } @Override public boolean onFailedToRecycleView(RecyclerView.ViewHolder holder) { if (holder instanceof FixedViewHolder) return false; return mAdapter.onFailedToRecycleView(holder); } @Override public void onViewRecycled(RecyclerView.ViewHolder holder) { if (holder instanceof FixedViewHolder) return; mAdapter.onViewRecycled(holder); } @Override public long getItemId(int position) { int adjPosition = position - mHeaderViews.size(); if (adjPosition >= 0 && adjPosition < mAdapter.getItemCount()) return mAdapter.getItemId(adjPosition); return RecyclerView.NO_ID; } private boolean isFixedViewType(int viewType) { final int type = ViewTypeSpec.getType(viewType); return type == ViewTypeSpec.HEADER || type == ViewTypeSpec.FOOTER; } public void addHeaderView(View view) { if (mHeaderViews.add(view)) { mAdapter.notifyDataSetChanged(); } } public void removeHeaderView(View view) { if (mHeaderViews.remove(view)) { mAdapter.notifyDataSetChanged(); } } public void addFooterView(View view) { if (mFooterViews.add(view)) { mAdapter.notifyDataSetChanged(); } } public void removeFooterView(View view) { if (mFooterViews.remove(view)) { mAdapter.notifyDataSetChanged(); } } static class ViewTypeSpec { static final int TYPE_SHIFT = 30; static final int TYPE_MASK = 0x3 << TYPE_SHIFT; public static final int UNSPECIFIED = 0 << TYPE_SHIFT; public static final int HEADER = 1 << TYPE_SHIFT; public static final int FOOTER = 2 << TYPE_SHIFT; @IntDef({UNSPECIFIED, HEADER, FOOTER}) @Retention(RetentionPolicy.SOURCE) public @interface ViewTypeSpecMode {} public static int makeItemViewTypeSpec(@IntRange(from = 0, to = (1 << TYPE_SHIFT) - 1) int value, @ViewTypeSpecMode int type) { return (value & ~TYPE_MASK) | (type & TYPE_MASK); } @ViewTypeSpecMode public static int getType(int viewType) { //noinspection ResourceType return (viewType & TYPE_MASK); } public static int getValue(int viewType) { return (viewType & ~TYPE_MASK); } } public static class FixedViewHolder extends RecyclerView.ViewHolder { public FixedViewHolder(View itemView) { super(itemView); setIsRecyclable(false); } public void onBind() { } } }
需要了解更多RecyclerView的功能,请访问RecyclerViewHelper。