【从 0 开始开发一款直播 APP】3.4 高层封装之 Adapter -- RecyclerView 优雅的添加 Header、Footer

本文为菜鸟窝作者蒋志碧的连载。“从 0 开始开发一款直播 APP ”系列来聊聊时下最火的直播 APP,如何完整的实现一个类"腾讯直播"的商业化项目
视频地址:http://www.cniao5.com/course/10121


【从 0 开始开发一款直播 APP】3.1 高层封装之 Adapter — ListView & GridView
【从 0 开始开发一款直播 APP】3.2 高层封装之 Adapter — RecyclerView 实现单布局展示
【从 0 开始开发一款直播 APP】3.3 高层封装之 Adapter -- RecyclerView 实现多条目展示
【从 0 开始开发一款直播 APP】3.4 高层封装之 Adapter -- RecyclerView 优雅的添加 Header、Footer


一、优雅的添加 Header、Footer

上一章讲解了 RecyclerView 封装的多条目实现聊天界面,接下来讲解 RecyclerView 实现添加 Header、Footer。运行效果如下:

线性布局

网格布局

RecyclerView 系统没有封装添加头部和底部方法,但是 ListView 和 GridView 有封装,通过查看源码,可以类似封装一个添加头部和尾部的类。ListView 本身也不支持头部和尾部,是在 ListView 上做了一层包裹。

ListView 源码解析 -- ListView.addHeaderView(View v, Object data, boolean isSelectable)

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;

        //判断 Adapter 是否为空,不判断无法添加头部
        // 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();
            }
        }
    }

**ListView 源码解析 -- **HeaderViewListAdapter.java

public class HeaderViewListAdapter implements WrapperListAdapter, Filterable {

    private final ListAdapter mAdapter;

    // These two ArrayList are assumed to NOT be null.
    // They are indeed created when declared in ListView and then shared.
    //存放头部和底部集合
    ArrayList<ListView.FixedViewInfo> mHeaderViewInfos;
    ArrayList<ListView.FixedViewInfo> mFooterViewInfos;

    public HeaderViewListAdapter(ArrayList<ListView.FixedViewInfo> headerViewInfos,
                                 ArrayList<ListView.FixedViewInfo> footerViewInfos,
                                 ListAdapter adapter) {
        //原始列表 Adapter
        mAdapter = adapter;
        //......
    }
    
   //移除头部
    public boolean removeHeader(View v) {
        for (int i = 0; i < mHeaderViewInfos.size(); i++) {
            ListView.FixedViewInfo info = mHeaderViewInfos.get(i);
            if (info.view == v) {
                mHeaderViewInfos.remove(i);

                mAreAllFixedViewsSelectable =
                        areAllListInfosSelectable(mHeaderViewInfos)
                        && areAllListInfosSelectable(mFooterViewInfos);

                return true;
            }
        }

        return false;
    }
    
    //移除底部
    public boolean removeFooter(View v) {
        for (int i = 0; i < mFooterViewInfos.size(); i++) {
            ListView.FixedViewInfo info = mFooterViewInfos.get(i);
            if (info.view == v) {
                mFooterViewInfos.remove(i);

                mAreAllFixedViewsSelectable =
                        areAllListInfosSelectable(mHeaderViewInfos)
                        && areAllListInfosSelectable(mFooterViewInfos);

                return true;
            }
        }

        return false;
    }

    //获取条目 
    public int getCount() {
        if (mAdapter != null) {
            //头部条目 + Adapter 条目 + 底部条目
            return getFootersCount() + getHeadersCount() + mAdapter.getCount();
        } else {
            return getFootersCount() + getHeadersCount();
        }
    }

    //getView 
    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;
    }
    
    //getItemViewType 和 RecyclerView 相似
    public int getItemViewType(int position) {
        int numHeaders = getHeadersCount();
        if (mAdapter != null && position >= numHeaders) {
            int adjPosition = position - numHeaders;
            int adapterCount = mAdapter.getCount();
            if (adjPosition < adapterCount) {
                return mAdapter.getItemViewType(adjPosition);
            }
        }

        return AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER;
    }
}

根据 ListView 的头部添加类,经过改写,可以得到我们自己的封装类,头部和底部可能有多个,因此需要用集合标识,这里使用 SparseArray。

