ExtraViewWrapperAdapter--添加额外头部尾部功能的装饰adapter

目录

  • 目录

    • 概述
    • 关于头部和尾部
      • 分离原始数据及装饰数据
      • headerView与footerView的创建与显示
      • 使用唯一的标签
      • 关于headerView与FooterView位置的计算
      • 头部尾部的判断方式
    • 与HeaderRecycleAdapter的接口相关
    • 其它
    • 使用方式
    • GitHub地址
    • 示例图片

概述

对于ListView有自带的方法添加headerView及footerView,但是RecycleView仅仅只是维护缓存的View,本身并不处理内容显示,都交给了RecycleView.Adapter处理,所以如果想要让RecycleView也可以添加headerView和footerView的话,只有两种方式

  • 是重写RecycleView
  • 是通过Adapter的方式去解决这个问题,将headerView及footerView转换成内部数据的形式显示出来.

针对这个问题,这里使用第二种方式,扩展了Adapter,通过装饰者模式,只需要将自己的Adatper包括进外层的Adapter中,即可直接添加headerView及footerView,同时不会对原有的数据造成任何影响.

这里需要注意的是,此处的ExtroViewWrapperAdaper包装类是通用的,但是更针对于HeaderRecycleAdatper,包括实现了HeaderRecycleAdapter相关的一些接口用于扩展.

对于普通的Adapter,也是可以直接使用.


关于头部和尾部

由于通过Adapter来完成头部与尾部的功能添加(这样做的好处是在任何的RecycleView上都是可以使用的),所以实现方式上跟普通的多类型item的Adapter的实现方式是一致的,这里不再强调.主要说明如何进行装饰.

分离原始数据及装饰数据

对于Adapter,由于每个Adapter完成的功能都不同,所以我们只能不能知道Adapter是具体如何完成的,那么我们需要尽量将原始的数据与装饰的数据(头部/尾部)分离开来.ExtraViewWrapperAdapter主要就是要处理这个.

首先是保存原有Adapter的引用.对于头部和尾部一般是只添加一个,但这里考虑到可能会添加多个,所以是允许添加多个的.

对于添加多个的情况,每一个headerView都可能不同,这时就需要提供每一个headerView的一个viewTag(标志),这个标志是唯一的,用于分辨不同的headerView的类型以显示出来.

对此使用一个内部类来管理添加的headerView或者footerView.

/**
 * 头部/尾部添加额外View的缓存处理类
 */
public static class HeaderFooterViewCache {
    private List<Map.Entry<Integer, View>> mViewCacheMap;
    private Map<Integer, Integer> mIndexMap;

    public HeaderFooterViewCache() {
        mViewCacheMap = new LinkedList<Map.Entry<Integer, View>>();
        mIndexMap = new ArrayMap<Integer, Integer>();
    }

    //返回当前保存的View的个数
    public int size();

    //根据位置获取对应的标签
    public int getViewViewTag(int position);

    //根据标签获取对应的view
    public View getView(int viewTag);

    //检测是否已经存在某个标签
    public boolean isContainsView(int viewTag);

    //添加新view及其唯一标签,当该标签已经存在某个view时,将替换该view,返回被替换的view,或者是null;若view为null,返回null,添加失败
    public View addView(int viewTag, View view);

    //移除某个标签对应的view
    public boolean removeView(int viewTag);

    //清除所有的view
    public void clearAllView();

    //获取view的标签列表,标签应该是唯一的
    public Set<Integer> getViewTags();
}

这里仅给出部分方法签名及其作用说明.具体实现暂不给出.这里缓存时使用两个不同的数据结构,一个是List,一个是Map,原因是List用来保存添加的view的顺序,Map是用于根据标签匹配并保存添加的view.


headerView与footerView的创建与显示

Adapter是自己加载layout并创建view的,但是headerView与footerView是不同的,是直接添加的view并不通过adapter加载与绑定数据.所以在这里使用的ViewHolder也只是一个临时保存的容器而已.

onCreateViewHolder()中创建headerView与footerView的holder

@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    return new ExtraViewHolder(mHeaderCache.getView(viewType));
}

