首页 > 技术文章 > Android-优雅地为RecyclerView添加HeaderView和FooterView

canf963 2017-10-17 16:33 原文

怎么给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() {

        }
    }
}
View Code

 

需要了解更多RecyclerView的功能,请访问RecyclerViewHelper

推荐阅读