public class WrapRecyclerAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

    //不包含头部和底部
    private RecyclerView.Adapter mAdapter;

    //由于头部和底部可能有多个,需要用标识来识别
    private int BASE_HEADER_KEY = 5500000;
    private int BASE_Footer_KEY = 6600000;

    //头部和底部集合 必须要用map集合进行标识 key->int  value->object
    SparseArray<View> mHeaderViews;
    SparseArray<View> mFooterViews;

    public WrapRecyclerAdapter(RecyclerView.Adapter adapter) {
        mAdapter = adapter;
        mHeaderViews = new SparseArray<>();
        mFooterViews = new SparseArray<>();
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

        //区分头部和底部,根据ViewType来
        //ViewType可能有三部分  头部 底部 Adapter

        //判断是否为头部
        if (isHeaderViewType(viewType)) {
            View headerView = mHeaderViews.get(viewType);
            //header
            return createHeaderOrFooterViewHolder(headerView);
        }
        
        //判断是否为底部
        if (isFooterViewType(viewType)) {
            View footerView = mFooterViews.get(viewType);
            //footer
            return createHeaderOrFooterViewHolder(footerView);
        }

        //列表
        return mAdapter.onCreateViewHolder(parent, viewType);
    }


    /**
     * 创建头部和底部ViewHolder
     * @param view
     * @return
     */
    private RecyclerView.ViewHolder createHeaderOrFooterViewHolder(View view) {
        return new RecyclerView.ViewHolder(view) {};
    }

    @Override
    public int getItemViewType(int position) {
        //position -> viewtype  头部 底部 adapter  必须要用map集合进行标识
        //if(头部)return 头部 key
        //if(中间位置)return mAdapter.getItemViewType(position);
        //if(底部) return 底部 key

        //header
        if (isHeaderPosition(position)) {
            return mHeaderViews.keyAt(position);
        }

        // footer
        if (isFooterPosition(position)) {
            position = position - mHeaderViews.size() - mAdapter.getItemCount();
            return mFooterViews.keyAt(position);
        }

        //adapter
        position = position - mHeaderViews.size();
        return mAdapter.getItemViewType(position);
    }


    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {

        //头部,底部不需要绑定数据
        if (isHeaderPosition(position) || isFooterPosition(position)) {
            return;
        }

        // Adapter
        position = position - mHeaderViews.size();
        mAdapter.onBindViewHolder(holder, position);
    }

    @Override
    public int getItemCount() {
        return mAdapter.getItemCount() + mHeaderViews.size() + mFooterViews.size();
    }

    //添加头部,底部
    public void addHeaderView(View view) {
        //没有包含头部
        int position = mHeaderViews.indexOfValue(view);
        if (position < 0) {
            //集合没有就添加,不能重复添加
            mHeaderViews.put(BASE_HEADER_KEY++, view);
        }
        notifyDataSetChanged();
    }

    public void addFooterView(View view) {
        //没有包含头部
        int position = mFooterViews.indexOfValue(view);
        if (position < 0) {
            //集合没有就添加,不能重复添加
            mFooterViews.put(BASE_Footer_KEY++, view);
        }
        notifyDataSetChanged();
    }

    //移除头部,底部
    public void removeHeaderView(View view) {
        //没有包含头部
        int index = mHeaderViews.indexOfValue(view);
        if (index < 0) return;
        //集合没有就添加,不能重复添加
        mHeaderViews.removeAt(index);
        notifyDataSetChanged();
    }

    public void removeFooterView(View view) {
        //没有包含底部
        int index = mFooterViews.indexOfValue(view);
        if (index < 0) return;
        //集合没有就添加,不能重复添加
        mFooterViews.removeAt(index);
        notifyDataSetChanged();
    }

    //是否为底部
    private boolean isFooterViewType(int viewType) {
        int footerPosition = mFooterViews.indexOfKey(viewType);
        return footerPosition >= 0;
    }

    //是否为头部
    private boolean isHeaderViewType(int viewType) {
        int headerPosition = mHeaderViews.indexOfKey(viewType);
        return headerPosition >= 0;
    }

    //是否为底部位置
    private boolean isFooterPosition(int position) {
        return position >= (mHeaderViews.size() + mAdapter.getItemCount());
    }

    //是否为头部位置
    private boolean isHeaderPosition(int position) {
        return position < mHeaderViews.size();
    }
}