ExtraViewHolder类的实现仅仅是继承RecycleView.ViewHolder而已,并没有任何其它的操作.这里只是为了配合Adapter的实现方式.

headerView与footerView的创建完全由外部处理,Adapter并不作任何处理(仅仅是缓存并显示而已),view的获取是通过缓存的HeaderFooterViewCache类通过唯一的viewTag(标签)进行获取的.


使用唯一的标签

由以上可知,headerView与FooterView是被HeaderFooterViewCache缓存的,并且使用唯一的viewTag进行标识.

使用Map的原因是标签必须是唯一的,一个标签也只能用于一个view,这样在Adapter加载view时才不会导致有关viewType问题.

以下为Adapter需要实现的一些方法.

//根据位置获取view的类型
public int getItemViewType(int position){
    //这里仅给出headerView位置的代码,其它位置代码忽略
    return mHeaderCache.getViewViewTag(position);
}

//根据view的类型加载view
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType){
    //这里仅给出headerView的holder创建
    return new ExtraViewHolder(mHeaderCache.getView(viewType));
}

Map中的唯一的viewTag标签就是用于此处的viewType,由于加载holder是通过viewType来创建的,所以不同的viewTag代表了不同的viewType,也是代表了不同的view.

通过这种方式处理的原因是,headerView与footerView是在创建ExtraViewHolder时需要使用的,而创建一个holder只会在onCreateViewHolder()中进行创建.此时参数来源仅有两个:

  • parent,父容器
  • viewType,item类型

这两个参数显然仅有第二个参数可以使用.由于headerView和FooterView理论上也不会有很多,因此可以通过指定唯一的viewTag匹配对应的view,同时将viewTag传递到onCreateViewHolder()就可以获取到该tag对应的view了.

对此,在Adapter的getItemViewType()方法中返回的viewType实际上也就是view所对应的viewTag.这样在一定程度上可以减少很多的计算与匹配工作.


关于headerView与FooterView位置的计算

RecycleView很重要的一个特点是根据position进行显示item.由于添加了headerView与FooterView,所以原始Adapter中position相对于原来的位置必定会改变.

这里需要计算headerView与FooterView的数量.关于这部分在前面给出的缓存类HeaderFooterViewCache中已经有相关的方法了,所以可以通过该方法直接获取其对应的数量.

private int getHeaderViewCount() {
    return mHeaderCache.size();
}

这里需要注意,headerView是通过mHeaderCache缓存管理的,footerView是通过mFooterCache缓存管理的,并不是同一个类管理两种view.

得到headerView或者footerView的数量后,就可以很方便地计算了.headerView必定在InnerAdapter的item之前,所以position对应的前面都是headerView.

//headerView的position
int headerPosition=position;
//innerAdapter中item的position,除去header部分
int innerPosition=position-headerViewCount;
//footerView的position,除去header及innerAdapter的item数量
int footerPosition=position-headerViewCount-innerAdapter.getItemCount();

ExtraViewWrapperAdapter提供了获取内部adapter的位置的方法,该方法就是基于以上的方式进行计算得到的.

//这里的wrapAdapterPosition指 ExtraViewWrapperAdapter 对应的position
public int getInnerAdapterPosition(int wrapAdapterPosition) {
    int headerViewCount = getHeaderViewCount();
    int innerPosition = wrapAdapterPosition - headerViewCount;
    if (mInnerAdapter != null && innerPosition < mInnerAdapter.getItemCount()) {
        return innerPosition;
    } else {
        return -1;
    }
}

头部尾部的判断方式

通过以上计算头部尾部的位置,我们是可以得到他们的判断方式的.因为判断头部尾部是给定一个position,判断其是否为头部或者尾部.

//当position在前面的位置且位于headerViewCount的数量范围内,则说明当前位置为头部
private boolean isHeaderView(int position) {
    //计算头部view结束的位置
    int headerEndPosition = getHeaderViewCount();
    return position >= 0 && position < headerEndPosition;
}

尾部的计算方式也是相同的.都是计算当前位置是否在指定的view类型范围内即可.


HeaderRecycleAdapter的接口相关

