精华内容
参与话题
问答
  • addHeaderView的一些注意点

    千次阅读 2017-01-23 20:19:05
    在ListView中有时会需要添加addHeaderView或addFootView,或者两者兼或; 首先,我来一份简单的代码TextView TextView=new TextView(this); textView.setText("Hello"); listView.addHeaderView(textView);...

    在ListView中有时会需要添加addHeaderView或addFootView,或者两者兼或;

    首先,我来一份简单的代码

    TextView TextView=new TextView(this);
    textView.setText("Hello");
    listView.addHeaderView(textView);
    自定义一个TextView,让他显示一句话"Hello",然后将它添加到Header中

    这时候添加item点击事件,在点击事件中

    PopwindowRightAdapter adapter = (PopwindowRightAdapter) parent.getAdapter();
    想要获取Adapter,结果却报错

    java.lang.ClassCastException: android.widget.HeaderViewListAdapter cannot be cast to 

    说是HeaderViewListAdapter 不能被转化为我们自定义的那个Adapter,我这里的就是PopwindowRightAdapter

    去addHeaderView中看一下源码

    public void addHeaderView(View v, Object data, boolean isSelectable) {
            final FixedViewInfo info = new FixedViewInfo();
            info.view = v;
            info.data = data;
            info.isSelectable = isSelectable;
            mHeaderViewInfos.add(info);
            mAreAllItemsSelectable &= isSelectable;
    
            // Wrap the adapter if it wasn't already wrapped.
            if (mAdapter != null) {
                if (!(mAdapter instanceof HeaderViewListAdapter)) {
                    mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, mAdapter);
                }
    
                // In the case of re-adding a header view, or adding one later on,
                // we need to notify the observer.
                if (mDataSetObserver != null) {
                    mDataSetObserver.onChanged();
                }
            }
        }
    

    adapter被转化为了HeaderViewListAdapter,就不是我们之前的那个adapter,

    看一下他的构造方法:

    public HeaderViewListAdapter(ArrayList<ListView.FixedViewInfo> headerViewInfos,
                                     ArrayList<ListView.FixedViewInfo> footerViewInfos,
                                     ListAdapter adapter) {
            mAdapter = adapter;
            mIsFilterable = adapter instanceof Filterable;
    
            if (headerViewInfos == null) {
                mHeaderViewInfos = EMPTY_INFO_LIST;
            } else {
                mHeaderViewInfos = headerViewInfos;
            }
    
            if (footerViewInfos == null) {
                mFooterViewInfos = EMPTY_INFO_LIST;
            } else {
                mFooterViewInfos = footerViewInfos;
            }
    
            mAreAllFixedViewsSelectable =
                    areAllListInfosSelectable(mHeaderViewInfos)
                    && areAllListInfosSelectable(mFooterViewInfos);
        }

    我也看不太懂,大概意思就是把header放到一个List中,footer放到一个List当中,然后和之前的数据集合到一块整合成一个新的Adapter;不过,位置是不同的,一个放头部,一个放底部

    再回到之前的那个报错问题,我在网上查了一下,有人给出如下方案:

    地址:http://blog.csdn.net/sunxingzhesunjinbiao/article/details/9952997

    ListView listView = getListView();
                    //ConversationListAdapter adapter = (ConversationListAdapter) listView.getAdapter();
        ConversationListAdapter adapter = null;
        if(listView.getAdapter() instanceof HeaderViewListAdapter) {
        HeaderViewListAdapter listAdapter=(HeaderViewListAdapter)listView.getAdapter();
    adapter = (ConversationListAdapter) listAdapter.getWrappedAdapter();
        } else {
        adapter = (ConversationListAdapter) listView.getAdapter();
        }    
    大概意思就是他们遇到的问题是在setAdapter之后才addHeaderView导致的问题
    还有一个解决方案:

    地址;http://www.xuebuyuan.com/984745.html

    cannot be cast to android.widget.HeaderViewListAdapter
    用listview设置header或footerview,通常发生该异常。
    
    如果你没有发生,那是碰巧。但是你可能不知道原因。
    
    如果在listview.setAdapter(adapter)方法之后 添加头或尾view,
    
    即addHeaderView或 addFooterView,
    
    那么在你listView.removeHearderView或removeFooterView时就会报该异常。
    
    在Listview的源码中如果设置了adapter,那么它会强转成HeaderViewListAdapter,所以就会报错。
    
    if (mAdapter != null && ((HeaderViewListAdapter) mAdapter).removeHeader(v)) {
         if (mDataSetObserver != null) {
             mDataSetObserver.onChanged();
         }
         result = true;
    }
    
    。
    
    所以你一定要在setAdapter之前调用addHeaderView或addFooterVeiw.
    都是顺序问题,导致发生了Adapter转换;之前的源码能看到

    if (mAdapter != null) {
                if (!(mAdapter instanceof HeaderViewListAdapter)) {
                    mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, mAdapter);
                }
    
                // In the case of re-adding a header view, or adding one later on,
                // we need to notify the observer.
                if (mDataSetObserver != null) {
                    mDataSetObserver.onChanged();
                }
            }
    mAdapter不为空的时候直接转换了,
    还看到一个转换的blog,地址:http://blog.sina.com.cn/s/blog_6d45d11f01014n1c.html
    private OnItemClickListener mItemClickListener = new OnItemClickListener() {
    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position,
    long id) {
    HeaderViewListAdapter ha = (HeaderViewListAdapter) parent.getAdapter();
    JobsAdapter ad = (JobsAdapter) ha.getWrappedAdapter();
    ad.toggle(position-1);
    }
    };

    HeaderViewListAdapter有个方法getWrappedAdapter,该方法能返回被包装的HeaderViewListAdapter的ListAdapter。

    说实话,这句话我没看懂,我还是不知道getWrappedAdapter是干什么用的,看了一下它的API说明

    Returns the adapter wrapped by this list adapter.

    我理解的意思就是返回当前包装的adapter,大概就是转换成当前的adapter的意思吧,猜的。。。估计差不远

    我用面的方式试了一下,除了这个toggle搞不出来,其余的差不多

    那个报错解决完之后,还有一个顺序的问题,添加了一个header,他的position就是0了,然后之前的顺移,这样就需要用到那个toggle,不过我失败了,怎么也找不到这方法,然后我就用下面的

    JBean pCarea = adapter.getItem(position-1);
    ,这样就可以依次取到之前的item的点击事件了,然后问题又来了,因为header也在,点击她的时候,她的position就是-1了,当时我想的是专门针对这个-1处理一下,做个判断处理一下,随后又感觉到不行,这只是一个header,如果有多个呢,然后又去找了一下资料,发现addView还有另一个重载方法addheadView(),对,就是最开始那个

    我最初是用的这个

     public void addHeaderView(View v) {
            addHeaderView(v, null, true);
        }
    

    后来才发现另一个重载方法的

    public void addHeaderView(View v, Object data, boolean isSelectable)
    主要在于最有一个参数,他设置header不被选中,就不会触发点击事件也就是-1也没事;

    设置多个addHeaderView的时候就可以给header添加点击事件OncliickListener,用来覆盖onItemClickListener,同时,之前的item获取item的时候,需要用position减去header的个数

    然后又有一个问题,那就是item中的子控件焦点之争;

    地址:http://blog.csdn.net/iblade/article/details/50958295

    当使用itemClickListener的时候,item里面的子控件如button会夺取焦点,这时候就有一个方法:

    setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);


    在ListView的xml中也可以设置

    地址:http://www.cnblogs.com/eyu8874521/archive/2012/10/17/2727882.html

    android:descendantFocusability
    它有三个可选值:

    beforeDescendants:viewgroup会优先其子类控件而获取到焦点

            afterDescendants:viewgroup只有当其子类控件不需要获取焦点时才获取焦点

            blocksDescendants:viewgroup会覆盖子类控件而直接获得焦点


    这样,应该是差不多了。




    展开全文
  • 【Android】让HeaderView也参与 回收 复用机制,自我感觉是优雅的为 RecyclerView 添加 HeaderView (FooterView)的解决方案 本文就参考翔神的装饰者模式,为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 RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    
        if (mHeaderDatas != null && !mHeaderDatas.isEmpty()) {//不为空,说明有headerview
            for (HeaderBean HeaderBean : mHeaderDatas) {
                if (HeaderBean.getLayoutId() == viewType) {//匹配上了说明是headerView
                    return ViewHolderHeader3.get(parent.getContext(), null, parent, viewType, -1);
                }
            }
        }
    protected abstract void onBindHeaderHolder(ViewHolderHeader3 holder, int headerPos, int layoutId, Object o);//多回传一个layoutId出去,用于判断是第几个headerview
    
    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        if (isHeaderViewPos(position)) {
            onBindHeaderHolder((ViewHolderHeader3) holder, position, mHeaderDatas.get(position).getLayoutId(), mHeaderDatas.get(position).getData());
            return;
    /**
     * 添加HeaderView
     *
     * @param layoutId headerView 的LayoutId
     * @param data     headerView 的data(可能多种不同类型的header 只能用Object了)
     */
    public void addHeaderView(int layoutId, Object data) {
        mHeaderDatas.add(new HeaderBean(layoutId, data));
    }
    
    /**
     * 添加HeaderView
     *
     * @param layoutId  headerView 的LayoutId
     * @param data      headerView 的data(可能多种不同类型的header 只能用Object了)
     * @param cacheSize 该种headerView在缓存池中的缓存个数
     */
    public void addHeaderView(int layoutId, Object data, int cacheSize) {
        mHeaderDatas.add(new HeaderBean(layoutId, data, cacheSize));
    }


    定义一个HeaderBean,存放HeaderView 的布局id,需要绑定的数据data,以及该种HeaderView在RecyclerViewPool缓存池中的缓存数量。

    将layoutId作为viewType。

    定义个ArrayList<HeaderBean>,按照add进来的顺序 存放HeaderBean。

    首先需要重写的就是getItemViewType()方法,在这个方法里根据postion判断是否在headerView的范围内,如果是返回layoutId作为viewType

    然后重写onCreateViewHolder()方法,如果headerData不为空,说明有HeaderView,那么遍历headerData,比较当前要创建的这个ViewHolder的type和headerData里的type,如果一样,说明则是要创建一个HeaderViewHolder。

    在onBindViewHolder()方法中,先根据postion判断是否是HeaderView,如果是,那么我们便根据postion从headerDatas里取出相应的layoutId和data,回调一个 abstract  的 onBindHeaderHolder()的方法,将这些参数都传入,交由子类去自由处理。 子类在这个方法里 完成数据的绑定即可。

    addHeaderView()方法比较简单,就是new 一个HeaderBean 然后add进HeaderDatas里即可。

    下面,重点来了,我们将在onAttachedToRecyclerView()方法里,设置headerView的在RecyclerViewPool里的缓存数量。

    @Override
    public void onAttachedToRecyclerView(RecyclerView recyclerView) {
        mInnerAdapter.onAttachedToRecyclerView(recyclerView);
        //设置HeaderView的ViewHolder的缓存数量
        if (null != mHeaderDatas && !mHeaderDatas.isEmpty()) {
            for (HeaderBean HeaderBean : mHeaderDatas) {
                recyclerView.getRecycledViewPool().setMaxRecycledViews(HeaderBean.getLayoutId(), HeaderBean.getCacheSize());
            }
        }
    判断如果headerdatas不为空,则遍历其中HeaderBean,并调用recyclerView.getRecycledViewPool()获取RecyclerViewPool对象,再调用它的setMaxRecycledViews()方法,传入viewType 和相应viewType的缓存数量。

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

    【4 RecyclerViewPool 源码浅析】

    关于RecyclerViewPool,可能还有很多人不是很了解(我也是最近才开始了解),这里简单说下我的理解,后续要深入研究一下RecyclerView相关的知识。

    RecyclerViewPool是一个为RecyclerView缓存ViewHolder的缓存池,它默认会为每种viewType缓存5个ViewHolder。

    源码为证:

    public static class RecycledViewPool {
        private SparseArray<ArrayList<ViewHolder>> mScrap =
                new SparseArray<ArrayList<ViewHolder>>();
        private SparseIntArray mMaxScrap = new SparseIntArray();
        private int mAttachCount = 0;
    
        private static final int DEFAULT_MAX_SCRAP = 5;
    

    这里的DEFAULT_MAX_SCRAP 就是每种viewType的默认缓存数量,本文所定义的默认数量和它保持一致,为5.

    mScrap 以viewType为key,value是一个ArrayList,里面存放的就是该种ViewType的ViewHolder啦。

    mMaxScrap 也以viewType为key,value就是每种viewType的最大缓存数量。

    mAttachCount 是用来计数的,每当RecyclerViewPool与一个adapter绑定、解绑,回调onAdapterChanged()方法,在其中便会+1 -1,当它为0时,就会清空这个RecyclerViewPool缓存池里的所有ViewHolder。这便是缓存池中ViewHolder被清空的时刻了。

    源码如下:

    public void clear() {
        mScrap.clear();
    }

    void attach(Adapter adapter) {
        mAttachCount++;
    }
    
    void detach() {
        mAttachCount--;
    }
    
    
    /**
     * Detaches the old adapter and attaches the new one.
     * <p>
     * RecycledViewPool will clear its cache if it has only one adapter attached and the new
     * adapter uses a different ViewHolder than the oldAdapter.
     *
     * @param oldAdapter The previous adapter instance. Will be detached.
     * @param newAdapter The new adapter instance. Will be attached.
     * @param compatibleWithPrevious True if both oldAdapter and newAdapter are using the same
     *                               ViewHolder and view types.
     */
    void onAdapterChanged(Adapter oldAdapter, Adapter newAdapter,
            boolean compatibleWithPrevious) {
        if (oldAdapter != null) {
            detach();
        }
        if (!compatibleWithPrevious && mAttachCount == 0) {
            clear();
        }
        if (newAdapter != null) {
            attach(newAdapter);
        }
    }

    我们在onAttachedtoRecyclerVie()方法里调用的recyclerView.getRecycledViewPool() 方法的源码如下:很简单 就是通过mRecycler获取RecyclerViewPool对象。

    /**
     * Retrieve this RecyclerView's {@link RecycledViewPool}. This method will never return null;
     * if no pool is set for this view a new one will be created. See
     * {@link #setRecycledViewPool(RecycledViewPool) setRecycledViewPool} for more information.
     *
     * @return The pool used to store recycled item views for reuse.
     * @see #setRecycledViewPool(RecycledViewPool)
     */
    public RecycledViewPool getRecycledViewPool() {
        return mRecycler.getRecycledViewPool();
    }

    紧接着调用的 设置缓存数量的方法源码如下:

    public void setMaxRecycledViews(int viewType, int max) {
        mMaxScrap.put(viewType, max);
        final ArrayList<ViewHolder> scrapHeap = mScrap.get(viewType);
        if (scrapHeap != null) {
            while (scrapHeap.size() > max) {
                scrapHeap.remove(scrapHeap.size() - 1);
            }
        }
    }
    这里除了将mMaxScrap里的value改变以外,还从mScrap里取出了该种ViewType的缓存队列list,并且判断size,如果超过最大值,会remove掉相应ViewHolder。

    RecyclerViewPool类还有几个其他的方法:

    获取缓存ViewHolder

    public ViewHolder getRecycledView(int viewType) {
        final ArrayList<ViewHolder> scrapHeap = mScrap.get(viewType);
        if (scrapHeap != null && !scrapHeap.isEmpty()) {
            final int index = scrapHeap.size() - 1;
            final ViewHolder scrap = scrapHeap.get(index);
            scrapHeap.remove(index);
            return scrap;
        }
        return null;
    }
    该方法通过viewType获取缓存的ViewHolder,可以看出,是从缓存list的尾部逐个取出ViewHolder的。


    存入ViewHolder

    public void putRecycledView(ViewHolder scrap) {
        final int viewType = scrap.getItemViewType();
        final ArrayList scrapHeap = getScrapHeapForType(viewType);
        if (mMaxScrap.get(viewType) <= scrapHeap.size()) {
            return;
        }
        if (DEBUG && scrapHeap.contains(scrap)) {
            throw new IllegalArgumentException("this scrap item already exists");
        }
        scrap.resetInternal();
        scrapHeap.add(scrap);
    }
    存入时,通过viewType获取对应ViewType的最大缓存数量 以及 对应viewType的缓存list,如果list.size大于等于最大值,则不缓存。 所以我们设置为0永远>=0,即没有缓存。(废话) 如果缓存list未满,则将该ViewHolder add进去,并且调用

    void resetInternal() {
        mFlags = 0;
        mPosition = NO_POSITION;
        mOldPosition = NO_POSITION;
        mItemId = NO_ID;
        mPreLayoutPosition = NO_POSITION;
        mIsRecyclableCount = 0;
        mShadowedHolder = null;
        mShadowingHolder = null;
        clearPayload();
        mWasImportantForAccessibilityBeforeHidden = ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO;
    }
    为这个ViewHolder恢复一些初始值,可以看到,都是一些flag itemId,postion等。


    RecyclerViewPool缓存池一共缓存的ViewHolder数量:是所有viewType的ViewHolder数量之和

    int size() {
        int count = 0;
        for (int i = 0; i < mScrap.size(); i ++) {
            ArrayList<ViewHolder> viewHolders = mScrap.valueAt(i);
            if (viewHolders != null) {
                count += viewHolders.size();
            }
        }
        return count;
    }


    最后一个方法是根据viewType获取相应缓存list,很简单,有就取出,没有就new一个,new的时候,要将自己put进mScrap中,如果从mMaxScrap中没有找到该种viewType对应的缓存数量上限,那么就使用默认值。

    如果我们已经调用过了setMaxRecycledViews()方法,设置过缓存上限,那么mMaxScrap就能找到该种viewType对应的index,所以就不会设置为默认值5.

    private ArrayList<ViewHolder> getScrapHeapForType(int viewType) {
        ArrayList<ViewHolder> scrap = mScrap.get(viewType);
        if (scrap == null) {
            scrap = new ArrayList<>();
            mScrap.put(viewType, scrap);
            if (mMaxScrap.indexOfKey(viewType) < 0) {
                mMaxScrap.put(viewType, DEFAULT_MAX_SCRAP);
            }
        }
        return scrap;
    }

    至此,所有的RecyclerViewPool的源码都已经看完啦,这个类还是比较简单的。

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

    【五,完整代码】

    这份代码FooterView并没有用此方法实现,是“强引用VIew方法实现的”。

    理由:

    1 因为FooterView往往是一个LoadMore相关的提示控件,内存占用很有限。

    2 LoadMore相关提示的控件 是需要强引用在Fragment Activity 或者相关类中,即使我在Adapter类里将其引用释放,这个View在内存的空间依然是无法被释放的。

    3 两种实现方法都放上来,大家可以根据本文描述的方法,自行尝试将FooterView也改写,可以和我讨论,稍后我也会附加上我修改的版本。

    /**
     * 介绍:一个给RecyclerView添加HeaderView FooterView的装饰Adapter类
     * 重点哦~ RecyclerView的HeaderView将可以被系统回收,不像老版的HeaderView是一个强引用在内存里
     * 作者:zhangxutong
     * 邮箱:zhangxutong@imcoming.com
     * 时间: 2016/8/2.
     */
    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;
            }
    
        }
    
        private static final int BASE_ITEM_TYPE_FOOTER = 2000000;//footerView的ViewType基准值
    
        //按照add顺序存放HeaderView的bean,bean包括layoutId,数据Data,和缓存数量cacheSize。
        // 在createViewHOlder里根据layoutId创建UI,在onbindViewHOlder里依据这个data渲染UI,
        // 在onAttachedToRecyclerView 为每种layoutId(同时也是viewType)的headerView设置缓存数量
        private ArrayList<HeaderBean> mHeaderDatas = new ArrayList<HeaderBean>();
        private SparseArrayCompat<View> mFooterViews = new SparseArrayCompat<>();//存放FooterViews,key是viewType
    
        protected RecyclerView.Adapter mInnerAdapter;//内部的的普通Adapter
    
        public HeaderRecyclerAndFooterWrapperAdapter2(RecyclerView.Adapter mInnerAdapter) {
            this.mInnerAdapter = mInnerAdapter;
        }
    
        public int getHeaderViewCount() {
            return mHeaderDatas.size();
        }
    
        public int getFooterViewCount() {
            return mFooterViews.size();
        }
    
        private int getInnerItemCount() {
            return mInnerAdapter != null ? mInnerAdapter.getItemCount() : 0;
        }
    
        /**
         * 传入position 判断是否是headerview
         *
         * @param position
         * @return
         */
        public boolean isHeaderViewPos(int position) {// 举例, 2 个头,pos 0 1,true, 2+ false
            return getHeaderViewCount() > position;
        }
    
        /**
         * 传入postion判断是否是footerview
         *
         * @param position
         * @return
         */
        public boolean isFooterViewPos(int position) {//举例, 2个头,2个inner,pos 0 1 2 3 ,false,4+true
            return position >= getHeaderViewCount() + getInnerItemCount();
        }
    
        /**
         * 添加HeaderView
         *
         * @param layoutId headerView 的LayoutId
         * @param data     headerView 的data(可能多种不同类型的header 只能用Object了)
         */
        public void addHeaderView(int layoutId, Object data) {
            //mHeaderViews.put(mHeaderViews.size() + BASE_ITEM_TYPE_HEADER, v);
    
    /*        SparseArrayCompat headerContainer = new SparseArrayCompat();
            headerContainer.put(layoutId, data);
            mHeaderDatas.put(mHeaderDatas.size() + BASE_ITEM_TYPE_HEADER, headerContainer);*/
    
            mHeaderDatas.add(new HeaderBean(layoutId, data));
        }
    
        /**
         * 添加HeaderView
         *
         * @param layoutId  headerView 的LayoutId
         * @param data      headerView 的data(可能多种不同类型的header 只能用Object了)
         * @param cacheSize 该种headerView在缓存池中的缓存个数
         */
        public void addHeaderView(int layoutId, Object data, int cacheSize) {
            //mHeaderViews.put(mHeaderViews.size() + BASE_ITEM_TYPE_HEADER, v);
    
    /*        SparseArrayCompat headerContainer = new SparseArrayCompat();
            headerContainer.put(layoutId, data);
            mHeaderDatas.put(mHeaderDatas.size() + BASE_ITEM_TYPE_HEADER, headerContainer);*/
    
            mHeaderDatas.add(new HeaderBean(layoutId, data, cacheSize));
        }
    
        /**
         * 设置某个位置的HeaderView
         *
         * @param headerPos 从0开始,如果pos过大 就是addHeaderview
         * @param layoutId
         * @param data
         */
        public void setHeaderView(int headerPos, int layoutId, Object data) {
            if (mHeaderDatas.size() > headerPos) {
    /*            SparseArrayCompat headerContainer = new SparseArrayCompat();
                headerContainer.put(layoutId, data);
                mHeaderDatas.setValueAt(headerPos, headerContainer);*/
                mHeaderDatas.get(headerPos).setLayoutId(layoutId);
                mHeaderDatas.get(headerPos).setData(data);
            } else if (mHeaderDatas.size() == headerPos) {//调用addHeaderView
                addHeaderView(layoutId, data);
            } else {
                //
                addHeaderView(layoutId, data);
            }
        }
    
        /**
         * 设置某个位置的HeaderView
         *
         * @param headerPos 从0开始,如果pos过大 就是addHeaderview
         * @param layoutId
         * @param data
         * @param cacheSize 该种headerView在缓存池中的缓存个数
         */
        public void setHeaderView(int headerPos, int layoutId, Object data, int cacheSize) {
            if (mHeaderDatas.size() > headerPos) {
    /*            SparseArrayCompat headerContainer = new SparseArrayCompat();
                headerContainer.put(layoutId, data);
                mHeaderDatas.setValueAt(headerPos, headerContainer);*/
                mHeaderDatas.get(headerPos).setLayoutId(layoutId);
                mHeaderDatas.get(headerPos).setData(data);
                mHeaderDatas.get(headerPos).setCacheSize(cacheSize);
            } else if (mHeaderDatas.size() == headerPos) {//调用addHeaderView
                addHeaderView(layoutId, data, cacheSize);
            } else {
                //
                addHeaderView(layoutId, data, cacheSize);
            }
        }
    
        /**
         * 添加FooterView
         *
         * @param v
         */
        public void addFooterView(View v) {
            mFooterViews.put(mFooterViews.size() + BASE_ITEM_TYPE_FOOTER, v);
        }
    
        /**
         * 清空HeaderView数据
         */
        public void clearHeaderView() {
            mHeaderDatas.clear();
        }
    
        public void clearFooterView() {
            mFooterViews.clear();
        }
    
    
        public void setFooterView(View v) {
            clearFooterView();
            addFooterView(v);
        }
    
        @Override
        public int getItemViewType(int position) {
            if (isHeaderViewPos(position)) {
                return mHeaderDatas.get(position).getLayoutId();//HeaderView的layoutId就是viewType
            } else if (isFooterViewPos(position)) {//举例:header 2, innter 2, 0123都不是,4才是,4-2-2 = 0,ok。
                return mFooterViews.keyAt(position - getHeaderViewCount() - getInnerItemCount());
            }
            return super.getItemViewType(position - getHeaderViewCount());
        }
    
        @Override
        public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    
            if (mHeaderDatas != null && !mHeaderDatas.isEmpty()) {//不为空,说明有headerview
                for (HeaderBean HeaderBean : mHeaderDatas) {
                    if (HeaderBean.getLayoutId() == viewType) {//匹配上了说明是headerView
                        return ViewHolderHeader3.get(parent.getContext(), null, parent, viewType, -1);
                    }
                }
            }
            if (mFooterViews.get(viewType) != null) {//不为空,说明是footerview
                return new ViewHolder(parent.getContext(), mFooterViews.get(viewType));
            }
            return mInnerAdapter.onCreateViewHolder(parent, viewType);
        }
    
        //protected abstract RecyclerView.ViewHolder createHeader(ViewGroup parent, int headerPos);
    
        protected abstract void onBindHeaderHolder(ViewHolderHeader3 holder, int headerPos, int layoutId, Object o);//多回传一个layoutId出去,用于判断是第几个headerview
    
        @Override
        public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
            if (isHeaderViewPos(position)) {
                onBindHeaderHolder((ViewHolderHeader3) holder, position, mHeaderDatas.get(position).getLayoutId(), mHeaderDatas.get(position).getData());
                return;
            } else if (isFooterViewPos(position)) {
                return;
            }
            //举例子,2个header,0 1是头,2是开始,2-2 = 0
            mInnerAdapter.onBindViewHolder(holder, position - getHeaderViewCount());
        }
    
    
        @Override
        public int getItemCount() {
            return getInnerItemCount() + getHeaderViewCount() + getFooterViewCount();
        }
    
        @Override
        public void onAttachedToRecyclerView(RecyclerView recyclerView) {
            mInnerAdapter.onAttachedToRecyclerView(recyclerView);
            //设置HeaderView的ViewHolder的缓存数量
            if (null != mHeaderDatas && !mHeaderDatas.isEmpty()) {
                for (HeaderBean HeaderBean : mHeaderDatas) {
                    recyclerView.getRecycledViewPool().setMaxRecycledViews(HeaderBean.getLayoutId(), HeaderBean.getCacheSize());
                }
            }
            //为了兼容GridLayout
            RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
            if (layoutManager instanceof GridLayoutManager) {
                final GridLayoutManager gridLayoutManager = (GridLayoutManager) layoutManager;
                final GridLayoutManager.SpanSizeLookup spanSizeLookup = gridLayoutManager.getSpanSizeLookup();
    
                gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
                    @Override
                    public int getSpanSize(int position) {
                        int viewType = getItemViewType(position);
                        if (isHeaderViewPos(position)) {
                            return gridLayoutManager.getSpanCount();
                        } else if (mFooterViews.get(viewType) != null) {
                            return gridLayoutManager.getSpanCount();
                        }
                        if (spanSizeLookup != null)
                            return spanSizeLookup.getSpanSize(position);
                        return 1;
                    }
                });
                gridLayoutManager.setSpanCount(gridLayoutManager.getSpanCount());
            }
    
        }
    
        @Override
        public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) {
            mInnerAdapter.onViewAttachedToWindow(holder);
            int position = holder.getLayoutPosition();
            if (isHeaderViewPos(position) || isFooterViewPos(position)) {
                ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
    
                if (lp != null
                        && lp instanceof StaggeredGridLayoutManager.LayoutParams) {
    
                    StaggeredGridLayoutManager.LayoutParams p =
                            (StaggeredGridLayoutManager.LayoutParams) lp;
    
                    p.setFullSpan(true);
                }
            }
        }
    }
    

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

    【6 实验】

    是否使用了我这种方法,设置了headerView的缓存数量为0后,该HeaderView不在屏幕显示时,触发GC,内存空间真的能被回收呢,?那么就实验见真相。

    这里我们借助 Android Studio 的Android Monitor里的dump java heap,它可以打印出当前内存里的对象的情况,还有旁边的小卡车按钮 Initiate GC,可以触发GC事件。

    我的猜想,设置HeaderView的缓存数量为0,当向下滑动一段距离,HeaderView已不可见时,打印内存对象情况,HeaderViewHolder应该是一个“”野对象“”,没有任何引用,触发GC后,它将被回收,内存降下降。

    而使用传统方法添加的HeaderView,即使设置缓存数量为0,由于View被强引用,内存空间也无法释放。

    (剧透一点,我原本想的是HeaderViewHolder应该被释放,而HeaderView不会被释放,但实际上内存空间真的没有减少,但是HeaderViewHolder也被强引用住了,如有知情者 望不吝赐教 )



    使用本方法:添加6个HeaderView,5个不设置缓存数量,1个设置缓存数量(加载四张图片的View),滑动到屏幕低端,Dump Java Heap,

    可以看到有一个HeaderView的ViewHolder的Depth为空,说明没设置缓存数量的ViewHolder的确会被回收掉,这时候我们点击黄色小卡车 强制GC,

    内存降了一半,这里面就有被回收掉的ViewHolder的功劳,ViewHolder被顺利回收,它里面引用的图片也可以被系统回收 腾出空间。

    此时再Dump Java Heap 查看一下,果然被回收掉了一个ViewHolder对象。

    再实验一下,只添加一个HeaderView(内含4个ImageView),然后滑动出屏幕后,HeaderViewHolder的数量,以及GC后的情况。
    对应的图片如下:

    此时就一个ViewHolderHeader的野对象,

    GC后,它顺利被回收,内存下降(四张图片被回收的功效。)

    这时,打开使用传统方式Adapter的Activity,也已经将这个RecyclerView的HeaderView的缓存设置为0

    和上次操作一样,进入后,滑动界面到HeaderView移除屏幕,Dump Java Heap,
    发现ViewHolderHeader3 的Depth是9,说明其正在被引用着,是无法被释放的,
    这时我们就算再怎么点击Initiate GC,内存也不会下降很明显,GC后再查看Java Heap,ViewHolderHeader3的对象依然存在,说明其确实被强引用着没有被系统回收。


    这里有个疑问,想问问各位,@翔神,在我的理解里,HeaderView实例由于在Adapter中强引用不会被回收,可是HeaderViewHolder 并没有被强引用住啊。这里看它确实被强引用了。这是一个疑问点,

    其实本文到这里,可以结束了,我已经证明了本文的HeaderAdapter 确实可以将其ViewHolder回收,如果此时触发GC,将释放该部分内存空间,可是那个HeaderViewHolder的强引用的问题 我依然没有答案。我又做了一些实验,

    不停的上下滑动,令这个HeaderView 一会滑出屏幕 一会进入屏幕,我的猜想中,应该会create N个 HeaderViewHolder。

    查看java heap,果然如下图:


    可以看到由于没有缓存,我们new了 9个ViewHolder出来,但是其中8个是野对象,可以被GC回收,此时屏幕上HeaderView已经不可见了,不明白为何还会有一个强引用。

    此问题我将继续研究下去。。


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

    【7 总结:】

    以本文的方法添加的HeaderView以layoutId做itemType。

    可以添加多种HeaderView。

    可以设置每种HeaderView的ViewHolder在RecyclerViewPool缓存池中的数量。
    设置为0则不缓存,当HeaderView不在屏幕可见时,触发GC的话, 它的ViewHolder将被回收,因为ViewHolder里的ItemView(HeaderView)本身也没有被其他强引用,所以它的内存空间将被释放。

    就算不设置cacheSize,默认缺省值为5(和RecyclerViewPool的缺省值一致)。

    同一种类型(相同的layoutId,viewType)的HeaderView也将被RecyclerVIew管理,不像之前那种方法 永远存于内存中。

    例如,有两种item1类型的HeaderView,设置缓存数量为1,那么 RecyclerViewPool里会缓存 一份item1类型的ViewHolder,有一份View的空间可以被系统回收。


    缓存本身是一种用空间换时间的技术,我们这么做,将更加灵活,headerView本身简单时,可以用默认配置,被RecyclerViewPool缓存住无法释放内存也无伤大雅,如果HeaderView本身很臃肿,占内存,滑出页面后,希望内存空间可以被回收,那么可以配置缓存数量,为0 则不缓存,但与此同时,我们获取了空间,就要付出时间的代价,每次滚回屏幕时,它的ViewHolder已经不存在,所以会重走onCreateViewHolder方法,这需要一定的时间。
    所以说,这种方法只是让开发者多一种选择,万一出现特殊情况,想要释放HeaderView,也有计可施,实际开发中,大部分情况不需要设置缓存个数,

    如果考虑多个RecyclerView共用同一个RecyclerViewPool,可为HeaderView设置1-2个缓存数。

    RecyclerView涉及的东西还很多,多个RecyclerView共用一个RecyclerViewPool 我稍后将研究一下,争取也研究出一点小心得分享给大家。即使没有什么浏览量,多一个人看到也是好事。


    说实话,设置缓存数量,95%的情况下用不到,但是假设一个场景,(我司app就如此),首页5个Tab,每个页面HeaderView长度2-3个屏幕,上面各种图片,5个Framgent切换是用hide show做的,所以5个页面全开时,将全部存于内存中,更可怕的是,每个页面头部再分2-3个子tab,每个子tab点开又是一坨。。。每个页面的风格都像h5。

    而不用replace是希望用户每次切换tab回来后还能停留在上次浏览的地方,这就需要我们做一个抉择,很多时候还要和产品商量(不过他们既然设计出这种界面,我已经放弃和他们商量)。

    如果依然要流畅的速度,还要这种又臭又长的页面,还不想内存OOMcrash,那么我只能选择!狗带~

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

    本文工具类已经被收入该库:

    该库还包括了N多的好用的Adapter。

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

    源码链接:http://download.csdn.net/detail/zxt0601/9609911

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



    展开全文
  • 网上普遍的资料反映的都是UITableView的header view动态变化需要重新给tableHeaderView赋值而不能只给其frame属性赋值 但如果出现需要多次给tableHeaderView赋值的情况时(比如每次进入界面都要刷新用户信息更新...

    网上普遍的资料反映的都是UITableView的header view动态变化需要重新给tableHeaderView赋值而不能只给其frame属性赋值

    但如果出现需要多次给tableHeaderView赋值的情况时(比如每次进入界面都要刷新用户信息更新header view的数据),就会出现每赋一次值header view和cell的间距就会越大的BUG。

    BUG表现:

    如果给tableHeaderView赋值的view的frame属性有带有非零小数的浮点数(尤其指.size.height和.origin.y),就会导致这个问题。

    具体表现为tableView的cell的位置每赋一次值就会下移一次,下移的数值为小数部分数值。

    BUG原因:

    从表现分析应该是个系统BUG,对浮点的处理有一定问题,具体原因就只能看大佬解答了。

    解决方法:

    1.直接保证tableHeaderView的frame都是整数,或是小数部分为0的数;

    2.目前公司项目计划使用钩子钩住这个系统函数 setTableHeaderView: ,然后做取整处理。

    12.26更新 附上代码实现:(同时对footer view也进行了处理)

    #import "FLTableViewHeaderFix.h"
    #import <objc/runtime.h>
    #import <objc/message.h>
    #import <UIKit/UIKit.h>
    
    @interface NSObject(FLTableViewHeaderFix)
    - (void)origin_setTableHeaderView:(UIView *)headerView;
    - (void)origin_setTableFooterView:(UIView *)footerView;
    @end
    
    @interface FLTableViewHeaderFix()
    
    @end
    
    @implementation FLTableViewHeaderFix
    
    + (void)load
    {
    //    NSLog(@"******** FLTableViewHeaderFix load *******");
        [FLTableViewHeaderFix hookSetTableHeaderView];
        [FLTableViewHeaderFix hookSetTableFooterView];
    }
    
    + (void) hookSetTableHeaderView
    {
        Class systemClass = objc_getClass("UITableView");
        SEL systemSelector = @selector(setTableHeaderView:);
        Class replaceClass = [self class];
        SEL replaceSelector = @selector(fix_setTableHeaderView:);
        Method originMethod = NULL;
        Method replaceMethod = NULL;
        originMethod = class_getInstanceMethod(systemClass,systemSelector);
        replaceMethod = class_getInstanceMethod(replaceClass, replaceSelector);
        
        if (originMethod && replaceMethod) {
            IMP originMethod_imp = method_getImplementation(originMethod);
            IMP replaceMethod_imp = method_getImplementation(replaceMethod);
            class_addMethod(systemClass, @selector(origin_setTableHeaderView:), originMethod_imp, method_getTypeEncoding(originMethod));
            class_replaceMethod(systemClass, systemSelector, replaceMethod_imp, method_getTypeEncoding(originMethod));
        }
    }
    
    + (void) hookSetTableFooterView
    {
        Class systemClass = objc_getClass("UITableView");
        SEL systemSelector = @selector(setTableFooterView:);
        Class replaceClass = [self class];
        SEL replaceSelector = @selector(fix_setTableFooterView:);
        Method originMethod = NULL;
        Method replaceMethod = NULL;
        originMethod = class_getInstanceMethod(systemClass,systemSelector);
        replaceMethod = class_getInstanceMethod(replaceClass, replaceSelector);
        
        if (originMethod && replaceMethod) {
            IMP originMethod_imp = method_getImplementation(originMethod);
            IMP replaceMethod_imp = method_getImplementation(replaceMethod);
            class_addMethod(systemClass, @selector(origin_setTableFooterView:), originMethod_imp, method_getTypeEncoding(originMethod));
            class_replaceMethod(systemClass, systemSelector, replaceMethod_imp, method_getTypeEncoding(originMethod));
        }
    }
    
    
    - (void)fix_setTableHeaderView:(UIView *)headerView
    {
    //    NSLog(@"******** FLTableViewHeaderFix fix_setTableHeaderView *******");
        CGRect tmp = headerView.frame;
        tmp.origin.x = ceil(tmp.origin.x);
        tmp.origin.y = ceil(tmp.origin.y);
        tmp.size.height = ceil(tmp.size.height);
        tmp.size.width = ceil(tmp.size.width);
        headerView.frame = tmp;
        [self origin_setTableHeaderView:headerView];
    }
    
    - (void)fix_setTableFooterView:(UIView *)footerView
    {
        //    NSLog(@"******** FLTableViewHeaderFix fix_setTableFooterView *******");
        CGRect tmp = footerView.frame;
        tmp.origin.x = ceil(tmp.origin.x);
        tmp.origin.y = ceil(tmp.origin.y);
        tmp.size.height = ceil(tmp.size.height);
        tmp.size.width = ceil(tmp.size.width);
        footerView.frame = tmp;
        [self origin_setTableFooterView:footerView];
    }
    
    @end

    在主工程项目中#import "FLTableViewHeaderFix.h"即可

    展开全文
  • 关于RecyclerView添加HeaderView和FooterView

    千次阅读 2018-01-11 15:27:02
    RecyclerView自己用了很长一段时间,如今基本不怎么使用ListView了,用过RecyclerView的小伙伴都知道,它的很多功能都需要自定义,比如item的点击事件,还有添加HeaderView和FooterView等等。高度的自定义使它受到...

    RecyclerView自己用了很长一段时间,如今基本不怎么使用ListView了,用过RecyclerView的小伙伴都知道,它的很多功能都需要自定义,比如item的点击事件,还有添加HeaderView和FooterView等等。高度的自定义使它受到众多Android码农的喜爱。网上也出现了很多封装好的RecyclerView,可以轻松的添加各种需要的事件。自己出于探索的好奇,也仔细研究了下关于HeaderView和FooterView的添加。可能说的不是很详细,如果有不对之处,还希望小伙伴们能够指出。

    RecyclerView有几个非常重要的方法,分别是:getItemCount,getItemViewType,onCreateViewHolder和onBindViewHolder。
    想要添加HeaderView和FooterView,就需要在getItemCount和getItemViewType上“做手脚”。

    先说下如何添加FooterView,因为FooterView是添加到最底下,所以基本不影响数据List,比HeaderView简单很多。
    一般getItemCount都会重写,return list.size(),所以想要添加FooterView,只需要return list.size()+1即可,然后在方法getItemViewType中,进行简单的判断

    
     @Override
     public int getItemCount() {
         return items.size() + 1;  
    }
    
    public static final int TYPE_FOOTER_VIEW = 1;
    @Override
    public int getItemViewType(int position) { 
         /*当position是最后一个的时候,也就是比list的数量多一个的时候,则表示FooterView*/
       if (position + 1 == items.size() + 1) {
            return TYPE_FOOTER_VIEW;
       }
        return super.getItemViewType(position);
    }

    细心的小伙伴会发现super.getItemViewType(position)实际上是返回一个0,也就是说,在RecyclerView中,默认是没有对ViewType进行处理的,所以真正意义上,返回什么值是有我们自己来操控的,我默认将TYPE_FOOTER_VIEW定义为1进行区别,重写了这两个方法后,只需要在onCreateViewHolder和onBindViewHolder进行简单判断,就可以产生FooterView效果了

    /**一个很简单的ViewHolder,小伙伴可以根据自己的需求自定义*/
    public static class FooterViewHolder extends RecyclerView.ViewHolder {
            public FooterViewHolder(View itemView) {
                super(itemView);
    
            }
    }
    
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
      if (viewType == TYPE_FOOTER_VIEW) {
      /*这里返回的是FooterView*/
         final View footerView = LayoutInflater.from(mContext).inflate(R.layout.adapter_foot_view, parent, false);
         return new FooterViewHolder(footerView);
      } else {
      /*这里返回的是普通的View*/
         final View view = LayoutInflater.from(mContext).inflate(R.layout.adapter_view, parent, false);
         return new BaseViewHolder(view);
      }
    }
    
        @Override
        public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
            if (holder != null) {
                if(holder instanceof BaseViewHolder){
                 /*这里返回的是普通的View*/
                }else if(holder instanceof FooterViewHolder){
                /*这里返回的是FooterView*/
                }
            }
        }

    说完了FooterView,再说说HeaderView,如果对FooterView已经理解了,估计应该有小伙伴知道HeaderView的实现原理了,也是一样让list的数量+1(这个是仅仅只有HeaderView,没有FooterView的情况,小伙伴不要混在一起,我只是说一下HeaderView的实现原理)

    @Override
     public int getItemCount() {
         return items.size() + 1;  
    }
    
    public static final int TYPE_HEADER_VIEW = 2;
    @Override
    public int getItemViewType(int position) { 
        /*当position是第一个的时候,则表示HeaderView*/
       if (position == 0) {
            return TYPE_HEADER_VIEW;
       }
        return super.getItemViewType(position);
    }

    由于HeaderView的增加,会影响到position,所以需要在onBindViewHolder中,进行调正

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
       if (holder != null) {
    
         if(holder instanceof BaseViewHolder){
           /*这里返回的是普通的View*/
           /*RecyclerView在增加HeaderView后,list的索引也发生了变化,需要整体-1,才是对应的list的索引*/
           items.get(--position);
         }else if(holder instanceof HeaderViewHolder){
           /*这里返回的是HeaderView*/
         }
       }
    }

    以上就是最简单的HeaderView和FooterView的实现,只要知道了这个原理,就可以根据自己的需求,添加HeaderView和FooterView,或者同时添加。也可以根据需求,添加两个以上的HeaderView,实际上就是在getItemCount()中添加对应HeaderView的数量。

    建议小伙伴们自己整理一个BaseAdapter,将HeaderView和FooterView的代码添加进去,这样以后要用到的时候,直接新建一个adapter去继承自己的BaseAdapter。

    展开全文
  • RecycleView添加headerView

    2018-04-22 11:05:43
    这两日在写一个小东西,需要给RecycleView添加一个headerView(轮播图),总结一下吧 思路 在RecycleView的Adapter里面声明一个内部view变量 然后在CreatView方法里面判断位置,如果是position为0的话就代表...
  • RecyclerView添加Header的正确方式

    万次阅读 多人点赞 2015-11-08 10:12:49
    看了一下博客目录,已经有好几篇博客是关于RecyclerView的,不过对于这么一款强大的控件,我还是要再写一篇博客来学习一下,这篇博客的主题是《为RecyclerView添加header》,当然在看完这篇博客后,相信添加Footer你...
  • ListView中加了头和尾后不显示HeaderView。当ListView高度为精确值时(dp)没问题。但当为match_parent和wrap_content时就只能看到尾了,看到头(头成是空白了)。 **activity_main.xml:** ``` ...
  • ListView添加删除FooterView HeaderView

    千次阅读 2016-05-07 09:32:40
    在开发项目,想要使异步或分页加载更好看一些,可以在ListView加上HeaderView或者footerView。 addHeaderView()方法:主要是向listView的头部添加布局 addFooterView()方法:主要是向listView的底部添加布局 ...
  • 第一个头布局我 设置了一下 位置,目的是为了证明,在自定义下拉刷新时,当listview已经添加了一个headerview时候,可以继续添加一个headerview影响下拉刷新 代码如下: MainActivity package com.ss.hsx;...
  • 在一个添加了headerViewlistView中使用SwipeRefreshLayout,需要headerView完成显示了,才能进行下拉刷新,请问大神怎样判断是否完成显示了?
  • 我给listview添加了 HeaderViewHeaderView填充的布局里面包含了一个TextView,起名为tv_image,我想获取tv_image的宽高,试了三种办法,前两种都成功而最后一个成功这是为什么呢? 第一种办法,通过视图树监听 ...
  • ListView添加HeaderView

    2014-06-27 11:32:49
    在应用程序把头部inflate出来,然后调用ListView的下面两个方法的一个就可以(具体请参考API doc) 1 2 public void addHeaderView(View v); public void addHeaderView(View v,...
  • listView是添加了headerView的,注意,在Adapter的getVIew方法的position是包括headerView的,也就是position = 0,就是指的其中第一个item. 但是在在监听方法,比如onItemClicklistener的position确实...
  • ListView中 item footerview headerview 的布局 事件监听等问题的收集整理
  • ListView中headerview的动态显示和隐藏的问题Android1.动态设置headerview的方法动态设置headerview有两个思路。方法一将header的布局写在list item的布局文件,在adapter通过判断position的值是否为0动态控制其...
  • mAdapter.notifyDataSetChanged(); my_list_view.setSelection(1); ...但是有时候会发现根本隐藏掉,我猜测是由于mAdapter.notifyDataSetChanged()后,listview的刷新是异步完成的,所以在s
  • android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="100dp" > </LinearLayout>
  • 这两天在做项目的 时候遇到了这个问题,发现网上也有人提出了这个问题,但是根本没有人去解决,可能是问题太简单了。我在这里贴上我的代码,以往对大家有帮助。...我要实现的是listviewheaderview 一开
  • 在Android开发,在使用listView时,我们会经常用到footerView或者headerView,给ListView添加的footer和header好多时候,会根据情况隐藏和显示。因为footer和header使用删除和添加的方法总感觉不好。而直接设置...
  • 当我们为listview,add一个headerView时,headerview里有好多控件,例如: android:id="@+id/rl_apply_content" android:layout_width="match_parent" android:layout_height="350dp"
  • 【简介】headerview就是通常看到的那种listview手势下滑露出上面的部分,下拉到一定位置,松手会开始请求网络数据,然后刷新listview的列表。footerview一般就是listview手势一直上滑动到显示出最后一条数据,然后...
  • 最近在使用Android ListView 添加 HeaderView
  • androidlistview在setAdapter后就无法再addHeaderView了,在2.3以下系统会crash,2.3以上系统暂未发现。 解决办法: 1.抛弃原来的adapter,为listview设置新的adapter,但这样在时间和空间的性能上不是很好...
  • 下面是listview,我就是清楚上面怎么做,如果用headerView 怎么才能实现响应的的点击响应事件! public class MainActivity extends Activity { private ListView lv; private List<String> list; private ...
  • android中ListViewHeaderView的Padding详解

    千次阅读 2015-09-28 16:14:46
    mHeaderViewHeight = 50; 则 mHeaderView.setPadding(0, mHeaderViewHeight, 0, 0); 时结果为: mHeaderView.setPadding(0, 0, 0, 0);...mHeaderView.setPadding(0, -
  • 之前又不止一次的碰到类似的需求,之前为了图方便,都是选择的重写listView的onMeasure方法@Override public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int expandSpec = MeasureSpec....
  • ListViewHeaderView

    2014-07-29 19:30:00
    今天突然发现ListView的OnItemClickListener监听事件的position返回是从1开始的,一直觉得很奇怪,在群里问了后,可能是headerView的问题,特意去查了一番,原来是如此: 特此记过! (1)添加HeaderView...
  • listview 实现隐藏显示headerview
  • 最后方法: listview.post(new Runnable()  {  @Override  public void run()  {  listvie
  • Android 优雅的为RecyclerView添加HeaderView和FooterView

    万次阅读 多人点赞 2016-07-08 08:19:40
    本文已授权微信公众号:鸿洋(hongyangAndroid)在微信公众号平台原创首发。 转载请标明出处: ... 本文出自:【张鸿洋的博客】 1、概述 ...RecyclerView通过其高度的可定制性深受大家的青睐,也有非常多的使用者...

空空如也

1 2 3 4 5 ... 20
收藏数 8,027
精华内容 3,210
关键字:

headerview