【Android】让HeaderView也参与回收机制,自我感觉是优雅的为 RecyclerView 添加 HeaderView (FooterView)的解决方案

本文站在巨人的肩膀上 自我感觉又进了一步而成。

基于翔神的大作基础之上写的一个为RecyclerView添加HeaderView FooterView 的另一种解决方案, 

翔神链接文首镇楼:http://blog.csdn.net/lmj623565791/article/details/51854533 

上次翔神发表这篇文章时,我就提了个问题:说headerView和FooterView都是强引用在Adapter中的,这样即使他所属的ViewHolder被回收复用(后实践发现,就算设置了HeaderView的ViewHolder不缓存,但是始终有一个HeaderView的ViewHolder在被强引用),但是View本身的实例还是在被强引用,内存空间也无法释放的。 这样做虽然速度没任何问题,(甚至还有提升,但是HeaderView过大内存空间就会吃紧了吧) 因为我司项目大多HeaderView又臭又长,所以我想了好久 改写了一下,换了种思路,给RecyclerView提供数据和布局,并且可以让开发者动态配置headerView在RecyclerViewPool里的缓存数,将UI的创建 和 数据的绑定分开来做,都交由Adapter维护。

先给大家看一下我司app的某个界面设计稿:这种页面在我们的APP里有10+个


是的你没看错,底部还是个不断加载更多的列表~,对于这种又臭又长的HeaderView,我一想到它在内存里不能释放,我就浑身难受。

墙裂建议大家先阅读翔神文章后 再立刻阅读此文,威力翻倍。这样对本文使用到的一些吊炸天的东西就不会陌生了,例如通用的CommonAdapter和ViewHolder。

敲黑板,如果只是伸手党,建议直接看 【2 使用方法】,并直接到文末下载链接里的工程,拷贝recyclerview包下的几个文件即可使用。

工程里已经参考解决,HeaderView适配GridLayoutManager 和StaggeredGridLayoutManager。

========================================================================

【1 引言】

众所周知,RecyclerView已经是主流,ListView已经成为过去式,而两者之间有些许的不同,其中比较重要的一点就是ListView自带addHeaderView,addFooterView方法,而RecyclerView并没有提供。So,我们开发者要自己想办法实现这个功能。

市面上大多为RecyclerView添加HeaderView的方案,都是在使用RecyclerView的类中(Activity Fragment)里构建一个View,并绑定好数据,然后通过XXXAdapter提供的addHeaderView方法,将这个View set进Adapter里。

Adapter内部使用ArrayList、或者翔神使用的是SparseArray存储这个View,并为HeaderView FooterView分配不同的itemViewType,然后Adapter在onCreateViewHolder和onBindViewHolder方法里,根据ViewType的不同来判断这是HeaderView 还是普通item。

这种方法目前为止我只发现一个弊端(也是本文改进的地方),就是这个HeaderView由于在Adapter里是被ArrayList、SparseArray强引用的,就算其所属的RecyclerView.ViewHolder在RecyclerViewPool的缓存池里 被设置缓存数量为0,被回收了(后来经过实测,发现HeaderViewHolder数量多后,始终有一个ViewHolder在被引用 没有被释放,其余的被成功释放),但是这个View会因为被ArrayList等强引用着,依然停留在内存中。所以该HeaderView并没有被回收而想一想普通的item都只有数据和layoutId传递给Adapter,并没有View的实例。

一般情况下 这并没有任何问题,因为普通项目的HeaderView也不大,但是若HeaderView过于庞大,(就像我司的项目,动辄HeaderView就三+个屏幕长度,三屏之后才是普通的item),在这个页面已经往下滑了很多距离,浏览了很多内容,HeaderView早已不可见,此时按照RecyclerView的思路,这个庞大的HeaderView所属的VIewHolder应该已经进入了RecyclerViewPool的缓存池中,如果设置该种viewType的缓存数量为0,即不缓存,ok,那么RecyclerView做了它该做的事,不再缓存这个HeaderView寄身的VIewHolder了,在GC垃圾回收触发后,虽然该种type的ViewHolder被回收了,可惜上文提到,此时HeaderView被强引用住,被回收的只是其所属的那个ViewHolder,这个庞大的VIew所占的内存空间依然没有被释放。

其实我们仔细想一想,RecyclerView Adapter里是不保存View对象的,它保存的只是数据和layout,而我们也应该遵循此原则 为其添加HeaderView(FooterView)。

(题外话,和ListView相比,RecyclerView更是进一步的 将 UI的创建 和数据的绑定 分成了两步,(oncreateViewHolder,onBindViewHolder))


敲黑板,本文就参考翔神的装饰者模式,为RecyclerView 添加 HeaderView(FooterView),

并且将HeaderView的UI创建,和数据绑定强制分开,提供配置每种headerView的缓存数量的方法,令HeaderView实例在Adapter中不再被强引用,让HeaderView和普通的ItemView没有两样~。

先上预览动图:


