RecyclerView实现addHeadView的三种方法原理说明和利弊分析(footHead同理)

介绍

上一篇博客我分析了ListView的源码看Google是怎么样实现addHeadView的,源码的思路是对绑定在ListView的Adapter做转换,在我们调用addHeadView的时候把已经写好的BaseAdapter转换成HeaderViewListAdapter这一组件,在代码内部调用BaseAdapter.getView方法。这样写的好处是解耦和不影响我们原有代码的前提下做好转换。这是最好的解决方案。这几天我从网络上看到很多人对RecyclerView添加HeadView的理解。整理如下。

直接修改RecyclerView,Adapter

这是原文链接还有这篇原文链接感谢这两位提供的博客
他们的思路都是修改了adapter的三个主要方法

  • getItemViewType 返回视图类型
  • onCreateViewHolder 创建ViewHolder
  • onBindViewHolder 绑定ViewHolder内容

然后添加addHeadView和addFootView方法
在不同的position位置上做类型判断,返回不同类型结果。

特别说明

当使用StaggeredGridLayoutManager实现瀑布流效果时需要调用setFullSpan才能让某个位置的View占满格,下面是使用示例代码。

//能够让某个view满格的 setFullSpan 方法
ViewGroup.LayoutParams layoutParams=holder.mView.getLayoutParams();
if (layoutParams!=null &&layoutParams instanceof StaggeredGridLayoutManager.LayoutParams){
    StaggeredGridLayoutManager.LayoutParams params= (StaggeredGridLayoutManager.LayoutParams) layoutParams;
params.setFullSpan(holder.getLayoutPosition()==0);
}

这样实现比较简单。也比较能够理解。但缺点是耦合的太高,比较影响现有代码。比如我已经实现了adapter这样的话就需要对整体的adapter代码进行重构。并且这个adapter的作用也被局限在是一个专门处理addHeadView的RecyclerView,Adapter。这里有个GitHub地址是封装好的Adapter

总结:耦合度太高

嵌套RecyclerView

  • 就是给RecyclerView嵌套上可滑动的父视图,然后代码控制Head和Foot的显示。
  • 我简单的尝试一下给RecyclerView嵌套ScrollingView,代码运行效率奇低,只要滑动就会不停的调用RecyclerView,Adapter的onCreateViewHolder产生无数个ViewHolder根本没有Recycler的复用机制,主要是ScrollingView影响了子视图的显示问题。
  • 当然也有大神解决了这个问题,用其他的思路实现嵌套
    –比如这个Github地址在RecyclerView上套上FrameLayout,然后添加准备好的HeadView视图,监听滑动。合适的同学可以传送过去看看给大神star。缺点是只实现了addHeadView。
    –还有这个用CoordinatorLayout 把 header 抽离出 RecyclerView,也算是很奇特的思路。

总结:实现复杂,可能产生滑动冲突,导致问题更加复杂

重点介绍

上面总结怎么多,重点终于来了。前面提到Google是用一个内部HeaderViewListAdapter替换我们的adapter,实现addHeadView并且代码解耦不影响现有代码。本来想自己写的,后来在Github上看到已经有位大神实现了这个思路的解决方案。哪就不重复造轮子了。
隆重介绍—>GitHub地址
下面直接上源码,—>源码在这里。

关键代码

/**
     * 设置adapter 得到绑定的adapter 赋值给内部变量adapter
     * @param adapter
     */
    public void setAdapter(RecyclerView.Adapter adapter) {

        if (adapter != null) {
            if (!(adapter instanceof RecyclerView.Adapter))
                throw new RuntimeException("your adapter must be a RecyclerView.Adapter");
        }

        if (mInnerAdapter != null) {
            notifyItemRangeRemoved(getHeaderViewsCount(), mInnerAdapter.getItemCount());
            mInnerAdapter.unregisterAdapterDataObserver(mDataObserver);
        }

        this.mInnerAdapter = adapter;
        mInnerAdapter.registerAdapterDataObserver(mDataObserver);
        notifyItemRangeInserted(getHeaderViewsCount(), mInnerAdapter.getItemCount());
    }
/**
     * 根据 viewType 和 headerViewsCountCount 的数量 
     * 决定创建的 ViewHolder是 使用List mHeaderViews 还是内部adapter的 onCreateViewHolder
     * @param parent
     * @param viewType
     * @return
     */
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        int headerViewsCountCount = getHeaderViewsCount();
        if (viewType < TYPE_HEADER_VIEW + headerViewsCountCount) {
            return new ViewHolder(mHeaderViews.get(viewType - TYPE_HEADER_VIEW));
        } else if (viewType >= TYPE_FOOTER_VIEW && viewType < Integer.MAX_VALUE / 2) {
            return new ViewHolder(mFooterViews.get(viewType - TYPE_FOOTER_VIEW));
        } else {
            return mInnerAdapter.onCreateViewHolder(parent, viewType - Integer.MAX_VALUE / 2);
        }
    }