二、线性布局添加 Header、Footer

public class AdapterActivity extends BaseActivity {
  
    private MutipleAdaper mMutipleAdaper;
    private WrapRecyclerAdapter mWrapRecyclerAdapter;
    private RecyclerView mRecyclerView;

    @Override
    protected void setToolbar() {
    }

    @Override
    protected void setListener() {
    }

    @Override
    protected void initData() {
        Datas = new ArrayList<>();
        for (int i = 1; i <= 30; i++) {
            if (i % 2 == 0) {
                Datas.add(new Item(R.drawable.tab_publish_normal,"我 get 新技能 " + i,0));
            }else {
                Datas.add(new Item(R.drawable.tab_publish_normal,"你 get 新技能 " + i,1));
            }
        }
      
        mMutipleAdaper = new MutipleAdaper(this,Datas);
        mWrapRecyclerAdapter = new WrapRecyclerAdapter(mMutipleAdaper);
        mRecyclerView.setAdapter(mWrapRecyclerAdapter);

        View headerView = LayoutInflater.from(this).inflate(R.layout.layout_header,mRecyclerView,false);
        View footView = LayoutInflater.from(this).inflate(R.layout.layout_footer,mRecyclerView,false);
        mWrapRecyclerAdapter.addHeaderView(headerView);
        mWrapRecyclerAdapter.addFooterView(footView);
    }

    @Override
    protected void initView() {
        mRecyclerView = obtainView(R.id.recyclerView);
        mRecyclerView.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL_LIST));
        mRecyclerView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false));
    }

    @Override
    protected int getLayoutId() {
        return R.layout.activity_adapter;
    }
}

三、运行效果图

四、WrapRecyclerView -- 自定义 RecyclerView

可以看到上面使用头部Adapter很不友好,需要将之前封装的布局添加到包裹 Adapter 中,可以将自定义一个 RecyclerView,让其继承 RecyclerView,将 WrapRecyclerAdapter 传入,调用其方法。

public class WrapRecyclerView extends RecyclerView {

    private WrapRecyclerAdapter mWrapRecyclerAdapter;

    private RecyclerView.Adapter mAdapter;

    public WrapRecyclerView(Context context) {
        this(context, null);
    }

    public WrapRecyclerView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public WrapRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    @Override
    public void setAdapter(Adapter adapter) {
        //防止多次设置Adapter
        if (mAdapter != null) {
            mAdapter = null;
        }

        this.mAdapter = adapter;

        if (adapter instanceof WrapRecyclerAdapter) {
            mWrapRecyclerAdapter = (WrapRecyclerAdapter) adapter;
        } else {
            mWrapRecyclerAdapter = new WrapRecyclerAdapter(adapter);
        }

        //删除的问题是列表的Adapter改变,但WrapRecyclerAdapter没有改,观察者模式
        super.setAdapter(mWrapRecyclerAdapter);

    }

    //添加头部,底部
    public void addHeaderView(View view) {
        //如果没有Adapter就不添加,也可以抛异常
        //必须先设置Adapter才能添加,仿照ListView的处理方式
        if (mWrapRecyclerAdapter != null) {
            mWrapRecyclerAdapter.addHeaderView(view);
        }
    }

    public void addFooterView(View view) {
        if (mWrapRecyclerAdapter != null) {
            mWrapRecyclerAdapter.addFooterView(view);
        }

    }


    //移除头部,底部
    public void removeHeaderView(View view) {
        if (mWrapRecyclerAdapter != null) {
            mWrapRecyclerAdapter.removeHeaderView(view);
        }
    }

    public void removeFooterView(View view) {
        if (mWrapRecyclerAdapter != null) {
            mWrapRecyclerAdapter.removeFooterView(view);
        }
    }
}

使用的时候其它不变,只需要稍微修改

五、WrapRecyclerView 的使用

布局文件不能使用 RecyclerView,使用自定义的 WrapRecyclerView

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.dali.admin.fragment.AdapterActivity">

   <!--<android.support.v7.widget.RecyclerView-->
       <!--android:id="@+id/recyclerView"-->
       <!--android:layout_width="match_parent"-->
       <!--android:layout_height="wrap_content">-->

   <!--</android.support.v7.widget.RecyclerView>-->

   <com.dali.admin.fragment.recycler.WrapRecyclerView
       android:id="@+id/recyclerView"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"></com.dali.admin.fragment.recycler.WrapRecyclerView>

