资讯专栏INFORMATION COLUMN

RecyclerView添加header与footer

xiaoqibTn / 555人阅读

摘要:使用与方法实现。只要自己自定义布局文件,添加进去即可。所以根据的添加头部与尾部的实现原理,我们可以模仿实现的相同的功能。实现正常的布局。在中重写方法添加标记。这样整个调整部分就基本完成。

前言

这次主要关于RecyclerView添加headerfooter的实现方法,我们都知道,在使用ListView的时候我们能自由的给自己的ListView添加头部与尾部。使用addHeaderView()addFooterView()方法实现。只要自己自定义布局文件,添加进去即可。但当我们使用RecyclerView的时候就会发现这两个方便的方法没有了。那么如果此时我们要实现这些功能时要怎么办呢?不急,下面我们先来熟悉下ListView的添加headerfooter的实现原理。

ListView源码

既然ListView实现了添加头部与尾部的原理,所以我们可以先进入ListView的源码,查看其中的addHeaderView()方法与addFooterView()方法。

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();
            }
        }
    }
这里省略addFooterView()源码,其实跟addHeaderView()基本一致

通过addHeaderView()的源码我们可以发现,它是使用一个FixedViewInfo类来存储数据。

public class FixedViewInfo {
        /** The view to add to the list */
        public View view;
        /** The data backing the view. This is returned from {@link ListAdapter#getItem(int)}. */
        public Object data;
        /** true if the fixed view should be selectable in the list */
        public boolean isSelectable;
    }

其中最主要的代码就是

 if (!(mAdapter instanceof HeaderViewListAdapter)) {
                mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, mAdapter);
            }

我们会发现它new了一个HeaderViewListAdapter类,那么我们再进入HeaderViewListAdapter源码中,发现他就相对于一个我们自定义的Adapterheaderfooter的封装。

代码都比较简单,主要是思想,其中主要的是有

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

根据position来设置ViewType的值

 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;
    }

再看getView()方法

public View getView(int position, View convertView, ViewGroup parent) {
        // Header (negative positions will throw an IndexOutOfBoundsException)
        int numHeaders = getHeadersCount();
        if (position < numHeaders) {
            return mHeaderViewInfos.get(position).view;
        }

        // Adapter
        final int adjPosition = position - numHeaders;
        int adapterCount = 0;
        if (mAdapter != null) {
            adapterCount = mAdapter.getCount();
            if (adjPosition < adapterCount) {
                return mAdapter.getView(adjPosition, convertView, parent);
            }
        }

        // Footer (off-limits positions will throw an IndexOutOfBoundsException)
        return mFooterViewInfos.get(adjPosition - adapterCount).view;
    }

getItemViewType类似,当position小于headercount时,此时返回头部View,当position减去headeercount时依然小于正常的adaptercount,此时返回正常的View,如果还有,自然剩下的就是footer了。

所以根据ListView的添加头部与尾部的实现原理,我们可以模仿实现RecyclerView的相同的功能。

EnhanceRecyclerView

那么我们根据ListView来实现一个能够添加headerfooterRecyclerView,我这里定义为EnhanceRecyclerView

FixedViewInfo

在上面我们看到ListView中使用到了FixedViewInfo,就是一个自定义的类,用来存储数据,我们做适当的修改如下,View还是不变,依然是添加的视图,将其余的删掉,换成viewType

public class FixedViewInfo {
        public View view;
        public int viewType;
    }
addHeaderView与addFooterView

做相应的修改,其中BASE_HEADER_VIEW_TYPEBASE_FOOTER_VIEW_TYPE是一个finalint数据

public void addHeaderView(View view) {
        FixedViewInfo info = new FixedViewInfo();
        info.view = view;
        info.viewType = BASE_HEADER_VIEW_TYPE + mHeaderViewInfos.size();
        mHeaderViewInfos.add(info);

        if (mAdapter != null) {
            mAdapter.notifyDataSetChanged();
        }
    }
    public void addFooterView(View view) {
        FixedViewInfo info = new FixedViewInfo();
        info.view = view;
        info.viewType = BASE_FOOTER_VIEW_TYPE + mFooterViewInfos.size();
        mFooterViewInfos.add(info);

        if (mAdapter != null) {
            mAdapter.notifyDataSetChanged();
        }
    }
setAdapter

下面是setAdapter(),使用后面封装的WrapperRecyclerViewAdapter

    @Override
    public void setAdapter(Adapter adapter) {
        if (!(adapter instanceof WrapperRecyclerViewAdapter))
            mAdapter = new WrapperRecyclerViewAdapter(mHeaderViewInfos, mFooterViewInfos, adapter);
        super.setAdapter(mAdapter);

//        if (isShouldSpan) {
//            ((WrapperRecyclerViewAdapter) mAdapter).adjustSpanSize(this);
//        }
    }