由于ExtraViewWrapperAdapter是为了兼容HeaderRecycleAdapter,所以实现了与其相同的一些接口,包括:

  • IStickerHeaderDecoration,用于显示固定头部
  • ISpanSizeHandler,用于兼容显示GridLayout的布局方式

由于innerAdapter有自己的接口实现,wrapperAdapter并不能代替其实现的功能,所以只能是通过保存对应的接口实现,并在实现这些接口时(与position有关的方法中)屏蔽headerView及footerView的部分,有关innerAdapter的item部分由其对应的接口自行处理.

大致类似以下的处理方式,此处与两个接口有关,需要了解的请查看HeaderRecycleAdapterStickHeaderItemDecoration两个类的分析文章.

//如 IStickerHeaderDecoration 接口中,判断当前position的item是否有固定头部时
@Override
public boolean hasStickHeader(int position) {
    //当前位置的item为headerView或者footerView,都不存在固定头部
    if (isHeaderView(position) || isFooterView(position)) {
        return false;
    } else if (mIStickHeaderDecoration != null) {
        //若不是,则说明当前位置应该是innerAdapter中的item,计算innerAdapter中对应的位置并回调其相关的接口处理
        return mIStickHeaderDecoration.hasStickHeader(getInnerAdapterPosition(position));
    } else {
        return false;
    }
}

这里涉及到了原innerAdapter相关的一些接口问题.由于HeaderRecycleAdapter是已经实现了相关的接口,所以可以在保存adapter时保存其实现接口的引用.

public void setInnerAdapter(@NonNull RecyclerView.Adapter innerAdapter) {
    mInnerAdapter = innerAdapter;
    //当前adapter实现了 IStickerHeaderDecoration接口,则保存其接口引用
    if (mInnerAdapter != null && mInnerAdapter instanceof StickHeaderItemDecoration.IStickerHeaderDecoration) {
        mIStickHeaderDecoration = (StickHeaderItemDecoration.IStickerHeaderDecoration) mInnerAdapter;
    }

    //当前adapter实现了 ISpanSizeHandler接口,则保存其接口引用
    if (mInnerAdapter != null && mInnerAdapter instanceof HeaderSpanSizeLookup.ISpanSizeHandler) {
        mISpanSizeLookup = (HeaderSpanSizeLookup.ISpanSizeHandler) mInnerAdapter;
    }
}

通过直接判断adatper是否实现了对应接口直接保存引用,可以更准确地绑定innerAdapter及其相关接口的实现.同时,也存在相关的接口直接设置方法手动绑定当前innerAdapter的实现接口.


其它

ExtraViewWrapperAdapter大致实现的原理如上,主要是为了增加headerView及footerView的添加功能,同时又能兼容原有innerAdapter的功能(主要指HeaderRecycleAdapter).

同时提供了添加多个headerView及footerView的功能,并额外添加了refreshView(刷新View)及loadView(加载View)分别在顶部及底部显示,默认为null不存在.

//设置刷新显示的view
mExtraAdapter.setRefreshingHeaderView(yourRefreshView);
//切换刷新/加载/header/footer显示的状态
ExtraViewWrapAdapter.setRefreshingViewStatus(true,true,rv);

使用方式

使用方式很简单,需要添加headerView或者footerView时,直接进行添加,然后设置原始数据innerAdatper,如果是实现了ISpanSizeHandlerIStcikerHeaderDecoration接口的adapter则不需要作额外处理,否则需要考虑一下是否要设置相关的接口实现.

//设置innearAdapter
mExtraAdapter = new ExtraViewWrapAdapter(mNormalAdapter);
//添加headerView
mExtraAdapter.addHeaderView(R.id.header_view, yourView);
//使用extraViewWrapperAdapter代码原有的adapter作为recycleView的数据绑定
rv.setAdapter(mExtraAdapter);

GitHub地址

https://github.com/CrazyTaro/RecycleViewAdapter

与之前的RecycleView相关的都置于同一个项目中


示例图片

时间: 2024-08-29 19:27:13

ExtraViewWrapperAdapter--添加额外头部尾部功能的装饰adapter的相关文章

