热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

欢乐的票圈重构之旅——RecyclerView的头尾布局增加

项目重构的Git地址:https:github.comrazerdpFriendCircletreemain-dev项目同步更新的文集:http:www.jianshu.comno

项目重构的Git地址:
https://github.com/razerdp/FriendCircle/tree/main-dev
项目同步更新的文集:
http://www.jianshu.com/notebooks/3224048/latest

上集:欢乐的票圈重构之旅——RecyclerView的上下拉以及logo的联动

下集:欢乐的票圈重构——九宫格控件(上)

上集介绍

上集没啥好说的。。。。END(就是这么傲娇,别打我!!!怕疼)

本集介绍

因为要跟以前的文章做差异,所以重复的内容咱们就不说了,比如什么头控件乱七八糟的,感兴趣的可以去看文集里面以前的内容。

这一次我们要做的是一个老生常谈的问题了,就是RecyclerVew的headerView和footerView。。。

也许你会说,这神马啊,adapter里面做不同的itemType不就好了咩。。。

说起来好像也对哦,不过我们这次做不同的。

大家想想哈,以前我们用ListView的时候,是不是这样玩:
listView=findview()
listView.addHeaderView()
listView.setAdapter()

而如果换成Rv,我们对着adapter是不是要这么玩:

class xxx extend RecyclerView.Adapter {
//处理这个处理那个,还得处理位置信息。。。。
}

对于懒到极点的我来说,就是下面这张图:

《欢乐的票圈重构之旅——RecyclerView的头尾布局增加》

既然如此,习惯偷懒的我还是做一个跟ListView一样玩法的东西吧。。。

ListView的headerView

首先我们看看ListView是怎么玩的:

《欢乐的票圈重构之旅——RecyclerView的头尾布局增加》 addHeaderView

从addHeaderView里面,我们看到的是new了一个FixedViewInfo,然后把View啊,data啊,什么奇奇怪怪的啊,都去喂饱它,接着通过观察者去刷新数据。

不过我们在这里留意到一个东西,就是adapter是做了一层包裹的,这个包裹是啥,我们接下去看看

《欢乐的票圈重构之旅——RecyclerView的头尾布局增加》 HeaderViewListAdapter

这个类实现了WrapperListAdapter,而这个接口又继承了ListAdapter,所以可以被直接通过listview.setadapter给塞进去。

那么既然这是一个adapter,里面肯定做了些什么,我们继续看看最主要的几个方法:

  • getCount();
  • getItemViewType();
  • getView();
  • getItemViewCount();

从这几个方法里面,不难看出,,,喵了个咪的,其实还是基于viewType来玩的。。。

首先看看getCount()代码:

public int getCount() {
if (mAdapter != null) {
return getFootersCount() + getHeadersCount() + mAdapter.getCount();
} else {
return getFootersCount() + getHeadersCount();
}
}

很简单,就是被包裹的adapter的count加上头尾view的数量

然后看看getItemViewType():

public int getItemViewType(int position) {
int numHeaders = getHeadersCount();
if (mAdapter != null && position >= numHeaders) {
int adjPosition = position - numHeaders;
int adapterCount = mAdapter.getCount();
if (adjPosition return mAdapter.getItemViewType(adjPosition);
}
}
return AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER;
}

这里做了一些位置的处理:

  • 首先获取headerview的数量
  • 然后判断位置是否在header和footer之间
  • 根据上面两个条件,返回原adapter的viewType或者专用于headerview的viewType

【ps:】在ListView中,headerView和footerView用的都是同一个viewType

最后看看getView():

嗯。。。。其实跟上面的处理差不多的,通过位置判断应该塞入header还是footer还是原来的adapter的getView(),咱们就略过吧- –

看完之后,大致了解到ListView的headerView玩法,其根本还是viewType。

不过在了解的过程中,发现了一个好玩的事情:我们都知道,ListView中的getViewTypeCount()的值是必须要比getViewType()大的,而对于headerView的viewType,官方赋值为-2,是一个负数,然后getViewTypeCount()在没有adapter时给的是1,这恰好满足了所有条件。。。

然后当然要我们可耻的学过去了←_←

RecyclerView的adapter处理

看完了ListView之后,我们对于RecyclerView的处理方法也就有了一个大概的做法了——
【把官方的弄进来不就可以了吗哈哈哈哈哈哈哈】

动手之前,我们要确定一下这两者最大的差异,我认为最大的差异在于:
ListView的getView()里面有position信息

RecyclerView的onCreateViewHolder()是只有viewType的

那么意味着我们的viewType必须要包含着位置信息,才能让我们正确的把header或者footer添加进去。