第二张图是为了测试headerViewHolder是否真的被回收特意选用4个ImageView组成的HeaderView看效果。


========================================================================

【2 使用方法】

//HeaderView使用方法小窥: 以下为Rv添加两个HeaderView
mHeaderAdapter = new HeaderRecyclerAndFooterWrapperAdapter(mAdapter) {
    @Override
    protected void onBindHeaderHolder(ViewHolder holder, int headerPos, int layoutId, Object o) {
        switch (layoutId) {
            case R.layout.item_header_1:
                TestHeader1 header1 = (TestHeader1) o;
                holder.setText(R.id.tv, header1.getText());
                break;
            case R.layout.item_header_2:
                TestHeader2 header2 = (TestHeader2) o;
                holder.setText(R.id.tv1, header2.getTxt1());
                holder.setText(R.id.tv2, header2.getTxt2());
                break;
            default:
                break;
        }
    }
};
mHeaderAdapter.addHeaderView(R.layout.item_header_1,new TestHeader1("第一个HeaderView"));
mHeaderAdapter.addHeaderView(R.layout.item_header_2,new TestHeader2("第二个","HeaderView"));
mRv.setAdapter(mHeaderAdapter);

以上每个HeaderView在缓存池RecyclerViewPool的数量都是默认的5个,

使用如下方法:

mHeaderAdapter.addHeaderView(R.layout.item_header_4, new TestHeader4(pics),0);
将该种类型的headerView的缓存数量配置为0个。


粗略这么一看,我擦 什么辣鸡,比翔神那个真是差十万八千里,人家只要4行代码就加一个HeaderView,而且还不用实现父类Adapter的方法,你这还要switch case 看起来就一坨好麻烦的样子,走了走了。

客官留步留步,如果客官有这种想法,先冷静一下,里听我港。

这个写法猛地看起来是略复杂了一些,但是它强制的让我们将UI的创建和数据的绑定分开了,我们重写的onBindHeaderHolder()方法,就是数据的绑定过程, 试想一下,基本上每个带HeaderView的页面都有下拉刷新功能,如果你使用传统方法添加HeaderView,那么你必须要持有HeaderView的引用才能在数据刷新时改变头部数据,而且那些烦人的set方法一样是要写一遍,你可能需要将 写在Activity(Fragment)里的 创建HeaderView时的set数据方法抽成一个函数,再调用一遍。所以工作量是一点没减少的。

而且重要的是,使用这种方法,如果将缓存数量设置为0,HeaderView在移出屏幕后,触发GC事件时,是可以被回收滴。文末给实验证明。

所以我们这种做法,你的工作量也是一点没增加滴!反而还是方便滴!优雅滴!

(躲开丢过来的鸡蛋)废话不多说,用法已经看到,下面看我们是怎么实现的。 如果伸手党看到这里觉得已经够了,那么就可以去文末直接下载源码copy使用了,里面使用的几个类版权大多归翔神所有。

========================================================================

【三,实现】

直接贴出核心代码:

public abstract class HeaderRecyclerAndFooterWrapperAdapter2 extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    private class HeaderBean {
        private final int DEFAULT_HEADER_VIEW_CACHE_SIZE = 5;//默认是5 和RecyclerViewPool的默认值一样
        private int layoutId;//viewType当做layoutId
        private Object data;//该viewType(LayoutId)对应的数据
        private int cacheSize;//该种viewType的HeaderView 在RecyclerViewPool的缓存池内的缓存数量

        public HeaderBean(int layoutId, Object data, int cacheSize) {
            this.layoutId = layoutId;
            this.data = data;
            this.cacheSize = cacheSize;
        }

        public HeaderBean(int layoutId, Object data) {
            this.layoutId = layoutId;
            this.data = data;
            this.cacheSize = DEFAULT_HEADER_VIEW_CACHE_SIZE;
        }

        public int getLayoutId() {
            return layoutId;
        }

        public void setLayoutId(int layoutId) {
            this.layoutId = layoutId;
        }

        public Object getData() {
            return data;
        }

        public void setData(Object data) {
            this.data = data;
        }

        public int getCacheSize() {
            return cacheSize;
        }

        public void setCacheSize(int cacheSize) {
            this.cacheSize = cacheSize;
        }

    }

//按照add顺序存放HeaderView的bean,bean包括layoutId,数据Data,和缓存数量cacheSize。
// 在createViewHOlder里根据layoutId创建UI,在onbindViewHOlder里依据这个data渲染UI,
// 在onAttachedToRecyclerView 为每种layoutId(同时也是viewType)的headerView设置缓存数量
private ArrayList<HeaderBean> mHeaderDatas = new ArrayList<HeaderBean>();
@Override
public int getItemViewType(int position) {
    if (isHeaderViewPos(position)) {
        return mHeaderDatas.get(position).getLayoutId();
//HeaderView的layoutId就是viewType
} return super.getItemViewType(position - getHeaderViewCount());}
@Override
public 
评论 29
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值