</LinearLayout>

在AdapterActivity 中只需要将 WrapRecyclerView 声明,直接调用 addHeaderView() 就可以添加头部,添加底部类似

    private WrapRecyclerView mRecyclerView; 
    View headerView = LayoutInflater.from(this).inflate(R.layout.layout_header,mRecyclerView,false);
    View footerView = LayoutInflater.from(this).inflate(R.layout.layout_footer,mRecyclerView,false);

    mRecyclerView.addHeaderView(headerView);
    mRecyclerView.addFooterView(footerView);

看到这里,基本封装就完了,细心的朋友会发现没有封装监听,移除头部和底部很简单,直接添加监听就行

headerView.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        mRecyclerView.removeHeaderView(v);
    }
});

 footerView.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            mRecyclerView.removeFooterView(v);
        }
    });

但是问题来了,我们在 ViewHolder 添加监听,Adapter 条目不会移除。怎么点都没反应。

public class MutipleAdaper extends RecyclerViewAdapter<Item> {

    public MutipleAdaper(Context context, List<Item> datas) {
        super(context, datas, new MutipleTypeSupport<Item>() {
            @Override
            public int getLayoutId(Item item) {
                if (item.getType() == 1){
                    return R.layout.list_item;
                }else {
                    return R.layout.list_item1;
                }
            }
        });
    }

    @Override
    protected void bindData(RecyclerViewHolder holder, final Item item, int position) {

        holder.setText(R.id.tv1,item.getTv1())
                .setImageResource(R.id.img,item.getRes())
        .setOnClickListener(R.id.tv1, new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(mContext,item.getTv1(),Toast.LENGTH_SHORT).show();
                mDatas.remove(item);    //移除条目 item
                notifyDataSetChanged();
            }
        });
    }
}

原因是因为我们操作的 Adapter 不是同一个,点击的条目 Adapter 是 MutipleAdaper,WrapRecyclerView 里面WrapRecyclerAdapter 没有改变,因此没有实现同步更新,这里需要使用到观察者模式。

我们可以在 ListView 的 HeaderViewListAdapter 源码里也看到有使用观察者模式。

public void registerDataSetObserver(DataSetObserver observer) {
    if (mAdapter != null) {
        mAdapter.registerDataSetObserver(observer);
    }
}

public void unregisterDataSetObserver(DataSetObserver observer) {
    if (mAdapter != null) {
        mAdapter.unregisterDataSetObserver(observer);
    }
}

我们也需要为其添加观察者模式,使通知到每个 Adapter,保证数据同步更新。否则列表的notifyDataSetChanged() 没效果

public class WrapRecyclerView extends RecyclerView {

    private WrapRecyclerAdapter mWrapRecyclerAdapter;
    private RecyclerView.Adapter mAdapter;

    //重写观察者类
    private AdapterDataObserver mDataObserver = new AdapterDataObserver() {
        @Override
        public void onChanged() {
            if (mAdapter == null) return;
            if (mWrapRecyclerAdapter != mAdapter)
                mWrapRecyclerAdapter.notifyDataSetChanged();
        }

        @Override
        public void onItemRangeRemoved(int positionStart, int itemCount) {
            if (mAdapter == null) return;
            if (mWrapRecyclerAdapter != mAdapter)
                mWrapRecyclerAdapter.notifyItemRemoved(positionStart);
        }

        @Override
        public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
            if (mAdapter == null) return;
            if (mWrapRecyclerAdapter != mAdapter)
                mWrapRecyclerAdapter.notifyItemMoved(fromPosition, toPosition);
        }

        @Override
        public void onItemRangeChanged(int positionStart, int itemCount) {
            if (mAdapter == null) return;
            if (mWrapRecyclerAdapter != mAdapter)
                mWrapRecyclerAdapter.notifyItemChanged(positionStart);
        }