所以我的做法很简单粗暴:

  • 对于header,其viewType从-2开始(-1是有官方定义的“INVALID_TYPE”)
  • 对于footer,其viewType从-99开始
  • 每增加一个header或者footer,则其viewType减去1
    • 对于header,其最多支持-2~-98共97个headerView的增加,所以假如header满了(谁会那么丧心病狂加那么多!!!!!!),则新加进来的会替换掉最后的一个
    • 对于footer,随便加了。。。。

以上的解决方法虽有点小瑕疵(headerview的数量限制),但考虑到一般人不会那么伤心病狂所以,理论上来说,还算行得通吧。。。。

接下来进入正片

《欢乐的票圈重构之旅——RecyclerView的头尾布局增加》

首先我们构造出一个FixedViewInfo类

private class FixedViewInfo {
/**
* The view to add to the list
*/
public View view;
/**
* 因为onCreateViewHolder不包含位置信息,所以itemViewType需要包含位置信息
*/
public int itemViewType;
}

名字我们都跟官方的对齐。。。。嗯,那啥,遵守国际规则嘛←_←

然后构造出两个LinkList(头和尾对位置有严格要求)

/**
* 以下为recyclerview 的headeradapter实现方案
*


* 以Listview的headerView和footerView为模板做出的recyclerview的header和footer
*/
private LinkedList mHeaderViewInfos = new LinkedList<>();
private LinkedList mFooterViewInfos = new LinkedList<>();

然后加入我们的addHeaderView和addFooterView方法方法

public void addHeaderView(View headerView) {
final FixedViewInfo info = new FixedViewInfo();
if (mHeaderViewInfos.size() == Math.abs(ITEM_VIEW_TYPE_FOOTER_START - ITEM_VIEW_TYPE_HEADER_START)) {
//FIXME:优化
//数量满了则新的替换掉最后一个
mHeaderViewInfos.removeLast();
}
info.view = headerView;
info.itemViewType = ITEM_VIEW_TYPE_HEADER_START - mHeaderViewInfos.size();
mHeaderViewInfos.add(info);
}
public void addFooterView(View footerView) {
final FixedViewInfo info = new FixedViewInfo();
info.view = footerView;
info.itemViewType = ITEM_VIEW_TYPE_FOOTER_START - mFooterViewInfos.size();
mFooterViewInfos.add(info);
}

其中ITEM_VIEW_TYPE_FOOTER_START=-2,ITEM_VIEW_TYPE_FOOTER_START=-99

接下来就是我们最主要的包裹的InnerWrapperHeaderViewRecyclerAdapter实现:

private final class InnerWrapperHeaderViewRecyclerAdapter extends RecyclerView.Adapter {
private final RecyclerView.Adapter mAdapter;
private RecyclerView.AdapterDataObserver mDataObserver = 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 onItemRangeRemoved(int positionStart, int itemCount) {
notifyItemRangeRemoved(positionStart + getHeadersCount(), itemCount);
}
@Override
public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
int headerViewsCountCount = getHeadersCount();
notifyItemRangeChanged(fromPosition + headerViewsCountCount, toPosition + headerViewsCountCount + itemCount);
}
};
public InnerWrapperHeaderViewRecyclerAdapter(RecyclerView.Adapter mAdapter) {
this.mAdapter = mAdapter;
this.mAdapter.registerAdapterDataObserver(mDataObserver);
}
public int getHeadersCount() {
return mHeaderViewInfos.size();
}
public int getFootersCount() {
return mFooterViewInfos.size();
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

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

}
@Override
public int getItemCount() {

}
@Override
public int getItemViewType(int position) {

}
private boolean onCreateHeaderViewHolder(int viewType) {
//是否创建headerview
return mHeaderViewInfos.size() > 0 && viewType <= ITEM_VIEW_TYPE_HEADER_START && viewType > ITEM_VIEW_TYPE_FOOTER_START;
}
private boolean onCreateFooterViewHolder(int viewType) {
//是否创建footer
return mFooterViewInfos.size() > 0 && viewType <= ITEM_VIEW_TYPE_FOOTER_START;
}
private int getHeaderPosition(int viewType) {
//根据viewType得到header的位置
return Math.abs(viewType) - Math.abs(ITEM_VIEW_TYPE_HEADER_START);
}
private int getFooterPosition(int viewType) {
//根据viewType得到footer的位置
return Math.abs(viewType) - Math.abs(ITEM_VIEW_TYPE_FOOTER_START);
}
private final class HeaderOrFooterViewHolder extends RecyclerView.ViewHolder {
public HeaderOrFooterViewHolder(View itemView) {
super(itemView);
}
}
}