Python学习笔记__4.4章 装饰器(添加额外功能)

# 这是学习廖雪峰老师python教程的学习笔记 1.概览 装饰器可以帮助我们为已经存在的对象添加额外的功能 装饰器本质上是一个Python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象. 装饰器经常用于有切面需求的场景,比如:插入日志.性能测试.事物处理.缓存.权限校验等场景. 1.1.为now函数 加一行日志 # 定义now函数 def now(): print('2018-5-8') # 编辑decorator def log(func):

BaseRecyclerAdapter之添加不同布局(头部尾部)

最近写了个Android开源库「BaseRecyclerViewAdapterHelper」集成了很多常见需求的解决方案,希望大家多多star哦~! 效果如何? 如何使用? 多个不同布局 public class MultipleItemAdapter extends BaseQuickAdapter<String> { private final int TEXT_TYPE = 1; private int mTextLayoutResId; public MultipleItemAdapt

无参装饰器为被装饰函数添加统计时间的功能

#需求 定义无参装饰器为被装饰函数添加统计时间的功能 1 import time #导入时间模块 2 3 def timer(func): 4 def wapper(): 5 start = time.time() 6 func() 7 stop = time.time() 8 index_spend_time = stop - start 9 print(index_spend_time) 10 return wapper 11 @timer 12 def index(): 13 time.s

iOS 给系统的对象添加额外的属性----关联属性

@interface NSObject (Objc) // @property (nonatomic, strong) NSString *name; // 在分类中 给系统的类添加属性, 一搬系统的类不能添加额外的属性 @end #import "NSObject+Objc.h" #import <objc/message.h> @implementation NSObject (Objc) //static NSString *_name; -(void)setName

Bootstrap 表单和图片 (内联表单,表单合组,水平排列,复选框和单选框,下拉列表,校验状态,添加额外的图标,控制尺寸,图片)

一.表单 基本格式 注:只有正确设置了输入框的 type 类型,才能被赋予正确的样式. 支持的输入框控件 包括:text.password.datetime.datetime-local.date.month.time.week. number.email.url.search.tel 和 color. <form> <div class="form-group"> <label>电子邮件</label> <input type=&

maven添加额外archetype

用Eclipse + m2e 插件新建maven项目时发现archetype太少了,网上搜索如何添加额外的archetype. http://maven.apache.org/archetype/maven-archetype-plugin/specification/archetype-catalog.html The Archetype Plugin knows by default about its internal catalog. It also knows about the lo

安卓自带下拉刷新SwipeRefreshLayout添加上拉刷新功能

在项目里面要用到刷新库,以前都是使用第三方的,不过看到官方出了  SwipeRefreshLayout之后就用SwipeRefreshLayout,但是不知道什么原因官方SwipeRefreshLayout只提供下拉刷新功能,很多时候我们需要上拉刷新功能,所以下载v4源码修改SwipeRefreshLayout,与之相关联的文件有两个分别是SwipeProgressBar,BakedBezierInterpolator把这三个文件拷贝到项目里面,修改一下包名就可以了.如何实现上拉刷新功能,其

Android高级控件(一)——ListView绑定CheckBox实现全选,添加和删除等功能

Android高级控件(一)--ListView绑定CheckBox实现全选,添加和删除等功能 这个控件还是挺复杂的.也是项目中应该算是比較经常使用的了,所以写了一个小Demo来讲讲,主要是自己定义adapter的使用方法.加了非常多的推断等等等等-.我们先来看看实现的效果吧! 好的,我们新建一个项目LvCheckBox 我们事先先把这两个布局写好吧,一个是主布局,另一个listview的item.xml.相信不用多说 activity_main.xml <LinearLayout xmlns:

Swift - 给表格添加移动单元格功能(拖动行)

1,下面的样例是给表格UITableView添加单元格移动功能: (1)给表格添加长按功能,长按后表格进入编辑状态 (2)在编辑状态下,可以看到单元格后面出现拖动按钮 (3)鼠标按住拖动按钮,可以拖动单元格到任意位置 (4)拖动完毕后,还会触发TabelView对应的代理事件 2,效果图如下:   3,代码如下 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34