        @Override
        public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
            if (mAdapter == null) return;
            if (mWrapRecyclerAdapter != mAdapter)
                mWrapRecyclerAdapter.notifyItemChanged(positionStart, payload);
        }

        @Override
        public void onItemRangeInserted(int positionStart, int itemCount) {
            if (mAdapter == null) return;
            if (mWrapRecyclerAdapter != mAdapter)
                mWrapRecyclerAdapter.notifyItemInserted(positionStart);
        }
    };


    public WrapRecyclerView(Context context) {
        this(context, null);
    }

    public WrapRecyclerView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public WrapRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    @Override
    public void setAdapter(Adapter adapter) {

        //防止多次设置Adapter
        if (mAdapter != null) {//注销观察者
            mAdapter.unregisterAdapterDataObserver(mDataObserver);
            mAdapter = null;
        }

        this.mAdapter = adapter;

        if (adapter instanceof WrapRecyclerAdapter) {
            mWrapRecyclerAdapter = (WrapRecyclerAdapter) adapter;
        } else {
            mWrapRecyclerAdapter = new WrapRecyclerAdapter(adapter);
        }

        //删除的问题是列表的 Adapter 改变,但 WrapRecyclerAdapter没有改,观察者模式
        super.setAdapter(mWrapRecyclerAdapter);

        //注册观察者模式
        mAdapter.registerAdapterDataObserver(mDataObserver);

    }

    //添加头部,底部
    public void addHeaderView(View view) {

        //如果没有Adapter就不添加,也可以抛异常
        //必须先设置Adapter才能添加,仿照ListView的处理方式
        if (mWrapRecyclerAdapter != null) {
            mWrapRecyclerAdapter.addHeaderView(view);
        }
    }

    public void addFooterView(View view) {
        if (mWrapRecyclerAdapter != null) {
            mWrapRecyclerAdapter.addFooterView(view);
        }
    }

    //移除头部,底部
    public void removeHeaderView(View view) {

        if (mWrapRecyclerAdapter != null) {
            mWrapRecyclerAdapter.removeHeaderView(view);
        }
    }

    public void removeFooterView(View view) {
        if (mWrapRecyclerAdapter != null) {
            mWrapRecyclerAdapter.removeFooterView(view);
        }
    }
}

六、完整的代码

public class AdapterActivity extends BaseActivity {
    private MutipleAdaper mMutipleAdaper;
    private WrapRecyclerView mRecyclerView;
    private ArrayList<Item> Datas;

    @Override
    protected void setToolbar() {
    }

    @Override
    protected void setListener() {
    }

    @Override
    protected void initData() {
        Datas = new ArrayList<>();
        for (int i = 1; i <= 30; i++) {
            if (i % 2 == 0) {
                Datas.add(new Item(R.drawable.tab_publish_normal,"我 get 新技能 " + i,0));
            }else {
                Datas.add(new Item(R.drawable.tab_publish_normal,"你 get 新技能 " + i,1));
            }
        }

        mMutipleAdaper = new MutipleAdaper(this,Datas);
        mRecyclerView.setAdapter(mMutipleAdaper);
        View headerView = LayoutInflater.from(this).inflate(R.layout.layout_header,mRecyclerView,false);
        View footerView = LayoutInflater.from(this).inflate(R.layout.layout_footer,mRecyclerView,false);
        mRecyclerView.addHeaderView(headerView);

        headerView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mRecyclerView.removeHeaderView(v);
            }
        });

        mRecyclerView.addFooterView(footerView);
        footerView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mRecyclerView.removeFooterView(v);
            }
        });

    }

    @Override
    protected void initView() {
        mRecyclerView = obtainView(R.id.recyclerView);
        mRecyclerView.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL_LIST));
        mRecyclerView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false));
    }

    @Override
    protected int getLayoutId() {
        return R.layout.activity_adapter;
    }
}

再次使用点击事件并运行

七、网格布局添加 Header、Footer

可以看到基本功能已经实现,但是这里只有 LinearLayout 的布局,我们运行 GridLayout 布局,并为其添加头部和底部。只需要改写布局管理器就好

 mRecyclerView.addItemDecoration(new DividerGridItemDecoration(this));
 mRecyclerView.setLayoutManager(new GridLayoutManager(this,2));