上述结构中,我们着重于最主要的三个,就是我们平时写adapter需要重写的那几个。。。

在那之前,我们可以看看其他的方法,最主要是看位置获取的方法。

在前面我们说过,viewType需要包含位置信息,而我们通过viewType得到对应的header在list中的位置,只需要通过viewType减去起始值得到偏移量就好了。。。

接下来,我们先补全一下getItemViewType()方法

@Override
public int getItemViewType(int position) {
int numHeaders = getHeadersCount();
if (mAdapter == null) return -1;
//header之后的view,返回adapter的itemType
int adjustPos = position - numHeaders;
int adapterItemCount = mAdapter.getItemCount();
if (position >= numHeaders) {
if (adjustPos //如果是adapter返回的范围内,则取adapter的itemviewtype
return mAdapter.getItemViewType(adjustPos);
}
} else if (position return mHeaderViewInfos.get(position).itemViewType;
}
return mFooterViewInfos.get(position - adapterItemCount - numHeaders).itemViewType;
}

大致上都是跟ListView的一样的,只不过因为对于RecyclerView来说,我们没有办法获取到位置,所以我们的header和footer不可以用同一个viewType。

getItemCount()方法忽略。。。

接下来,我们需要针对header和footer的LayoutParam进行修复

在实际应用中,我们一般都是inflate一个view或者new一个view来进行addHeader或者addFooter,但是有一个问题就因此产生了,就是我们的header或者footer的margin等等全部失效,而且即使我们设置为match_parent,有时候也会被强行设置成wrap_content。

造成这个原因其实很简单,就是因为我们初始化这个view的时候并没有LayoutParams,因此RecyclerView会自行调用generateDefaultLayoutParams(实际上是调用LayoutManager的方法),一般情况下,default的都是wrap_content的,所以就被强制设置了。

解决方法也很简单,我们需要针对不同情况进行创建就好了:

private void checkAndSetRecyclerViewLayoutParams(View child) {
if (child == null) return;
ViewGroup.LayoutParams p = child.getLayoutParams();
RecyclerView.LayoutParams params = null;
if (p == null) {
params = new RecyclerView.LayoutParams(new MarginLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT)));
} else {
if (!(p instanceof RecyclerView.LayoutParams)) {
params = recyclerView.getLayoutManager().generateLayoutParams(p);
}
}
child.setLayoutParams(params);
}

然后,我们看看onCreateViewHolder()方法

@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
//header
if (onCreateHeaderViewHolder(viewType)) {
final int headerPosition = getHeaderPosition(viewType);
View headerView = mHeaderViewInfos.get(headerPosition).view;
checkAndSetRecyclerViewLayoutParams(headerView);
return new HeaderOrFooterViewHolder(headerView);
} else if (onCreateFooterViewHolder(viewType)) {
//footer
final int footerPosition = getFooterPosition(viewType);
View footerView = mFooterViewInfos.get(footerPosition).view;
checkAndSetRecyclerViewLayoutParams(footerView);
return new HeaderOrFooterViewHolder(footerView);
}
return mAdapter.onCreateViewHolder(parent, viewType);
}

在这里我们根据viewType分辨出应该创建header还是footer,然后的到对应的位置以及View,创建一个普通的viewHolder返回即可。。。

这些小case对于大家来说肯定是so easy的-V-

最后,当然是onBindViewHolder方法

@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
int numHeaders = getHeadersCount();
int adapterCount = mAdapter.getItemCount();
if (position return;
} else if (position > (numHeaders + adapterCount)) {
return;
} else {
int adjustPosition = position - numHeaders;
if (adjustPosition mAdapter.onBindViewHolder(holder, adjustPosition );
}
}
}

对于header和footer,我们不需要对其处理,因为这些view一般都是外部传入,那么应该交回给外部处理。

值得注意的是,当需要用到原来的adapter的onBindView时,我们需要对位置做一下处理,否则会引发位置信息不对的问题。

最后,在外面的使用则跟listview的玩法一样:

《欢乐的票圈重构之旅——RecyclerView的头尾布局增加》 addheader

效果图:

《欢乐的票圈重构之旅——RecyclerView的头尾布局增加》 header
《欢乐的票圈重构之旅——RecyclerView的头尾布局增加》 footer

再一次看到亲爱的穹妹-V- 有种莫名的熟悉感呐~

本篇结束,下一次。。。。。

我也不知道写什么好,遇到跟以前不同的地方我再写好了-V-

感谢您的支持与谅解