/**
     * head和foot的视图不复用 不需要特别的 onBindViewHolder
     * 只是在 使用StaggeredGridLayoutManager 瀑布流时候 让head和foot 视图占据满格 setFullSpan(true)
     * @param holder
     * @param position
     */
    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        int headerViewsCountCount = getHeaderViewsCount();
        if (position >= headerViewsCountCount && position < headerViewsCountCount + mInnerAdapter.getItemCount()) {
            mInnerAdapter.onBindViewHolder(holder, position - headerViewsCountCount);
        } else {
            ViewGroup.LayoutParams layoutParams = holder.itemView.getLayoutParams();
            if(layoutParams instanceof StaggeredGridLayoutManager.LayoutParams) {
                ((StaggeredGridLayoutManager.LayoutParams) layoutParams).setFullSpan(true);
            }
        }
    }

代码太多,只说明关键部分。实现的思路我在 上一篇博客 有相同的分析的源码的思路,看不懂的点击链接看博客。
我做了部分修改 ,符合我的项目使用,主要就是添加了部分功能的调用,这样我adapter才能得到方法调用。否则我的很多adapter方法无效。

修改部分代码


    @Override
    public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) {
        super.onViewAttachedToWindow(holder);
        //添加代码 需要调用内部adapter 才能收到通知
        mInnerAdapter.onViewAttachedToWindow(holder);

    }

    @Override
    public void onViewRecycled(RecyclerView.ViewHolder holder) {
        super.onViewRecycled(holder);
        //添加代码 需要调用内部adapter 才能收到通知
        mInnerAdapter.onViewRecycled(holder);
    }

使用说明

看完上面代码,怎么使用你心里也应该有底了,就是和系统几乎一样。来自GitHub

mHeaderAndFooterRecyclerViewAdapter = new HeaderAndFooterRecyclerViewAdapter(mDataAdapter);
        mRecyclerView.setAdapter(mHeaderAndFooterRecyclerViewAdapter);

        mRecyclerView.setLayoutManager(new LinearLayoutManager(this));

        //add a HeaderView
        RecyclerViewUtils.setHeaderView(mRecyclerView, new SampleHeader(this));

        //add a FooterView
        RecyclerViewUtils.setFooterView(mRecyclerView, new SampleFooter(this));

总结:这就是我要的方式,代码解耦,不影响现有代码结构。

我的使用心得

当时看到代码很激动,但是直接使用我的项目App会启动崩溃。抛出ClassCastException类型转换异常。

分析

这是因为解耦的关键,我的项目Adapter和HeaderAndFooterRecyclerViewAdapter没有直接继承关键,都是
extends RecyclerView.Adapter
继承自系统,算是兄弟类关系。这在ListView上就是这样没有问题,但是RecyclerView添加了ViewHolder,直接调用方法通知使用ViewHolder类会类型转换异常。
道理是这样的:

继承中,子类可以自动转型为父类,但是父类强制转换为子类时只有当引用类型真正的身份为子类时才会强制转换成功,否则失败

解决办法

这是我原来的Adapter类结构

public class RecyclerCardAdapter extends RecyclerView.Adapter<RecyclerCardAdapter.ViewHolder>

修改后

public class RecyclerHeadCardAdapter extends RecyclerView.Adapter

相应的类的方法也得修改

@Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 
        ViewHolderGeneral holder = null;//ViewHolder的子类

        View view = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.cardview_item_image, parent, false);
        holder = new ViewHolderGeneral(view);//使用子类初始化ViewHolder 
        //子类可以自动转型为父类
        return holder;
    }
@Override
    public void onBindViewHolder(final RecyclerView.ViewHolder holder, int position) {
         ViewHolder viewHolder = (ViewHolder) holder;
         //强制转化,父类转子类
         //当父类的引用类型真正的身份为子类时才会强制转换成功
         //因为在onCreateViewHolder中是用子类初始化的父类 所以能成功
        onBindData(viewHolder, bean); //绑定操作    
    }
@Override
    public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) {
        super.onViewAttachedToWindow(holder);

       //这是添加headView后 需要修正的position位置
        mAdapterPosition = RecyclerViewUtils.getAdapterPosition(mRecyclerView, holder);
    }

总结

  1. 这篇博客是我对RecyclerView的理解和使用,addHeadView这个方法我从ListView上找思路,在GitHub上看到一模一样的实现。感谢Cundong大神的无私贡献。
  2. 这个项目我花了我很多心思,希望能通过这项目提高我的代码水平。快毕业了希望能够找到一份好的Android开发工作。

你可能感兴趣的:(项目笔记,源码分析,开源项目)