可以看到是这种效果,并不是独占一行,为解决此问题,要在原布局添加一个方法判断是否是 GridLayoutManager,如果是,对其头部和底部显示做处理。

 public class WrapRecyclerAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
     /**
     * 解决GridLayoutManager添加头部和底部不占用一行的问题
     * @param recycler
     */
    public void adjustSpanSize(RecyclerView recycler) {
        if (recycler.getLayoutManager() instanceof GridLayoutManager) {
            final GridLayoutManager layoutManager = (GridLayoutManager) recycler.getLayoutManager();
            layoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
                @Override
                public int getSpanSize(int position) {
                    boolean isHeaderOrFooter = isHeaderPosition(position) || isFooterPosition(position);
                    return isHeaderOrFooter ? layoutManager.getSpanCount() : 1;
                }
            });
        }
    }
}

此方法要在封装的RecyclerView中调用

public class WrapRecyclerView extends RecyclerView {
    @Override
    public void setAdapter(Adapter adapter) {
        //解决GridLayout添加头部和底部也要占据一行
        mWrapRecyclerAdapter.adjustSpanSize(this);
    }
}

再次运行

总结

对于 RecyclerView 的 Adapter 的封装。

单条目的封装步骤

1、 ViewHolder 的封装,继承 RecyclerView.ViewHolder,实现其方法和构造函数
2、封装 ViewHolder 的 getView() 方法,避免 findViewById 及类型转换,提供设置控件和监听的一系列方法
3、Adapter 的封装,继承 RecyclerView.Adapter 类,将封装的 ViewHolder 作为范型传入,将数据源作为范型传入。如:BaseAdapter<T> extends RecyclerView.Adapter<BaseViewHolder>。实现其构造方法和抽象方法。
4、设置数据源,上下文对象,布局 ID,抽象绑定 ViewHolder 具体实现,将 ViewHolder 和数据源作为参数传递出去,让用户对数据进行相应处理。

多条目的封装

1、在单条目基础上通过构造函数传递一个提供布局的接口,通过 ViewType 类型判断到底使用什么布局。
2、在 getItemViewType() 方法中判断布局类型,在 onCreateViewHolder() 方法中判断是否需要多布局,根据用户需要实现不同的构造函数

头部和底部添加

1、查看 ListView 的 addHeaderView() 及其相关源码,了解添加头部和底部的具体思路,根据其封装进行编写我们自己的方法。
2、头部和底部考虑使用容器存储,且需要进行标识,采用 android 为我们提供的 SparseArray 进行存储,范型为 View 类型。
3、getItemViewType() 方法根据 position 进行判断是头部、底部还是条目Adapter。
3、onCreateViewHolder() 方法是构造 ViewHolder 的,此处应该有三类 ViewHolder,头部,底部,条目 Adapter,根据 ViewType 来进行判断是什么类型的 ViewHolder,并相应的进行构造。
4、onBindViewHolder() 方法是绑定条目数据的,头部和底部是不需要绑定数据的,因此需要将其排除在外,在对条目 Adapter 进行绑定数据。
5、最后添加 addHeaderView()、addFooterView()、removeHeaderView()、removeFooterView() 等方法,封装算大致完毕。
6、对 RecyclerView 进行重写,其实就是对 RecyclerView 进行一层包裹,将封装的可添加头部和底部的 Adapter 声明,并调用其方法就行了,形成和 ListView 一样可以直接添加头部和底部的自定义控件。
7、要实现真正的点击同步,需要使用观察者模式,实现 Adapter 操作通知同步。
8、对于 GridView 添加头部不能独占一行进行相应处理。

140套Android优秀开源项目源码,领取地址:http://mp.weixin.qq.com/s/afPGHqfdiApALZqHsXbw-A

对于 RecyclerView 的封装讲到此,有不足的地方欢迎指正。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,830评论 25 707
  • 你以为你以下将看到的内容因为打开方式不对所以乱码,其实真实的情况是这样子的。这不是一篇能为你努力学习和工作打鸡血的...
    pain365日阅读 1,203评论 0 6
  • 二十多年来,我发现亘古不变的爱好就是花钱买东西。以前没有网络的时候,最爱逛超市逛街,现在各种途径各种网络,想买啥下...
    夏景暄sunny阅读 146评论 0 0
  • 今日感冒,头晕脑胀,看什么都不得劲。就写写最近的感受吧!以前说过我是个三分钟热度者,自己感觉,要么这件事就悄...
    秋水长天碧阅读 60评论 0 0
  • (二)紧急会议 周五下午,离下班还有两个小时,赵经理在办公室隔着玻璃敲了敲,示意组装车间主管夏超波到他办公室来一趟...
    乔预阅读 813评论 0 0