推荐阅读
  • CSS3选择器的使用方法详解,提高Web开发效率和精准度
    本文详细介绍了CSS3新增的选择器方法,包括属性选择器的使用。通过CSS3选择器,可以提高Web开发的效率和精准度,使得查找元素更加方便和快捷。同时,本文还对属性选择器的各种用法进行了详细解释,并给出了相应的代码示例。通过学习本文,读者可以更好地掌握CSS3选择器的使用方法,提升自己的Web开发能力。 ... [详细]
  • IjustinheritedsomewebpageswhichusesMooTools.IneverusedMooTools.NowIneedtoaddsomef ... [详细]
  • JDK源码学习之HashTable(附带面试题)的学习笔记
    本文介绍了JDK源码学习之HashTable(附带面试题)的学习笔记,包括HashTable的定义、数据类型、与HashMap的关系和区别。文章提供了干货,并附带了其他相关主题的学习笔记。 ... [详细]
  • 微软头条实习生分享深度学习自学指南
    本文介绍了一位微软头条实习生自学深度学习的经验分享,包括学习资源推荐、重要基础知识的学习要点等。作者强调了学好Python和数学基础的重要性,并提供了一些建议。 ... [详细]
  • 如何自行分析定位SAP BSP错误
    The“BSPtag”Imentionedintheblogtitlemeansforexamplethetagchtmlb:configCelleratorbelowwhichi ... [详细]
  • Spring源码解密之默认标签的解析方式分析
    本文分析了Spring源码解密中默认标签的解析方式。通过对命名空间的判断,区分默认命名空间和自定义命名空间,并采用不同的解析方式。其中,bean标签的解析最为复杂和重要。 ... [详细]
  • android listview OnItemClickListener失效原因
    最近在做listview时发现OnItemClickListener失效的问题,经过查找发现是因为button的原因。不仅listitem中存在button会影响OnItemClickListener事件的失效,还会导致单击后listview每个item的背景改变,使得item中的所有有关焦点的事件都失效。本文给出了一个范例来说明这种情况,并提供了解决方法。 ... [详细]
  • 本文讨论了如何使用IF函数从基于有限输入列表的有限输出列表中获取输出,并提出了是否有更快/更有效的执行代码的方法。作者希望了解是否有办法缩短代码,并从自我开发的角度来看是否有更好的方法。提供的代码可以按原样工作,但作者想知道是否有更好的方法来执行这样的任务。 ... [详细]
  • 本文介绍了Python爬虫技术基础篇面向对象高级编程(中)中的多重继承概念。通过继承,子类可以扩展父类的功能。文章以动物类层次的设计为例,讨论了按照不同分类方式设计类层次的复杂性和多重继承的优势。最后给出了哺乳动物和鸟类的设计示例,以及能跑、能飞、宠物类和非宠物类的增加对类数量的影响。 ... [详细]
  • Android系统源码分析Zygote和SystemServer启动过程详解
    本文详细解析了Android系统源码中Zygote和SystemServer的启动过程。首先介绍了系统framework层启动的内容,帮助理解四大组件的启动和管理过程。接着介绍了AMS、PMS等系统服务的作用和调用方式。然后详细分析了Zygote的启动过程,解释了Zygote在Android启动过程中的决定作用。最后通过时序图展示了整个过程。 ... [详细]
  • Week04面向对象设计与继承学习总结及作业要求
    本文总结了Week04面向对象设计与继承的重要知识点,包括对象、类、封装性、静态属性、静态方法、重载、继承和多态等。同时,还介绍了私有构造函数在类外部无法被调用、static不能访问非静态属性以及该类实例可以共享类里的static属性等内容。此外,还提到了作业要求,包括讲述一个在网上商城购物或在班级博客进行学习的故事,并使用Markdown的加粗标记和语句块标记标注关键名词和动词。最后,还提到了参考资料中关于UML类图如何绘制的范例。 ... [详细]
  • 学习SLAM的女生,很酷
    本文介绍了学习SLAM的女生的故事,她们选择SLAM作为研究方向,面临各种学习挑战,但坚持不懈,最终获得成功。文章鼓励未来想走科研道路的女生勇敢追求自己的梦想,同时提到了一位正在英国攻读硕士学位的女生与SLAM结缘的经历。 ... [详细]
  • 生成式对抗网络模型综述摘要生成式对抗网络模型(GAN)是基于深度学习的一种强大的生成模型,可以应用于计算机视觉、自然语言处理、半监督学习等重要领域。生成式对抗网络 ... [详细]
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • 本文讨论了在Windows 8上安装gvim中插件时出现的错误加载问题。作者将EasyMotion插件放在了正确的位置,但加载时却出现了错误。作者提供了下载链接和之前放置插件的位置,并列出了出现的错误信息。 ... [详细]
author-avatar
sensor
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有