WrapperRecyclerViewAdapter

我们也可以根据ListViewHeaderViewListAdapter来模仿实现一个封装头部与尾部的adapter。我这里定义为WrapperRecyclerViewAdapter继承RecyclerView.Adapter根据情况适当修改。

getItemCount
@Override
    public int getItemCount() {
        if (mAdapter != null) {
            return getHeadersCount() + getFootersCount() + mAdapter.getItemCount();
        } else {
            return getHeadersCount() + getFootersCount();
        }
    }
getItemViewType
@Override
    public int getItemViewType(int position) {
        int numHeaders = getHeadersCount();
        if (position < numHeaders) {
            return mHeaderViewInfos.get(position).viewType;
        }
        int adjPosition = position - numHeaders;
        int adapterPosition = 0;
        if (mAdapter != null) {
            adapterPosition = mAdapter.getItemCount();
            if (adjPosition < adapterPosition) {
                return mAdapter.getItemViewType(adjPosition);
            }
        }

        return mFooterViewInfos.get(position - adapterPosition - getHeadersCount()).viewType;
    }

这里逻辑与ListView的基本一致。

onCreateViewHolder

这里需将HeaderViewListAdapter中的getView()方法分成两部分来实现。

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        if (viewType >= EnhanceRecyclerView.BASE_HEADER_VIEW_TYPE && viewType < EnhanceRecyclerView.BASE_HEADER_VIEW_TYPE + getHeadersCount()) {
            View view = mHeaderViewInfos.get(viewType - EnhanceRecyclerView.BASE_HEADER_VIEW_TYPE).view;
            return viewHolder(view);
        } else if (viewType >= EnhanceRecyclerView.BASE_FOOTER_VIEW_TYPE && viewType < EnhanceRecyclerView.BASE_FOOTER_VIEW_TYPE + getFootersCount()) {
            View view = mFooterViewInfos.get(viewType - EnhanceRecyclerView.BASE_FOOTER_VIEW_TYPE).view;
            return viewHolder(view);
        }
        return mAdapter.onCreateViewHolder(parent, viewType);
    }

根据不同的viewType来实现不同的布局文件.不是headerfooter布局时则直接回调原始adapteronCreateViewHolder()方法。实现正常的布局。

onBindViewHolder
    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        int numHeaders = getHeadersCount();
        if (position < numHeaders) {
            return;
        }
        int adjPosition = position - numHeaders;
        int adapterCount = 0;
        if (mAdapter != null) {
            adapterCount = mAdapter.getItemCount();
            if (adjPosition < adapterCount) {
                mAdapter.onBindViewHolder(holder, adjPosition);
                return;
            }
        }

    }

根据position的位置来选择,当position属于正常的位置范围之内时,则回调原始的adapteronBindViewHolder()方法,实现数据的绑定;对于其它的情况,无需做处理。

主要的模仿变动就是这些,然后自己在根据情况进行适当的修改,最后在布局文件中使用自定义的EnhanceRecyclerView替代RecyclerView
调整

我们都知道在使用RecyclerView的时候要设置LayoutManager,如果按照上面的实现,当LayoutManager设置为LinearLayout时,自然没上面问题,头部与尾部能正常添加;但当我们的LayoutManager设置为GridLayoutManagerStaggeredGridLayoutManager时,我们会发现头部与尾部的添加出现异常,就是他们不能独自占一行,所以这里我们要做相应的调整,使他们在添加头部与尾部的时候独自占一行。

adjustSpanSize

在上面我们自定义的WrapperRecyclerViewAdapter中添加adjustSpanSize()方法,用来实现调整。

public void adjustSpanSize(RecyclerView recyclerView) {
        if (recyclerView.getLayoutManager() instanceof GridLayoutManager) {
            final GridLayoutManager manager = (GridLayoutManager) recyclerView.getLayoutManager();
            manager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
                @Override
                public int getSpanSize(int position) {
                    int numHeaders = getHeadersCount();
                    int adjPosition = position - numHeaders;
                    if (position < numHeaders || adjPosition >= mAdapter.getItemCount())
                        return manager.getSpanCount();
                    return 1;
                }
            });
        }

        if (recyclerView.getLayoutManager() instanceof StaggeredGridLayoutManager) {
            isStaggered = true;
        }
    }

可以看出当LayoutManagerGridLayoutManager时,我们通过manager来设置setSpanSizeLookup()getSpanSize()方法中根据具体判断来调用 manager.getSpanCount()来实现头部与尾部的占一行的效果;对于StaggeredGridLayoutManager因为其没有setSpanSizeLookup()方法,所以我们先做标记,在ViewHolder中为每一个需要的ItemView来设置paramssetFullSpa(true)方法来填充一行。

    private RecyclerView.ViewHolder viewHolder(View itemView) {

        if (isStaggered) {
            StaggeredGridLayoutManager.LayoutParams params = new StaggeredGridLayoutManager.LayoutParams(StaggeredGridLayoutManager.LayoutParams.MATCH_PARENT,
                    StaggeredGridLayoutManager.LayoutParams.WRAP_CONTENT);
            params.setFullSpan(true);
            itemView.setLayoutParams(params);
        }
        return new RecyclerView.ViewHolder(itemView) {
        };
    }
