Android 解析RecyclerView(2)——带顶部View和底部View的RecyclerView

来源:互联网 发布:淘宝发货地址在哪里填 编辑:程序博客网 时间:2024/06/03 12:29

RecyclerView是用来替代ListView的一个控件,比ListView更加的简洁高效,不过也有一些比较不足的地方,比如:无法直接设置点击事件监听; 无法像ListView那样直接添加顶部View和底部View。

设置点击事件监听在前一篇文章已经解决了,这一篇文章要来介绍如何为RecyclerView添加顶部View和底部View。

一、源码分析

先来看下ListView的源码,研究它是如何添加顶部View的

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)) {                wrapHeaderListAdapterInternal();            }            // 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();            }        }    }

可以看到,addHeaderView() 方法首先是将 headerView 保存到 FixedViewInfo 对象中,再将 FixedViewInfo 对象保存到集合中。
FixedViewInfo 类的定义如下所示:

   public class FixedViewInfo {        /** The view to add to the list */        public View view;        /** The data backing the view. This is returned from {@link ListAdapter#getItem(int)}. */        public Object data;        /** <code>true</code> if the fixed view should be selectable in the list */        public boolean isSelectable;    }

mAdapter 即是为ListView设置的Adapter 对象,如果 mAdapter 不是HeaderViewListAdapter 的直接实例,则调用 wrapHeaderListAdapterInternal() 方法,以顶部View、底部View和mAdapter为参数,来将 mAdapter 转为 HeaderViewListAdapter 对象

   protected void wrapHeaderListAdapterInternal() {        mAdapter = wrapHeaderListAdapterInternal(mHeaderViewInfos, mFooterViewInfos, mAdapter);    }    protected HeaderViewListAdapter wrapHeaderListAdapterInternal(            ArrayList<ListView.FixedViewInfo> headerViewInfos,            ArrayList<ListView.FixedViewInfo> footerViewInfos,            ListAdapter adapter) {        return new HeaderViewListAdapter(headerViewInfos, footerViewInfos, adapter);    }

查看 HeaderViewListAdapter 类的一些方法,可以看出 HeaderViewListAdapter 在计算子项总数和获取子项实例时,都是将顶部View和底部View包含进来的。

