上一篇博客我分析了ListView的源码看Google是怎么样实现addHeadView的,源码的思路是对绑定在ListView的Adapter做转换,在我们调用addHeadView的时候把已经写好的BaseAdapter转换成HeaderViewListAdapter
这一组件,在代码内部调用BaseAdapter.getView方法。这样写的好处是解耦和不影响我们原有代码的前提下做好转换。这是最好的解决方案。这几天我从网络上看到很多人对RecyclerView添加HeadView的理解。整理如下。
这是原文链接还有这篇原文链接感谢这两位提供的博客
他们的思路都是修改了adapter的三个主要方法
然后添加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
嵌套ScrollingView
,代码运行效率奇低,只要滑动就会不停的调用RecyclerView,Adapter的onCreateViewHolder产生无数个ViewHolder根本没有Recycler的复用机制,主要是ScrollingView影响了子视图的显示问题。总结:实现复杂,可能产生滑动冲突,导致问题更加复杂
上面总结怎么多,重点终于来了。前面提到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
没有直接继承关键,都是
继承自系统,算是兄弟类关系。这在ListView上就是这样没有问题,但是RecyclerView添加了ViewHolder,直接调用方法通知使用ViewHolder类会类型转换异常。
extends RecyclerView.Adapter
道理是这样的:
继承中,子类可以自动转型为父类,但是父类强制转换为子类时只有当引用类型真正的身份为子类时才会强制转换成功,否则失败
这是我原来的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);
}