setLayoutManager

EnhanceRecyclerView中重写setLayoutManager()方法,添加标记。

@Override
    public void setLayoutManager(LayoutManager layout) {
        if (layout instanceof GridLayoutManager || layout instanceof StaggeredGridLayoutManager)
            isShouldSpan = true;
        super.setLayoutManager(layout);
    }

最后再 在EnhanceRecyclerViewsetAdapter()方法中根据标记来判断是否调用adjustSpanSize()方法来进行调整headerfooter.

if (isShouldSpan) {
            ((WrapperRecyclerViewAdapter) mAdapter).adjustSpanSize(this);
        }

其实就是前面setAdapter()中所注释的代码。

这样整个调整部分就基本完成。headerfooter的添加也就基本完成了。

总结

通过RecyclerView的头部与尾部的添加,我们应该能学习如何运用原有的资源来实现我们所需要的功能。虽然方法不一定是最好的,因为使用这种方法实现也存在一些问题,例如不能在初始化(onCreate)中同时添加headerfooter,否则会出现布局混乱的现象,现在还不知道为何有这种特例,希望知道的可以告知解决下,但这种模仿的思想还是很值得推荐的。

个人blog地址:https://idisfkj.github.io/arc...

关注

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/12944.html

相关文章

  • RecyclerView封装库和综合案例【包含25篇博客】

    摘要:支持复杂页面,例如添加自定义头部和底部布局,支持横向滑动,还可以支持粘贴头部类似微信好友分组,支持不规则瀑布流效果,支持侧滑删除功能。支持粘贴头部的需求效果,这种效果类似微信好友分组的那种功能界面。 目录介绍 1.复杂页面库介绍 2.本库优势亮点 2.1 支持多种状态切换管理 2.2 支持添加多个header和footer 2.3 支持侧滑功能和拖拽移动 2.4 其他亮点介绍 ...

    silenceboy 评论0 收藏0
  • RecyclerView下拉刷新上拉更多

    摘要:前言在原来的文章中我提及了如何使用添加与,今天我们来更深入的扩展一下使用实现常用的下拉刷新与上拉加载更多的功能。状态重置设置在调用下拉刷新或者上拉加载更多之后,我们为其构造通用方法实现,状态的重置与数据的更新,方便统一调用。 前言 在原来的文章中我提及了如何使用RecyclerView添加header与footer,今天我们来更深入的扩展一下使用RecyclerView实现常用的下拉刷...

    Meils 评论0 收藏0
  • 复杂type页面封装库,支持多种状态切换和下拉刷新上拉加载

    摘要:支持复杂页面,例如添加自定义头部和底部布局,支持横向滑动,还可以支持粘贴头部类似微信好友分组,支持不规则瀑布流效果,支持侧滑删除功能。十分方便实现复杂的布局页面,结构上层次分明,便于维护。 目录介绍 1.复杂页面库介绍 2.本库优势亮点 2.1 支持多种状态切换管理 2.2 支持添加多个header和footer 2.3 支持侧滑功能和拖拽移动 2.4 其他亮点介绍 3.如...

    Karrdy 评论0 收藏0
  • TwinklingRefreshLayout 小而强大的刷新控件,自带顺滑的越界回弹,v1.04 版

    摘要:为了达到更好的显示效果,最好禁用系统的,如上给添加。头部固定高度在此高度上显示刷新状态底部高度设置最大的越界高度灵活的设置是否禁用上下拉。是否允许越界回弹。表示向上拉下拉释放时回调的状态。 TwinklingRefreshLayout v1.04 版精心重构,优化 UI、刷新及越界动画效果,修复众多 bug,完美发布!TwinklingRefreshLayout延伸了Google的Sw...

    2501207950 评论0 收藏0
  • TwinklingRefreshLayout 小而强大的刷新控件,自带顺滑的越界回弹,v1.04 版

    摘要:为了达到更好的显示效果,最好禁用系统的,如上给添加。头部固定高度在此高度上显示刷新状态底部高度设置最大的越界高度灵活的设置是否禁用上下拉。是否允许越界回弹。表示向上拉下拉释放时回调的状态。 TwinklingRefreshLayout v1.04 版精心重构,优化 UI、刷新及越界动画效果,修复众多 bug,完美发布!TwinklingRefreshLayout延伸了Google的Sw...

    lentrue 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<