27
2017
09

参考ListView为RecyclerView添加Header、Footer和Loader

上一篇博客中分析了ListView添加Header的原理,接下来我们就参考listview的实现原理来为RecyclerView添加Header。

RecyclerView的Adapter已经不再是基于View进行复用,而是基于ViewHolder进行复用;创建viewholder是基于重写的view type方法。因此,我们在recyclerview中直接保存View的集合,由于recyclerview的Adapter是基于view type,因此不再使用ArrayList进行保存,使用SparseArrayCompat进行保存。

private SparseArrayCompat<View> mHeaderViews = new SparseArrayCompat<>();
    private SparseArrayCompat<View> mFooterViews = new SparseArrayCompat<>();
private View mLoaderView;
private int mCounts;

这里使用mCounts记录添加Header等的次数,防止key重复。我们来看一下addHeaderView、removeHeaderView方法(Footer和Loader类似,就不进行分析了):

public void addHeaderView(View v) {
        mHeaderViews.put(HeaderViewRecyclerAdapter.VIEW_TYPE_HEADER + mCounts++, v);
        // Wrap the adapter if it wasn't already wrapped.
        if (mAdapter != null) {
            if (!(mAdapter instanceof HeaderViewRecyclerAdapter)) {
                wrapHeaderViewRecyclerAdapter();
            }
            // In the case of re-adding a header view, or adding one later on,
            // we need to notify the observer.
            mAdapter.notifyItemInserted(mHeaderViews.size() - 1);
        }
    }
public boolean removeHeaderView(View v) {
        if (mHeaderViews.size() > 0) {
            boolean result = false;
            if (mAdapter != null && ((HeaderViewRecyclerAdapter) mAdapter).removeHeader(v)) {
                result = true;
            }
            removeFixedView(v, mHeaderViews);
            return result;
        }
        return false;
    }

这里和listview还是类似的,对notify方法做下对应修改。接下来看下setAdapter:

 @Override
    public void setAdapter(Adapter adapter) {
        if (mHeaderViews.size() > 0 || mFooterViews.size() > 0 || mLoaderView != null) {
            mAdapter = wrapHeaderViewRecyclerAdapter(mHeaderViews, mFooterViews, mLoaderView, adapter);
        } else {
            mAdapter = adapter;
        }
        super.setAdapter(mAdapter);
    }

这里也和listview是相似的,如果有Header、Footer或者Loader就创建HeaderViewRecyclerAdapter进行包装。
接下来看重点的HeaderViewRecyclerAdapter:

private class HeaderViewRecyclerAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
        public static final int VIEW_TYPE_HEADER = Integer.MAX_VALUE >> 1;
        public static final int VIEW_TYPE_FOOTER = Integer.MAX_VALUE >> 2;
        public static final int VIEW_TYPE_LOADER = Integer.MAX_VALUE;
        private SparseArrayCompat<View> mHeaderViews;
        private SparseArrayCompat<View> mFooterViews;
        private View mLoaderView;
        private RecyclerView.Adapter mAdapter;

        HeaderViewRecyclerAdapter(SparseArrayCompat<View> mHeaderViews, SparseArrayCompat<View> mFooterViews, View mLoaderView,
                                  RecyclerView.Adapter adapter) {
            this.mHeaderViews = mHeaderViews;
            this.mFooterViews = mFooterViews;
            this.mLoaderView = mLoaderView;
            if (mAdapter != null) {
                mAdapter.unregisterAdapterDataObserver(mObserver);
            }
            mAdapter = adapter;
            if (mAdapter != null) {
                mAdapter.registerAdapterDataObserver(mObserver);
            }
        }

        @Override
        public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
            if (mHeaderViews.get(viewType) != null) {
                return new ViewHolder(mHeaderViews.get(viewType));
            } else if (mFooterViews.get(viewType) != null) {
                return new ViewHolder(mFooterViews.get(viewType));
            } else if (viewType == VIEW_TYPE_LOADER) {
                return new ViewHolder(mLoaderView);
            }
            if (mAdapter != null)
                return mAdapter.createViewHolder(viewGroup, viewType);
            throw new RuntimeException("position should match header or footer or loader");
        }

        @Override
        public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
            int numHeaders = getHeadersCount();
            if (position < numHeaders) {
                return;
            }
            // Adapter
            final int adjPosition = position - numHeaders;
            int adapterCount = 0;
            if (mAdapter != null) {
                adapterCount = mAdapter.getItemCount();
                if (adjPosition < adapterCount) {
                    mAdapter.onBindViewHolder(holder, adjPosition);
                }
            }
        }

        @Override
        public int getItemCount() {
            return getFootersCount() + getHeadersCount() + getLoadersCount() + getCount();
        }

        private int getCount() {
            return mAdapter != null ? mAdapter.getItemCount() : 0;
        }

        @Override
        public int getItemViewType(int position) {
            int numHeaders = getHeadersCount();
            if (position < numHeaders) {
                return mHeaderViews.keyAt(position);
            }
            // Adapter
            final int adjPosition = position - numHeaders;
            int adapterCount = 0;
            if (mAdapter != null) {
                adapterCount = mAdapter.getItemCount();
                if (adjPosition < adapterCount) {
                    return mAdapter.getItemViewType(adjPosition);
                }
            }
            int numFooters = getFootersCount();
            final int footPosition = position - numHeaders - adapterCount;
            if (footPosition < numFooters) {
                return mFooterViews.keyAt(footPosition);
            }
            return VIEW_TYPE_LOADER;
        }

        private int getHeadersCount() {
            return mHeaderViews.size();
        }

        private int getFootersCount() {
            return mFooterViews.size();
        }

        private int getLoadersCount() {
            return mLoaderView != null ? 1 : 0;
        }

        private boolean removeHeader(View v) {
            int index = mHeaderViews.indexOfValue(v);
            if (index >= 0) {
                mHeaderViews.removeAt(index);
                notifyItemRemoved(index);
                return true;
            }
            return false;
        }

        private boolean removeFooter(View v) {
            int index = mFooterViews.indexOfValue(v);
            if (index >= 0) {
                mFooterViews.removeAt(index);
                notifyItemRemoved(getHeadersCount() + getCount() + index);
                return true;
            }
            return false;
        }

        private boolean removeLoader() {
            boolean success = mLoaderView != null;
            if (success) {
                mLoaderView = null;
                notifyItemRemoved(getItemCount());
            }
            return success;
        }

        @Override
        public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) {
            if (holder instanceof ViewHolder) return;
            if (mAdapter != null) mAdapter.onViewAttachedToWindow(holder);
        }

        @Override
        public void onViewDetachedFromWindow(RecyclerView.ViewHolder holder) {
            if (holder instanceof ViewHolder) return;
            if (mAdapter != null) mAdapter.onViewDetachedFromWindow(holder);
        }

        @Override
        public void onViewRecycled(RecyclerView.ViewHolder holder) {
            if (holder instanceof ViewHolder) return;
            if (mAdapter != null) mAdapter.onViewRecycled(holder);
        }

        private RecyclerView.AdapterDataObserver mObserver = new RecyclerView.AdapterDataObserver() {
            @Override
            public void onChanged() {
                notifyDataSetChanged();
            }

            @Override
            public void onItemRangeChanged(int positionStart, int itemCount) {
                notifyItemRangeChanged(positionStart + getHeadersCount(), itemCount);
            }

            @Override
            public void onItemRangeInserted(int positionStart, int itemCount) {
                notifyItemRangeInserted(positionStart + getHeadersCount(), itemCount);
            }

            @Override
            public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
                int headerViewsCountCount = getHeadersCount();
                notifyItemRangeChanged(fromPosition + headerViewsCountCount,
                        toPosition + headerViewsCountCount + itemCount);
            }

            @Override
            public void onItemRangeRemoved(int positionStart, int itemCount) {
                notifyItemRangeRemoved(positionStart + getHeadersCount(), itemCount);
            }
        };
    }

代码比较多,重点主要是两块:

  1. listview是根据position获取对应的View,而recyclerview是根据view type创建view holder,因此采用了mHeaderViews.keyAt(position)来获取唯一的view type。
  2. listview只有一个notifyDataChanged方法,recyclerview多了很多类notify方法,比如notifyItemInserted(position)。我们要考虑position的位置矫正,这里采用的对被包装Adapter注册AdapterDataObserver,然后处理Header导致的位置问题。
  3. recyclerview的Adapter是带有泛型的,为了防止类型转换错误,自定义了ViewHolder,并在onViewRecycled等方法中进行类型判断,防止类型转换错误。

    具体的代码可以参见GitHub:HeaderRecyclerView

上一篇:Ubuntu firefox 上无法联网 下一篇:Android 监听应用内Activity生命周期