public int getCount() {        if (mAdapter != null) {            return getFootersCount() + getHeadersCount() + mAdapter.getCount();        } else {            return getFootersCount() + getHeadersCount();        }    }public Object getItem(int position) {        // Header (negative positions will throw an IndexOutOfBoundsException)        int numHeaders = getHeadersCount();        if (position < numHeaders) {            return mHeaderViewInfos.get(position).data;        }        // Adapter        final int adjPosition = position - numHeaders;        int adapterCount = 0;        if (mAdapter != null) {            adapterCount = mAdapter.getCount();            if (adjPosition < adapterCount) {                return mAdapter.getItem(adjPosition);            }        }        // Footer (off-limits positions will throw an IndexOutOfBoundsException)        return mFooterViewInfos.get(adjPosition - adapterCount).data;    }

经过以上的分析,就给我们提供了设计思路。我们同样可以设计一个包裹了 RecyclerView Adapter 和顶部底部View的外部Adapter,再将该Adapter设置给 RecyclerView。此外,由于原生的 RecyclerView 没有对应的 addHeaderView() 和 addFooterView() 方法,所以也需要再来继承 RecyclerView,在子类中定义需要的方法。

二、自定义Adapter

先定义几个需要用到的变量,headerViews 和 footerViews 分别用于存储顶部View和底部View,两个整数值则是会不断自增加一,用来作为SparseArray< View > 的Key值,adapter则是指向为RecyclerView设置的Adapter

    private RecyclerView.Adapter adapter;    private SparseArray<View> headerViews;    private SparseArray<View> footerViews;    //头部类型开始位置,用于viewType    private static int BASE_ITEM_TYPE_HEADER = 1000;    //底部类型开始位置,用于viewType    private static int BASE_ITEM_TYPE_FOOTER = 2000;

然后声明几个用来添加和移除View的方法

/**     * 添加头部View     *     * @param view 头部View     */    public void addHeaderView(View view) {        if (headerViews.indexOfValue(view) < 0) {            headerViews.put(BASE_ITEM_TYPE_HEADER++, view);            notifyDataSetChanged();        }    }    /**     * 添加底部View     *     * @param view 底部View     */    public void addFooterView(View view) {        if (footerViews.indexOfValue(view) < 0) {            footerViews.put(BASE_ITEM_TYPE_FOOTER++, view);            notifyDataSetChanged();        }    }    /**     * 移除头部View     *     * @param view View     */    public void removeHeaderView(View view) {        int index = headerViews.indexOfValue(view);        if (index > -1) {            headerViews.removeAt(index);            notifyDataSetChanged();        }    }    /**     * 移除底部View     *     * @param view View     */    public void removeFooterView(View view) {        int index = footerViews.indexOfValue(view);        if (index > -1) {            footerViews.removeAt(index);            notifyDataSetChanged();        }    }

重点是 getItemViewType() 方法,如果索引值position指向的是顶部View或者底部View,则返回该View在 SparseArray< View >中的Key值,以该值作为View的 ItemViewType。如果索引指向的是中间的展示数据的子项,则调用adapter本身相同的方法

 /**     * 根据索引判断该位置的View类型     * 如果是头部,则返回该View在headerViews中的key     * 如果是底部,则返回该View在footerViews中的key     *     * @param position 索引     * @return View类型     */    @Override    public int getItemViewType(int position) {        if (isHeaderPosition(position)) {            return headerViews.keyAt(position);        }        if (isFooterPosition(position)) {            position = position - headerViews.size() - adapter.getItemCount();            return footerViews.keyAt(position);        }        position = position - headerViews.size();        return adapter.getItemViewType(position);    }

为不同的View指定了不同的ItemViewType后,则可以在onCreateViewHolder() 方法中返回不同的 ViewHolder 对象了

    @Override    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {        if (isHeaderViewType(viewType)) {            return createHeaderFooterViewHolder(headerViews.get(viewType));        }        if (isFooterViewType(viewType)) {            return createHeaderFooterViewHolder(footerViews.get(viewType));        }        return adapter.onCreateViewHolder(parent, viewType);    }

总的方法定义如下所示:

/** * 作者: 叶应是叶 * 时间: 2017/6/4 */public class WrapRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {    private RecyclerView.Adapter adapter;    private SparseArray<View> headerViews;    private SparseArray<View> footerViews;    //头部类型开始位置,用于viewType    private int BASE_ITEM_TYPE_HEADER = 1000;    //底部类型开始位置,用于viewType    private int BASE_ITEM_TYPE_FOOTER = 2000;    public WrapRecyclerViewAdapter(RecyclerView.Adapter adapter) {        this.adapter = adapter;        headerViews = new SparseArray<>();        footerViews = new SparseArray<>();        this.adapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {            @Override            public void onChanged() {                super.onChanged();                notifyDataSetChanged();            }        });    }    @Override    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {        if (isHeaderViewType(viewType)) {            return createHeaderFooterViewHolder(headerViews.get(viewType));        }        if (isFooterViewType(viewType)) {            return createHeaderFooterViewHolder(footerViews.get(viewType));        }        return adapter.onCreateViewHolder(parent, viewType);    }    @Override    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {        if (isHeaderPosition(position) || isFooterPosition(position)) {            return;        }        adapter.onBindViewHolder(holder, position - headerViews.size());    }    /**     * 获取列表总的条数(头部View个数+列表条数+底部View个数)     *     * @return 总的条数     */    @Override    public int getItemCount() {        return adapter.getItemCount() + headerViews.size() + footerViews.size();    }    /**     * 获取不包含头部和底部View之后列表的条数     *     * @return 列表条数     */    public int getDataItemCount() {        return adapter.getItemCount();    }    /**     * 根据索引判断该位置的View类型     * 如果是头部,则返回该View在headerViews中的key     * 如果是底部,则返回该View在footerViews中的key     *     * @param position 索引     * @return View类型     */    @Override    public int getItemViewType(int position) {        if (isHeaderPosition(position)) {            return headerViews.keyAt(position);        }        if (isFooterPosition(position)) {            position = position - headerViews.size() - adapter.getItemCount();            return footerViews.keyAt(position);        }        position = position - headerViews.size();        return adapter.getItemViewType(position);    }    /**     * 创建头部View或底部View的ViewHolder     *     * @param view 头部View或底部View     * @return ViewHolder     */    private RecyclerView.ViewHolder createHeaderFooterViewHolder(View view) {        return new RecyclerView.ViewHolder(view) {        };    }    /**     * 判断是否是头部View     *     * @param key Key     * @return 是否是头部View     */    private boolean isHeaderViewType(int key) {        return headerViews.indexOfKey(key) > -1;    }    /**     * 判断是否是底部View     *     * @param key Key     * @return 是否是底部View     */    private boolean isFooterViewType(int key) {        return footerViews.indexOfKey(key) > -1;    }    /**     * 根据索引判断该位置的View是否是头部View     *     * @param position 索引     * @return 是否是头部View     */    private boolean isHeaderPosition(int position) {        return (position > -1) && (position < headerViews.size());    }    /**     * 根据索引判断该位置的View是否是底部View     *     * @param position 索引     * @return 是否是底部View     */    private boolean isFooterPosition(int position) {        return (position >= (headerViews.size() + adapter.getItemCount())) &&                (position < (headerViews.size() + adapter.getItemCount() + footerViews.size()));    }    /**     * 添加头部View     *     * @param view 头部View     */    public void addHeaderView(View view) {        if (headerViews.indexOfValue(view) < 0) {            headerViews.put(BASE_ITEM_TYPE_HEADER++, view);            notifyDataSetChanged();        }    }    /**     * 添加底部View     *     * @param view 底部View     */    public void addFooterView(View view) {        if (footerViews.indexOfValue(view) < 0) {            footerViews.put(BASE_ITEM_TYPE_FOOTER++, view);            notifyDataSetChanged();        }    }    /**     * 移除头部View     *     * @param view View     */    public void removeHeaderView(View view) {        int index = headerViews.indexOfValue(view);        if (index > -1) {            headerViews.removeAt(index);            notifyDataSetChanged();        }    }    /**     * 移除底部View     *     * @param view View     */    public void removeFooterView(View view) {        int index = footerViews.indexOfValue(view);        if (index > -1) {            footerViews.removeAt(index);            notifyDataSetChanged();        }    }}

三、自定义RecyclerView

继承 RecyclerView 实现 WrapRecyclerView 子类
首先需要声明两个变量

    /**     * 用来指向传入的 Adapter 或来构造 WrapRecyclerViewAdapter     */    private WrapRecyclerViewAdapter wrapRecyclerViewAdapter;    /**     * 用来指向传入的Adapter     */    private Adapter mRecyclerAdapter;

仿照ListView的思路来重写 setAdapter() 方法,构建一个 WrapRecyclerViewAdapter 对象作为实际的Adapter。
当中,需要注意的是,如果传入的 recyclerAdapter 是直接继承于 WrapViewRecycleAdapter 的话,则直接强转类型就可以了,否则的话需要再来根据 recyclerAdapter 构造一个 WrapViewRecycleAdapter 对象
如果 wrapRecyclerViewAdapter 是通过强转得来的,则当 mRecyclerAdapter 数据刷新时,wrapRecyclerViewAdapter 自然也会做出相应的变化,因为两者指向的是同一个对象。
如果 wrapRecyclerViewAdapter 是用new关键字重新声明的,则需要在为 mRecyclerAdapter 注册一个观察者对象,在 mRecyclerAdapter 数据刷新时同时通知 wrapRecyclerViewAdapter 也进行数据刷新。

 @Override    public void setAdapter(Adapter recyclerAdapter) {        if (mRecyclerAdapter != null) {            mRecyclerAdapter.unregisterAdapterDataObserver(adapterDataObserver);            mRecyclerAdapter = null;        }        mRecyclerAdapter = recyclerAdapter;        // 如果传入的 recyclerAdapter 是直接继承于 WrapViewRecycleAdapter 的话,则直接强转类型        // 否则的话再来根据 recyclerAdapter 构造一个 WrapViewRecycleAdapter        if (mRecyclerAdapter instanceof WrapRecyclerViewAdapter) {            wrapRecyclerViewAdapter = (WrapRecyclerViewAdapter) mRecyclerAdapter;        } else {            // 注册观察者对象            mRecyclerAdapter.registerAdapterDataObserver(adapterDataObserver);            wrapRecyclerViewAdapter = new WrapRecyclerViewAdapter(mRecyclerAdapter);        }        super.setAdapter(wrapRecyclerViewAdapter);    }

总的代码如下所示:

/** * 作者: 叶应是叶 * 时间: 2017/6/4 * 描述: 可以带头部View与尾部View的RecyclerView */public class WrapRecyclerView extends RecyclerView {    /**     * 用来指向传入的 Adapter 或来构造 WrapRecyclerViewAdapter     */    private WrapRecyclerViewAdapter wrapRecyclerViewAdapter;    /**     * 用来指向传入的Adapter     */    private Adapter mRecyclerAdapter;    public WrapRecyclerView(Context context) {        super(context);    }    public WrapRecyclerView(Context context, AttributeSet attrs) {        super(context, attrs);    }    public WrapRecyclerView(Context context, AttributeSet attrs, int defStyle) {        super(context, attrs, defStyle);    }    @Override    public void setAdapter(Adapter recyclerAdapter) {        if (mRecyclerAdapter != null) {            mRecyclerAdapter.unregisterAdapterDataObserver(adapterDataObserver);            mRecyclerAdapter = null;        }        mRecyclerAdapter = recyclerAdapter;        // 如果传入的 recyclerAdapter 是直接继承于 WrapViewRecycleAdapter 的话,则直接强转类型        // 否则的话再来根据 recyclerAdapter 构造一个 WrapViewRecycleAdapter        if (mRecyclerAdapter instanceof WrapRecyclerViewAdapter) {            wrapRecyclerViewAdapter = (WrapRecyclerViewAdapter) mRecyclerAdapter;        } else {            // 注册观察者对象            mRecyclerAdapter.registerAdapterDataObserver(adapterDataObserver);            wrapRecyclerViewAdapter = new WrapRecyclerViewAdapter(mRecyclerAdapter);        }        super.setAdapter(wrapRecyclerViewAdapter);    }    /**     * AdapterDataObserver是RecyclerView内部的一个抽象类     * 用来作为观察者监听数据变化     */    private AdapterDataObserver adapterDataObserver = new AdapterDataObserver() {        @Override        public void onChanged() {            if (wrapRecyclerViewAdapter != mRecyclerAdapter) {                wrapRecyclerViewAdapter.notifyDataSetChanged();            }        }        @Override        public void onItemRangeRemoved(int positionStart, int itemCount) {            if (wrapRecyclerViewAdapter != mRecyclerAdapter) {                wrapRecyclerViewAdapter.notifyItemRemoved(positionStart);            }        }        @Override        public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {            if (wrapRecyclerViewAdapter != mRecyclerAdapter) {                wrapRecyclerViewAdapter.notifyItemMoved(fromPosition, toPosition);            }        }        @Override        public void onItemRangeChanged(int positionStart, int itemCount) {            if (wrapRecyclerViewAdapter != mRecyclerAdapter) {                wrapRecyclerViewAdapter.notifyItemChanged(positionStart);            }        }        @Override        public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {            if (wrapRecyclerViewAdapter != mRecyclerAdapter) {                wrapRecyclerViewAdapter.notifyItemChanged(positionStart, payload);            }        }        @Override        public void onItemRangeInserted(int positionStart, int itemCount) {            if (wrapRecyclerViewAdapter != mRecyclerAdapter) {                wrapRecyclerViewAdapter.notifyItemInserted(positionStart);            }        }    };    /**     * 添加头部View     *     * @param view View     */    public void addHeaderView(View view) {        if (wrapRecyclerViewAdapter != null) {            wrapRecyclerViewAdapter.addHeaderView(view);        } else {            throw new RuntimeException("WrapRecyclerViewAdapter == null");        }    }    /**     * 添加底部View     *     * @param view View     */    public void addFooterView(View view) {        if (wrapRecyclerViewAdapter != null) {            wrapRecyclerViewAdapter.addFooterView(view);        } else {            throw new RuntimeException("WrapRecyclerViewAdapter == null");        }    }    /**     * 移除头部View     *     * @param view View     */    public void removeHeaderView(View view) {        if (wrapRecyclerViewAdapter != null) {            wrapRecyclerViewAdapter.removeHeaderView(view);        } else {            throw new RuntimeException("WrapRecyclerViewAdapter == null");        }    }    /**     * 移除底部View     *     * @param view View     */    public void removeFooterView(View view) {        if (wrapRecyclerViewAdapter != null) {            wrapRecyclerViewAdapter.removeFooterView(view);        } else {            throw new RuntimeException("WrapRecyclerViewAdapter == null");        }    }}

四、实际使用

以我上一篇文章:解析RecyclerView(1)——带点击事件监听的通用Adapter 使用到的 MyCommonRecyclerAdapter 类作为最原始的Adapter

在布局文件中声明的RecyclerView就要使用自定义的 WrapRecyclerView 了,再增加几个按钮用于进行RecyclerView展示的数据,顶部View和底部View的增添删除操作。

<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent"    tools:context="com.czy.demo.RecyclerView.Wrap.WrapRecyclerActivity">    <Button        android:id="@+id/btn_addData"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:text="增添数据" />    <Button        android:id="@+id/btn_deleteData"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:layout_below="@id/btn_addData"        android:text="删除数据" />    <Button        android:id="@+id/btn_addHeaderView"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:layout_below="@id/btn_deleteData"        android:text="增加头部View" />    <Button        android:id="@+id/btn_deleteHeaderView"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:layout_below="@id/btn_addHeaderView"        android:text="删除头部View" />    <Button        android:id="@+id/btn_addFooterView"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:layout_below="@id/btn_deleteHeaderView"        android:text="增加底部View" />    <Button        android:id="@+id/btn_deleteFooterView"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:layout_below="@id/btn_addFooterView"        android:text="删除底部View" />    <com.czy.common.RecyclerView.Wrap.WrapRecyclerView        android:id="@+id/wrv_dataList"        android:layout_width="match_parent"        android:layout_height="match_parent"        android:layout_below="@id/btn_deleteFooterView" /></RelativeLayout>

Activity总的代码如下所示,加载的顶部View布局文件 R.layout.header_view 只包含含一个 ImageView 控件,底部View布局文件 R.layout.footer _view 只包含一个 TextView 控件

public class WrapRecyclerActivity extends AppCompatActivity implements CommonRecyclerHolder.onClickCommonListener, View.OnClickListener {    private List<Data> dataList;    private List<View> headerViewList;    private List<View> footerViewList;    private WrapRecyclerView wrv_dataList;    private MyCommonRecyclerAdapter myCommonRecyclerAdapter;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_wrap_recycler);        initData();        wrv_dataList = (WrapRecyclerView) findViewById(R.id.wrv_dataList);        wrv_dataList.setLayoutManager(new LinearLayoutManager(this));        myCommonRecyclerAdapter = new MyCommonRecyclerAdapter(this, dataList, R.layout.item, this);        wrv_dataList.setAdapter(myCommonRecyclerAdapter);        View headerView1 = getLayoutInflater().inflate(R.layout.header_view, wrv_dataList, false);        View headerView2 = getLayoutInflater().inflate(R.layout.header_view, wrv_dataList, false);        View footerView1 = getLayoutInflater().inflate(R.layout.footer_view, wrv_dataList, false);        View footerView2 = getLayoutInflater().inflate(R.layout.footer_view, wrv_dataList, false);        wrv_dataList.addHeaderView(headerView1);        wrv_dataList.addHeaderView(headerView2);        wrv_dataList.addFooterView(footerView1);        wrv_dataList.addFooterView(footerView2);        headerViewList.add(headerView1);        headerViewList.add(headerView2);        footerViewList.add(footerView1);        footerViewList.add(footerView2);        findViewById(R.id.btn_addData).setOnClickListener(this);        findViewById(R.id.btn_deleteData).setOnClickListener(this);        findViewById(R.id.btn_addHeaderView).setOnClickListener(this);        findViewById(R.id.btn_deleteHeaderView).setOnClickListener(this);        findViewById(R.id.btn_addFooterView).setOnClickListener(this);        findViewById(R.id.btn_deleteFooterView).setOnClickListener(this);    }    @Override    public void onClick(View v) {        switch (v.getId()) {            case R.id.btn_addData:                Data data = new Data(R.mipmap.ic_launcher, "Hi");                dataList.add(data);                myCommonRecyclerAdapter.notifyDataSetChanged();                break;            case R.id.btn_deleteData:                if (dataList.size() > 0) {                    dataList.remove(0);                }                myCommonRecyclerAdapter.notifyDataSetChanged();                break;            case R.id.btn_addHeaderView:                View headerView = getLayoutInflater().inflate(R.layout.header_view, wrv_dataList, false);                headerViewList.add(headerView);                wrv_dataList.addHeaderView(headerView);                break;            case R.id.btn_deleteHeaderView:                if (headerViewList.size() > 0) {                    wrv_dataList.removeHeaderView(headerViewList.get(0));                    headerViewList.remove(0);                }                break;            case R.id.btn_addFooterView:                View footerView = getLayoutInflater().inflate(R.layout.footer_view, wrv_dataList, false);                footerViewList.add(footerView);                wrv_dataList.addFooterView(footerView);                break;            case R.id.btn_deleteFooterView:                if (footerViewList.size() > 0) {                    wrv_dataList.removeFooterView(footerViewList.get(0));                    footerViewList.remove(0);                }                break;        }    }    private void initData() {        dataList = new ArrayList<>();        headerViewList = new ArrayList<>();        footerViewList = new ArrayList<>();        for (int i = 0; i < 50; i++) {            Data data = new Data(R.mipmap.ic_launcher_round, "Hi:" + i);            dataList.add(data);        }    }    @Override    public void onClick(int position) {        Toast.makeText(this, "点击:" + position, Toast.LENGTH_SHORT).show();    }    @Override    public void onLongClick(int position) {        Toast.makeText(this, "长按:" + position, Toast.LENGTH_SHORT).show();    }}

运行效果:

这里写图片描述

这里提供代码下载:解析RecyclerView(2)——带顶部View和底部View的RecyclerView

阅读全文
0 0
原创粉丝点击