博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
欢乐的票圈重构之旅——RecyclerView的头尾布局增加
阅读量:6914 次
发布时间:2019-06-27

本文共 11340 字,大约阅读时间需要 37 分钟。

项目重构的Git地址:https://github.com/razerdp/FriendCircle

上集:

下集:

上集介绍

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

本集介绍

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

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

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

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

大家想想哈,以前我们用ListView的时候,是不是这样玩:

  • listView=findview()
  • listView.addHeaderView()
  • listView.setAdapter()

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

class xxx extend RecyclerView.Adapter {

//处理这个处理那个,还得处理位置信息。。。。

}

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

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


ListView的headerView

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

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

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

这个类实现了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 < adapterCount) {                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的数量限制),但考虑到一般人不会那么伤心病狂所以,理论上来说,还算行得通吧。。。。

接下来进入正片


首先我们构造出一个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 < adapterItemCount) {                    //如果是adapter返回的范围内,则取adapter的itemviewtype                    return mAdapter.getItemViewType(adjustPos);                }            } else if (position < numHeaders) {                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 < numHeaders) {                return;            } else if (position > (numHeaders + adapterCount)) {                return;            } else {                int adjustPosition = position - numHeaders;                if (adjustPosition < adapterCount) {                    mAdapter.onBindViewHolder(holder, adjustPosition );                }            }        }复制代码

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

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

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

效果图:

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

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

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

感谢您的支持与谅解

转载地址:http://txicl.baihongyu.com/

你可能感兴趣的文章
Tomcat部署servlet小应用无法找到相应的servlet类的问题可能原因
查看>>
Coding and Paper Letter(七)
查看>>
51CTO篮球俱乐部精彩集锦(5月9日)
查看>>
java实现正则表达式到NFA的转换
查看>>
python-54: 验证码登陆尝试
查看>>
Fedora 11 安装指南-06
查看>>
C#中的异步和多线程
查看>>
归并非递归排序
查看>>
我的友情链接
查看>>
HeadFirst设计模式(五) - 单例模式
查看>>
executeQuery、executeUpdate 和 execute返回值
查看>>
Java利用Tomcat作为服务器与Android的Http通信
查看>>
HDP-2.6.5 & ambari-2.6.2 安装配置
查看>>
一天一点T-SQL:关于分区表(二)
查看>>
如何找到适合自己的学习方法
查看>>
Use crash to show the code line of a backtrace entry like "ocfs2_truncate_file+0x127/0x6c0"
查看>>
nagios 报警
查看>>
java利用Collection类排序
查看>>
<民工哥linux>公众号2017年全年文章汇总
查看>>
8月15日 Google收购MOTOROLA移动 125亿美元成交
查看>>