UI-RecyclerView

RecyclerView 优化

  • 数据处理和视图加载分离:数据的处理逻辑尽可能放在异步处理,onBindViewHolder 方法中只处理数据填充到视图中。
  • 数据优化:分页拉取远端数据,对拉取下来的远端数据进行缓存,提升二次加载速度;对于新增或者删除数据通过 DiffUtil 来进行局部刷新数据,而不是一味地全局刷新数据。

示例

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
public class AdapterDiffCallback extends DiffUtil.Callback {

private List<String> mOldList;
private List<String> mNewList;

public AdapterDiffCallback(List<String> oldList, List<String> newList) {
mOldList = oldList;
mNewList = newList;
DiffUtil.DiffResult
}

@Override
public int getOldListSize() {
return mOldList.size();
}

@Override
public int getNewListSize() {
return mNewList.size();
}

@Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
return mOldList.get(oldItemPosition).getClass().equals(mNewList.get(newItemPosition).getClass());
}

@Override
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
return mOldList.get(oldItemPosition).equals(mNewList.get(newItemPosition));
}
}
1
2
DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new AdapterDiffCallback(oldList, newList));
diffResult.dispatchUpdatesTo(mAdapter);
  • DiffUtil.Callback
  • 布局优化:减少布局层级,简化 ItemView
  • 升级 RecycleView 版本到 25.1.0 及以上使用 Prefetch 功能
  • 通过重写 RecyclerView.onViewRecycled(holder) 来回收资源
  • 如果 Item 高度是固定的话,可以使用 RecyclerView.setHasFixedSize(true); 来避免 requestLayout 浪费资源【不是瀑布流的情况下可以加这句】
  • ItemView 设置监听器,不要对每个 Item 都调用 addXxListener,应该大家公用一个 XxListener,根据 ID 来进行不同的操作,优化了对象的频繁创建带来的资源消耗
  • 如果多个 RecycledViewAdapter 是一样的,比如嵌套的 RecyclerView 中存在一样的 Adapter,可以通过设置 RecyclerView.setRecycledViewPool(pool),来共用一个 RecycledViewPool

二级列表

在布局文件中添加 ExpandableAdapter,或在 Activityextends ExpandableAdapter
Activityoncreate

1
2
//将默认的左边的箭头去掉
expandLview.setGroupIndicator(null);
  1. 创建一个类继承 BaseExpandableListAdapter
  2. 先重写 getGroupCountgetGroupView 再写 getChildViewgetChildrenCount
  3. activity中创建适配器对象,并且设置适配器

思路:
继承 activity

  1. 创建两 xml布局
    布局1:有 ExpandableListView控件
    布局2:子列表的样式
  2. activityoncreate方法中实例化 ExpandableListView控件
  3. 自定义可扩展适配器,创建一个类继承 BaseExpandableListAdapter并添加为实现的方法
  4. 必须重写的方法:先重写 getGroupCountgetGroupView再写 getChildViewgetChildrenCount
  5. onCreate方法中实例化可扩展适配器对象
  6. 调用 setadapter设置适配器

注释:如果我们在自定义适配器时,注意是否需要加载元素样式布局(如果需要加载,这个时候必须获取布局过滤器对象)–>通常情况处理方式为:添加一个构造方法,里面的参数只收有 context c在获取 view,必须通过布局过滤器对象调用 inflate方法,加载样式布局 返回 view对象,记得赋值

如果继承 listactivity。。。

可扩展适配器实现步骤

  1. 继承activity
  2. 创建两xml布局
    布局1:有ExpandableListView控件
    布局2:子列表元素的样式
  3. activityoncreate方法中实例化ExpandableListView控件
  4. 自定义可扩展适配器,继承BaseExpandableListAdapter并添加未实现的方法
  5. 必须重写getGroupViewgetGroupCountgetChildViewgetChildrenCount
  6. oncreate方法中实例化自定义可扩展适配器对象
  7. 调用setadapter设置适配器

注释:如果我们在自定义适配器时,注意是否需要加载元素样式布局(如果需要加载,这个时候必须获取布局过滤器对象)—>通常情况处理方式为:添加一个构造方法,里面的参数至少有Context c 在获取view,必须通过布局过滤器对象调用inflate方法,加载样式布局 返回View对象,记得赋值

RecyclerView

RecyclerView整体架构设计

Android/RecyclerView1

RecyclerView类注释中对自己的解释:它是一种可以在有限区域内展示大量数据的View

  • RecyclerView本质是ViewGroup
  • AdapterLayoutManagerRecycler这三个对RecyclerView是必不可少的
    • Recycler的工作被封装在RecyclerView内部完成,对使用者来说是透明的。
    • 谷歌提供了集中LayoutManager,实现单列列表、瀑布流列表等效果

RecyclerView的重要成员

ViewHolder

RecyclerView的基本单位:ViewHolder

如果用Item表示数据集中的一个数据对象,ItemView表示用来展示该ItemView对象。ViewHolder负责在RecyclerView中承载一个ItemView,除了维护View对象本身外,还维护着Item位置(position,此处的位置是指Item数据集中的次序)、item类型、item Id等。大部分时候,RecyclerView内部或其辅助类不会直接操作View,而是操作ViewHolder

在阅读RecyclerView源码时发现,一些操作需要用View做参数,也有一些操作需要用ViewHolder做参数,实际上在RecyclerView中,可以通过任意一样拿到另一样,不必太过纠结RecyclerView的各个内部类的变量中究竟维护的是哪种类型。

Android/RecyclerView2

但一般认为,

  • Adapter负责根据数据Item创建对应的ViewHolder;
  • Recycler负责管理ViewHolder,根据实际情况创建新的ViewHolder或复用已有的ViewHolder;
  • LayoutManager可以通过Recycler直接获取到View,负责讲其添加到RecyclerView的布局中,并通过Recycler回收已经不被展示的View。

Android/RecyclerView3

画了10张图,带你搞定RecyclerView的缓存复用机制

RecyclerView:回收其列表项视图以供重用。具体:当一个列表项被移出屏幕后,RecyclerView并不会销毁其视图,而是会缓存起来,以提供给新进入屏幕的列表项重用,这种重用可以:

  • 避免重复创建不必要的视图。
  • 避免重复执行昂贵的findViewById

    RecyclerView是如何构建动态列表的?

    与RecyclerView构建动态列表相关联的几个重要类中,Adapter与ViewHolder负责配合使用,共同定义RecyclerView列表项数据的展示方式,其中:
  • ViewHolder是一个「包含列表项视图(itemView)的封装容器」,同时也是「RecyclerView缓存复用的主要对象」。
  • Adapter则提供了「数据<->视图」 的“绑定”关系,其包含以下几个关键方法:
    • onCreateViewHolder:负责创建并初始化ViewHolder及其关联的视图,但不会填充视图内容。
    • onBindViewHolder:负责提取适当的数据,填充ViewHolder的视图内容。
      然而,这2个方法并非每一个进入屏幕的列表项都会回调,相反,由于视图创建及findViewById执行等动作都主要集中在这2个方法,每次都要回调的话反而效率不佳。因此,我们应该通过对ViewHolder对象积极地缓存复用,来尽量减少对这2个方法的回调频次。
  1. 最优情况是——取得的缓存对象正好是原先的ViewHolder对象,这种情况下既不需要重新创建该对象,也不需要重新绑定数据,即拿即用。
  2. 次优情况是——取得的缓存对象虽然不是原先的ViewHolder对象,但由于二者的列表项类型(itemType)相同,其关联的视图可以复用,因此只需要重新绑定数据即可。
  3. 最后实在没办法了,才需要执行这2个方法的回调,即创建新的ViewHolder对象并绑定数据。
    实际上,这也是RecyclerView从缓存中查找最佳匹配ViewHolder对象时所遵循的优先级顺序。而真正负责执行这项查找工作的,则是RecyclerView类中一个被称为「回收者」的内部类——Recycler。

    Recycler是如何查找ViewHolder对象的?

    fold
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    /**  
     * ...
     * When {@link Recycler#getViewForPosition(int)} is called, Recycler checks attached scrap and
     * first level cache to find a matching View. If it cannot find a suitable View, Recycler will
     * call the {@link #getViewForPositionAndType(Recycler, int, int)} before checking
     * {@link RecycledViewPool}.
     * 
     * 当调用getViewForPosition(int)方法时,Recycler会检查attached scrap和一级缓存(指的是mCachedViews)以找到匹配的View。 
     * 如果找不到合适的View,Recycler会先调用ViewCacheExtension的getViewForPositionAndType(RecyclerView.Recycler, int, int)方法,再检查RecycledViewPool对象。
     * ...
     */
    public abstract static class ViewCacheExtension {
    ...
    }
fold
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
public final class Recycler {  
    ...
    /**
     * Attempts to get the ViewHolder for the given position, either from the Recycler scrap,
     * cache, the RecycledViewPool, or creating it directly.
     * 
     * 尝试通过从Recycler scrap缓存、RecycledViewPool查找或直接创建的形式来获取指定位置的ViewHolder。
     * ...
     */
    @Nullable
    ViewHolder tryGetViewHolderForPositionByDeadline(int position,
            boolean dryRun, long deadlineNs) {
        if (mState.isPreLayout()) {
            // 0 尝试从mChangedScrap中获取ViewHolder对象
            holder = getChangedScrapViewForPosition(position);
            ...
        }
        if (holder == null) {
            // 1.1 尝试根据position从mAttachedScrap或mCachedViews中获取ViewHolder对象
            holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
            ...
        }
        if (holder == null) {
            ...
            final int type = mAdapter.getItemViewType(offsetPosition);
            if (mAdapter.hasStableIds()) {
                // 1.2 尝试根据id从mAttachedScrap或mCachedViews中获取ViewHolder对象
                holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
                        type, dryRun);
                ...
            }
            if (holder == null && mViewCacheExtension != null) {
                // 2 尝试从mViewCacheExtension中获取ViewHolder对象
                final View view = mViewCacheExtension
                        .getViewForPositionAndType(this, position, type);
                if (view != null) {
                    holder = getChildViewHolder(view);
                    ...
                }
            }
            if (holder == null) { // fallback to pool
                // 3 尝试从mRecycledViewPool中获取ViewHolder对象
                holder = getRecycledViewPool().getRecycledView(type);
                ...
            }
            if (holder == null) {
                // 4.1 回调createViewHolder方法创建ViewHolder对象及其关联的视图
                holder = mAdapter.createViewHolder(RecyclerView.this, type);
                ...
            }
        }

        if (mState.isPreLayout() && holder.isBound()) {
            ...
        } else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
            ...
            // 4.1 回调bindViewHolder方法提取数据填充ViewHolder的视图内容
            bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
        }

        ...

        return holder;
    }
    ...
}

结合RecyclerView类中的源码及注释可知,Recycler会依次从mChangedScrap/mAttachedScrapmCachedViewsmViewCacheExtensionmRecyclerPool中尝试获取指定位置或ID的ViewHolder对象以供重用,如果全都获取不到则直接重新创建。这其中涉及的几层缓存结构分别是:

mChangedScrap/mAttachedScrap

mChangedScrap/mAttachedScrap主要用于「临时存放仍在当前屏幕可见、但被标记为「移除」或「重用」的列表项」,其均以ArrayList的形式持有着每个列表项的ViewHolder对象,大小无明确限制,但一般来讲,其最大数就是屏幕内总的可见列表项数。

fold
1
2
final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();  
ArrayList<ViewHolder> mChangedScrap = null;

但问题来了,既然是当前屏幕可见的列表项,为什么还需要缓存呢?又是什么时候列表项会被标记为「移除」或「重用」的呢?
这2个缓存结构实际上更多是为了避免出现像「局部刷新」这一类的操作,导致所有的列表项都需要重绘的情形。
区别在于,mChangedScrap主要的使用场景是:

  1. 开启了列表项动画(itemAnimator),并且列表项动画的canReuseUpdatedViewHolder(ViewHolder viewHolder)方法返回false的前提下;
  2. 调用了notifyItemChangednotifyItemRangeChanged这一类方法,通知列表项数据发生变化;
    fold
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    boolean canReuseUpdatedViewHolder(ViewHolder viewHolder) {  
        return mItemAnimator =null || mItemAnimator.canReuseUpdatedViewHolder(viewHolder,
                viewHolder.getUnmodifiedPayloads());
    }

    public boolean canReuseUpdatedViewHolder(@NonNull ViewHolder viewHolder,
            @NonNull List<Object> payloads) {
        return canReuseUpdatedViewHolder(viewHolder);
    }

    public boolean canReuseUpdatedViewHolder(@NonNull ViewHolder viewHolder) {
        return true;
    }
    canReuseUpdatedViewHolder方法的返回值表示的不同含义如下:
  • true,表示可以重用原先的ViewHolder对象。
  • false,表示应该创建该ViewHolder的副本,以便itemAnimator利用两者来实现动画效果(例如交叉淡入淡出效果)。
    简单讲就是,mChangedScrap主要是为列表项数据发生变化时的动画效果服务的
    mAttachedScrap应对的则是剩下的绝大部分场景,比如:
  • notifyItemMovednotifyItemRemoved这种列表项发生移动,但列表项数据本身没有发生变化的场景。
  • 关闭了列表项动画,或者列表项动画的canReuseUpdatedViewHolder方法返回true,即允许重用原先的ViewHolder对象的场景。
    下面以一个简单的notifyItemRemoved(int position)操作为例来演示:
    notifyItemRemoved(int position)方法用于通知观察者,先前位于position的列表项已被移除, 其往后的列表项position都将往前移动1位。
    为了简化问题、方便演示,我们的范例将会居于以下限制:
  • 列表项总个数没有铺满整个屏幕——意味着不会触发mCachedViewsmRecyclerPool等结构的缓存操作。
  • 去除列表项动画——意味着调用notifyItemRemoved后RecyclerView只会重新布局子视图一次。
    fold
    1
    recyclerView.itemAnimator = null
    理想情况下,调用notifyItemRemoved(int position)方法后,应只有位于position的列表项会被移除,其他的列表项,无论是位于position之前或之后,都最多只会调整position值,而不应发生视图的重新创建或数据的重新绑定,即不应该回调onCreateViewHolderonBindViewHolder这2个方法。
    为此,我们就需要将当前屏幕内的可见列表项暂时从当前屏幕剥离,临时缓存到mAttachedScrap这个结构中去。
    当前屏幕内的可见列表项暂时从当前屏幕剥离临时缓存到mAttachedScrap
    等到RecyclerView重新开始布局显示其子视图后,再遍历mAttachedScrap找到对应position的ViewHolder对象进行复用。
    再遍历mAttachedScrap找到对应position的ViewHolder对象

    mCachedViews

    mCachedViews主要用于「存放已被移出屏幕、但有可能很快重新进入屏幕的列表项」。其同样是以ArrayList的形式持有着每个列表项的ViewHolder对象,默认大小限制为2。
fold
1
2
3
final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();  
int mViewCacheMax = DEFAULT_CACHE_SIZE;
static final int DEFAULT_CACHE_SIZE = 2;

比如像朋友圈这种按更新时间的先后顺序展示的Feed流,我们经常会在快速滑动中确定是否有自己感兴趣的内容,当意识到刚才滑走的内容可能比较有趣时,我们往往就会将上一条内容重新滑回来查看。
这种场景下我们追求的自然是上一条内容展示的实时性与完整性,而不应让用户产生“才滑走那么一会儿又要重新加载”的抱怨,也即同样不应发生视图的重新创建或数据的重新绑定。
我们用几张流程示意图来演示这种情况:
同样为了简化问题、方便描述,我们的范例将会居于以下限制:

  • 关闭预拉取——意味着之后向上滑动时,都不会再预拉取「待进入屏幕区域」的一个列表项放入mCachedView了。
    fold
    1
    recyclerView.layoutManager?.isItemPrefetchEnabled = false
  • 只存在一种类型的列表项,即所有列表项的itemType相同,默认都为0。
    我们将图中的列表项分成了3块区域,分别是被滑出屏幕之外的区域、屏幕内的可见区域、随着滑动手势待进入屏幕的区域。
    将图中的列表项分成了3块区域
  1. 当position=0的列表项随着向上滑动的手势被移出屏幕后,由于mCachedViews初始容量为0,因此可直接放入;当前屏幕内的可见列表项暂时从当前屏幕剥离临时缓存到mAttachedScrap1
  2. 当position=1的列表项同样被移出屏幕后,由于未达到mCachedViews的默认容量大小限制,因此也可继续放入;当前屏幕内的可见列表项暂时从当前屏幕剥离临时缓存到mAttachedScrap2
  3. 此时改为向下滑动,position=1的列表项重新进入屏幕,Recycler就会依次从mAttachedScrapmCachedViews查找可重用于此位置的ViewHolder对象;
  4. mAttachedScrap不是应对这种情况的,自然找不到。而mCachedViews会遍历自身持有的ViewHolder对象,对比ViewHolder对象的position值与待复用位置的position值是否一致,是的话就会将ViewHolder对象从mCachedViews中移除并返回;
  5. 此处拿到的ViewHolder对象即可直接复用,即符合前面所述的「最优情况」。当前屏幕内的可见列表项暂时从当前屏幕剥离临时缓存到mAttachedScrap3
  6. 另外,随着position=1的列表项重新进入屏幕,position=7的列表项也会被移出屏幕,该位置的列表项同样会进入mCachedViews,即RecyclerView是双向缓存的。当前屏幕内的可见列表项暂时从当前屏幕剥离临时缓存到mAttachedScrap4

    mViewCacheExtension

    mViewCacheExtension主要用于提供额外的、可由开发人员自由控制的缓存层级,属于非常规使用的情况,因此这里暂不展开讲。

    mRecyclerPool

    mRecyclerPool主要用于「按不同的itemType分别存放超出mCachedViews限制的、被移出屏幕的列表项」,其会先以SparseArray区分不同的itemType,然后每种itemType对应的值又以ArrayList的形式持有着每个列表项的ViewHolder对象,每种itemType的ArrayList大小限制默认为5
    fold
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public static class RecycledViewPool {  
        private static final int DEFAULT_MAX_SCRAP = 5;
        static class ScrapData {
            final ArrayList<ViewHolder> mScrapHeap = new ArrayList<>();
            int mMaxScrap = DEFAULT_MAX_SCRAP;
            long mCreateRunningAverageNs = 0;
            long mBindRunningAverageNs = 0;
        }
        SparseArray<ScrapData> mScrap = new SparseArray<>();
        ...
    }
    由于mCachedViews默认的大小限制仅为2,因此,当滑出屏幕的列表项超过2个后,就会按照先进先出的顺序,依次将ViewHolder对象从mCachedViews移出,并按itemType放入RecycledViewPool中的不同ArrayList。
    这种缓存结构主要考虑的是随着被滑出屏幕列表项的增多,以及被滑出距离的越来越远,重新进入屏幕内的可能性也随之降低。于是Recycler就在时间与空间上做了一个权衡,允许相同itemType的ViewHolder被提取复用,只需要重新绑定数据即可。
    这样一来,既可以避免无限增长的ViewHolder对象缓存挤占了原本就紧张的内存空间,又可以减少回调相比较之下执行代价更加昂贵的onCreateViewHolder方法。
    同样我们用几张流程示意图来演示这种情况,这些示意图将在前面的mCachedViews示意图基础上继续操作:
  7. 假设目前存在于mCachedViews中的仍是position=0及position=1这两个列表项。
  8. 当我们继续向上滑动时,position=2的列表项会尝试进入mCachedViews,由于超出了mCachedViews的容量限制,position=0的列表项会从mCachedViews中被移出,并放入RecycledViewPool中itemType为0的ArrayList,即图中的情况①。mRecyclerPool1
  9. 同时,底部的一个新的列表项也将随着滑动手势进入到屏幕内,但由于此时mAttachedScrapmCachedViewsmRecyclerPool均没有合适的ViewHolder对象可以提供给其复用,因此该列表项只能执行onCreateViewHolderonBindViewHolder这2个方法的回调,即图中的情况②。
  10. 等到position=2的列表项被完全移出了屏幕后,也就顺利进入了mCachedViews中。mRecyclerPool2
  11. 我们继续保持向上滑动的手势,此时,由于下一个待进入屏幕的列表项与position=0的列表项的itemType相同,因此我们可以在走到从mRecyclerPool查找合适的ViewHolder对象这一步时,根据itemType找到对应的ArrayList,再取出其中的1个ViewHolder对象进行复用,即图中的情况①。
  12. 由于itemType类型一致,其关联的视图可以复用,因此只需要重新绑定数据即可,即符合前面所述的「次优情况」。mRecyclerPool3
  13. ②③ 情况与前面的一致,此处不再赘余。

    总结

    RecyclerView缓存复用机制
    对象 ViewHolder(包含列表项视图(itemView)的封装容器)
    目的 减少对onCreateViewHolderonBindViewHolder这2个方法的回调
    好处 1. 避免重复创建不必要的视图
    2. 避免重复执行昂贵的findViewById
    效果 改善性能 、提升应用响应能力、降低功耗
    核心类 RecyclerRecyclerViewPool
    缓存结构 mChangedScrap/mAttachedScrapmCachedViewsmViewCacheExtensionmRecyclerPool
缓存结构 容器类型 容量限制 缓存用途 优先级顺序
(数值越小,
优先级越高)
mChangedScrap/mAttachedScrap ArrayList 无,一般为屏幕内总的可见列表项数 临时存放仍在当前屏幕可见、但被标记为「移除」或「重用」的列表项 0
mCachedViews ArrayList 默认为2 存放已被移出屏幕、但有可能很快重新进入屏幕的列表项 1
mViewCacheExtension 开发者自己定义 提供额外的可由开发人员自由控制的缓存层级 2
mRecyclerPool SparseArray<ArrayList> 每种ItemType默认为5 按不同itemType分别存放超出mCachedViews限制的、被移出屏幕的列表项 3

代码:baseadapter-recyclerview

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
├── Common
│ └── src
│ └── main
│ └── java
│ └── utils
│ └── adapter.rvadapter
│ ── CommonRvAdapter.java
│ ── MultiItemTypeAdapter.java
│ └── base
│ ── ItemViewDelegate.java
│ ── ItemViewDelegateManager.java
│ ── ViewHolder.java
│ └── utils
│ ── WrapperUtils.java
│ └── wrapper
│ ── EmptyWrapper.java
│ ── HeaderAndFooterWrapper.java
│ ── LoadMoreWrapper.java

具体工具类代码

CommonRvAdapter.java

1
package com.xm597.common.utils.adapter.rvadapter;import android.content.Context;import android.view.LayoutInflater;import com.xm597.common.utils.adapter.rvadapter.base.ItemViewDelegate;import com.xm597.common.utils.adapter.rvadapter.base.ViewHolder;import java.util.ArrayList;import java.util.List;public abstract class CommonRvAdapter<T> extends MultiItemTypeAdapter<T> {    protected Context mContext;    protected int mLayoutId;    protected List<T> mDatas;    protected LayoutInflater mInflater;    public CommonRvAdapter(final Context context, final int layoutId) {        super(context);        mContext = context;        mInflater = LayoutInflater.from(context);        mLayoutId = layoutId;        addItemViewDelegate(new ItemViewDelegate<T>() {            @Override            public int getItemViewLayoutId() {                return layoutId;            }            @Override            public boolean isForViewType(T item, int position) {                return true;            }            @Override            public void convert(ViewHolder holder, T t, int position) {                CommonRvAdapter.this.convert(holder, t, position);            }        });    }    @Override    public void setDatas(List<T> datas) {        if (datas == null) {            datas = new ArrayList<>();        }        mDatas = datas;        super.setDatas(mDatas);    }    protected abstract void convert(ViewHolder holder, T t, int position);}

MultiItemTypeAdapter.java

1
package com.xm597.common.utils.adapter.rvadapter;import android.content.Context;import android.view.View;import android.view.ViewGroup;import androidx.recyclerview.widget.RecyclerView;import com.xm597.common.utils.adapter.rvadapter.base.ItemViewDelegate;import com.xm597.common.utils.adapter.rvadapter.base.ItemViewDelegateManager;import com.xm597.common.utils.adapter.rvadapter.base.ViewHolder;import java.util.ArrayList;import java.util.List;public class MultiItemTypeAdapter<T> extends RecyclerView.Adapter<ViewHolder> {    protected Context mContext;    protected List<T> mDatas;    protected ItemViewDelegateManager mItemViewDelegateManager;    protected OnItemClickListener mOnItemClickListener;    public MultiItemTypeAdapter(Context context) {        mContext = context;        mItemViewDelegateManager = new ItemViewDelegateManager();    }    public void setDatas(List<T> datas) {        if (datas == null) {            datas = new ArrayList<>();        }        mDatas = datas;    }    @Override    public int getItemViewType(int position) {        if (!useItemViewDelegateManager()) {            return super.getItemViewType(position);        }        return mItemViewDelegateManager.getItemViewType(mDatas.get(position), position);    }    @Override    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {        ItemViewDelegate itemViewDelegate = mItemViewDelegateManager.getItemViewDelegate(viewType);        int layoutId = itemViewDelegate.getItemViewLayoutId();        ViewHolder holder = ViewHolder.createViewHolder(mContext, parent, layoutId);        onViewHolderCreated(holder, holder.getConvertView());        setListener(parent, holder, viewType);        return holder;    }    public void onViewHolderCreated(ViewHolder holder, View itemView) {    }    public void convert(ViewHolder holder, T t) {        mItemViewDelegateManager.convert(holder, t, holder.getAdapterPosition());    }    protected boolean isEnabled(int viewType) {        return true;    }    protected void setListener(final ViewGroup parent, final ViewHolder viewHolder, int viewType) {        if (!isEnabled(viewType)) {            return;        }        viewHolder.getConvertView().setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                if (mOnItemClickListener != null) {                    int position = viewHolder.getAdapterPosition();                    mOnItemClickListener.onItemClick(v, viewHolder, position);                }            }        });        viewHolder.getConvertView().setOnLongClickListener(new View.OnLongClickListener() {            @Override            public boolean onLongClick(View v) {                if (mOnItemClickListener != null) {                    int position = viewHolder.getAdapterPosition();                    return mOnItemClickListener.onItemLongClick(v, viewHolder, position);                }                return false;            }        });    }    @Override    public void onBindViewHolder(ViewHolder holder, int position) {        convert(holder, mDatas.get(position));    }    @Override    public int getItemCount() {        int itemCount = mDatas.size();        return itemCount;    }    public List<T> getDatas() {        return mDatas;    }    public MultiItemTypeAdapter addItemViewDelegate(ItemViewDelegate<T> itemViewDelegate) {        mItemViewDelegateManager.addDelegate(itemViewDelegate);        return this;    }    public MultiItemTypeAdapter addItemViewDelegate(int viewType, ItemViewDelegate<T> itemViewDelegate) {        mItemViewDelegateManager.addDelegate(viewType, itemViewDelegate);        return this;    }    protected boolean useItemViewDelegateManager() {        return mItemViewDelegateManager.getItemViewDelegateCount() > 0;    }    public interface OnItemClickListener {        void onItemClick(View view, RecyclerView.ViewHolder holder, int position);        boolean onItemLongClick(View view, RecyclerView.ViewHolder holder, int position);    }    public void setOnItemClickListener(OnItemClickListener onItemClickListener) {        this.mOnItemClickListener = onItemClickListener;    }}

ItemViewDelegate.java

1
package com.xm597.common.utils.adapter.rvadapter.base;public interface ItemViewDelegate<T> {    int getItemViewLayoutId();    boolean isForViewType(T item, int position);    void convert(ViewHolder holder, T t, int position);}

ItemViewDelegateManager.java

1
package com.xm597.common.utils.adapter.rvadapter.base;import androidx.collection.SparseArrayCompat;public class ItemViewDelegateManager<T> {    SparseArrayCompat<ItemViewDelegate<T>> delegates = new SparseArrayCompat();    public int getItemViewDelegateCount() {        return delegates.size();    }    public ItemViewDelegateManager<T> addDelegate(ItemViewDelegate<T> delegate) {        int viewType = delegates.size();        if (delegate != null) {            delegates.put(viewType, delegate);            viewType++;        }        return this;    }    public ItemViewDelegateManager<T> addDelegate(int viewType, ItemViewDelegate<T> delegate) {        if (delegates.get(viewType) != null) {            throw new IllegalArgumentException(                    "An ItemViewDelegate is already registered for the viewType = "                            + viewType                            + ". Already registered ItemViewDelegate is "                            + delegates.get(viewType));        }        delegates.put(viewType, delegate);        return this;    }    public ItemViewDelegateManager<T> removeDelegate(ItemViewDelegate<T> delegate) {        if (delegate == null) {            throw new NullPointerException("ItemViewDelegate is null");        }        int indexToRemove = delegates.indexOfValue(delegate);        if (indexToRemove >= 0) {            delegates.removeAt(indexToRemove);        }        return this;    }    public ItemViewDelegateManager<T> removeDelegate(int itemType) {        int indexToRemove = delegates.indexOfKey(itemType);        if (indexToRemove >= 0) {            delegates.removeAt(indexToRemove);        }        return this;    }    public int getItemViewType(T item, int position) {        int delegatesCount = delegates.size();        for (int i = delegatesCount - 1; i >= 0; i--) {            ItemViewDelegate<T> delegate = delegates.valueAt(i);            if (delegate.isForViewType(item, position)) {                return delegates.keyAt(i);            }        }        throw new IllegalArgumentException(                "No ItemViewDelegate added that matches position=" + position + " in data source");    }    public void convert(ViewHolder holder, T item, int position) {        int delegatesCount = delegates.size();        for (int i = 0; i < delegatesCount; i++) {            ItemViewDelegate<T> delegate = delegates.valueAt(i);            if (delegate.isForViewType(item, position)) {                delegate.convert(holder, item, position);                return;            }        }        throw new IllegalArgumentException(                "No ItemViewDelegateManager added that matches position=" + position + " in data source");    }    public ItemViewDelegate getItemViewDelegate(int viewType) {        return delegates.get(viewType);    }    public int getItemViewLayoutId(int viewType) {        return getItemViewDelegate(viewType).getItemViewLayoutId();    }    public int getItemViewType(ItemViewDelegate itemViewDelegate) {        return delegates.indexOfValue(itemViewDelegate);    }}

ViewHolder.java

1
package com.xm597.common.utils.adapter.rvadapter.base;import android.annotation.SuppressLint;import android.content.Context;import android.graphics.Bitmap;import android.graphics.Paint;import android.graphics.Typeface;import android.graphics.drawable.Drawable;import android.os.Build;import android.text.util.Linkify;import android.util.SparseArray;import android.view.LayoutInflater;import android.view.View;import android.view.ViewGroup;import android.view.animation.AlphaAnimation;import android.widget.Checkable;import android.widget.ImageView;import android.widget.ProgressBar;import android.widget.RatingBar;import android.widget.TextView;import androidx.recyclerview.widget.RecyclerView;public class ViewHolder extends RecyclerView.ViewHolder {    private SparseArray<View> mViews;    private View mConvertView;    private Context mContext;    public ViewHolder(Context context, View itemView) {        super(itemView);        mContext = context;        mConvertView = itemView;        mViews = new SparseArray<View>();    }    public static ViewHolder createViewHolder(Context context, View itemView) {        ViewHolder holder = new ViewHolder(context, itemView);        return holder;    }    public static ViewHolder createViewHolder(Context context,                                              ViewGroup parent, int layoutId) {        View itemView = LayoutInflater.from(context).inflate(layoutId, parent,                false);        ViewHolder holder = new ViewHolder(context, itemView);        return holder;    }    /**     * 通过viewId获取控件     *     * @param viewId     * @return     */    public <T extends View> T getView(int viewId) {        View view = mViews.get(viewId);        if (view == null) {            view = mConvertView.findViewById(viewId);            mViews.put(viewId, view);        }        return (T) view;    }    public View getConvertView() {        return mConvertView;    }    /****以下为辅助方法*****/    /**     * 设置TextView的值     *     * @param viewId     * @param text     * @return     */    public ViewHolder setText(int viewId, String text) {        TextView tv = getView(viewId);        tv.setText(text);        return this;    }    public ViewHolder setImageResource(int viewId, int resId) {        ImageView view = getView(viewId);        view.setImageResource(resId);        return this;    }    public ViewHolder setImageBitmap(int viewId, Bitmap bitmap) {        ImageView view = getView(viewId);        view.setImageBitmap(bitmap);        return this;    }    public ViewHolder setImageDrawable(int viewId, Drawable drawable) {        ImageView view = getView(viewId);        view.setImageDrawable(drawable);        return this;    }    public ViewHolder setBackgroundColor(int viewId, int color) {        View view = getView(viewId);        view.setBackgroundColor(color);        return this;    }    public ViewHolder setBackgroundRes(int viewId, int backgroundRes) {        View view = getView(viewId);        view.setBackgroundResource(backgroundRes);        return this;    }    public ViewHolder setTextColor(int viewId, int textColor) {        TextView view = getView(viewId);        view.setTextColor(textColor);        return this;    }    public ViewHolder setTextColorRes(int viewId, int textColorRes) {        TextView view = getView(viewId);        view.setTextColor(mContext.getResources().getColor(textColorRes));        return this;    }    @SuppressLint("NewApi")    public ViewHolder setAlpha(int viewId, float value) {        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {            getView(viewId).setAlpha(value);        } else {            // Pre-honeycomb hack to set Alpha value            AlphaAnimation alpha = new AlphaAnimation(value, value);            alpha.setDuration(0);            alpha.setFillAfter(true);            getView(viewId).startAnimation(alpha);        }        return this;    }    public ViewHolder setVisible(int viewId, boolean visible) {        View view = getView(viewId);        view.setVisibility(visible ? View.VISIBLE : View.GONE);        return this;    }    public ViewHolder linkify(int viewId) {        TextView view = getView(viewId);        Linkify.addLinks(view, Linkify.ALL);        return this;    }    public ViewHolder setTypeface(Typeface typeface, int... viewIds) {        for (int viewId : viewIds) {            TextView view = getView(viewId);            view.setTypeface(typeface);            view.setPaintFlags(view.getPaintFlags() | Paint.SUBPIXEL_TEXT_FLAG);        }        return this;    }    public ViewHolder setProgress(int viewId, int progress) {        ProgressBar view = getView(viewId);        view.setProgress(progress);        return this;    }    public ViewHolder setProgress(int viewId, int progress, int max) {        ProgressBar view = getView(viewId);        view.setMax(max);        view.setProgress(progress);        return this;    }    public ViewHolder setMax(int viewId, int max) {        ProgressBar view = getView(viewId);        view.setMax(max);        return this;    }    public ViewHolder setRating(int viewId, float rating) {        RatingBar view = getView(viewId);        view.setRating(rating);        return this;    }    public ViewHolder setRating(int viewId, float rating, int max) {        RatingBar view = getView(viewId);        view.setMax(max);        view.setRating(rating);        return this;    }    public ViewHolder setTag(int viewId, Object tag) {        View view = getView(viewId);        view.setTag(tag);        return this;    }    public ViewHolder setTag(int viewId, int key, Object tag) {        View view = getView(viewId);        view.setTag(key, tag);        return this;    }    public ViewHolder setChecked(int viewId, boolean checked) {        Checkable view = (Checkable) getView(viewId);        view.setChecked(checked);        return this;    }    /**     * 关于事件的     */    public ViewHolder setOnClickListener(int viewId,                                         View.OnClickListener listener) {        View view = getView(viewId);        view.setOnClickListener(listener);        return this;    }    public ViewHolder setOnTouchListener(int viewId,                                         View.OnTouchListener listener) {        View view = getView(viewId);        view.setOnTouchListener(listener);        return this;    }    public ViewHolder setOnLongClickListener(int viewId,                                             View.OnLongClickListener listener) {        View view = getView(viewId);        view.setOnLongClickListener(listener);        return this;    }}

WrapperUtils.java

1
package com.xm597.common.utils.adapter.rvadapter.utils;import android.view.ViewGroup;import androidx.recyclerview.widget.GridLayoutManager;import androidx.recyclerview.widget.RecyclerView;import androidx.recyclerview.widget.StaggeredGridLayoutManager;public class WrapperUtils {    public interface SpanSizeCallback {        int getSpanSize(GridLayoutManager layoutManager, GridLayoutManager.SpanSizeLookup oldLookup, int position);    }    public static void onAttachedToRecyclerView(RecyclerView.Adapter innerAdapter, RecyclerView recyclerView, final SpanSizeCallback callback) {        innerAdapter.onAttachedToRecyclerView(recyclerView);        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) {                    return callback.getSpanSize(gridLayoutManager, spanSizeLookup, position);                }            });            gridLayoutManager.setSpanCount(gridLayoutManager.getSpanCount());        }    }    public static void setFullSpan(RecyclerView.ViewHolder holder) {        ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();        if (lp != null                && lp instanceof StaggeredGridLayoutManager.LayoutParams) {            StaggeredGridLayoutManager.LayoutParams p = (StaggeredGridLayoutManager.LayoutParams) lp;            p.setFullSpan(true);        }    }}

EmptyWraper.java

1
package com.xm597.common.utils.adapter.rvadapter.wrapper;import android.view.View;import android.view.ViewGroup;import androidx.recyclerview.widget.GridLayoutManager;import androidx.recyclerview.widget.RecyclerView;import com.xm597.common.utils.adapter.rvadapter.base.ViewHolder;import com.xm597.common.utils.adapter.rvadapter.utils.WrapperUtils;public class EmptyWrapper<T> extends RecyclerView.Adapter<RecyclerView.ViewHolder> {    public static final int ITEM_TYPE_EMPTY = Integer.MAX_VALUE - 1;    private RecyclerView.Adapter mInnerAdapter;    private View mEmptyView;    private int mEmptyLayoutId;    public EmptyWrapper(RecyclerView.Adapter adapter) {        mInnerAdapter = adapter;    }    private boolean isEmpty() {        return (mEmptyView != null || mEmptyLayoutId != 0) && mInnerAdapter.getItemCount() == 0;    }    @Override    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {        if (isEmpty()) {            ViewHolder holder;            if (mEmptyView != null) {                holder = ViewHolder.createViewHolder(parent.getContext(), mEmptyView);            } else {                holder = ViewHolder.createViewHolder(parent.getContext(), parent, mEmptyLayoutId);            }            return holder;        }        return mInnerAdapter.onCreateViewHolder(parent, viewType);    }    @Override    public void onAttachedToRecyclerView(RecyclerView recyclerView) {        WrapperUtils.onAttachedToRecyclerView(mInnerAdapter, recyclerView, new WrapperUtils.SpanSizeCallback() {            @Override            public int getSpanSize(GridLayoutManager gridLayoutManager, GridLayoutManager.SpanSizeLookup oldLookup, int position) {                if (isEmpty()) {                    return gridLayoutManager.getSpanCount();                }                if (oldLookup != null) {                    return oldLookup.getSpanSize(position);                }                return 1;            }        });    }    @Override    public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) {        mInnerAdapter.onViewAttachedToWindow(holder);        if (isEmpty()) {            WrapperUtils.setFullSpan(holder);        }    }    @Override    public int getItemViewType(int position) {        if (isEmpty()) {            return ITEM_TYPE_EMPTY;        }        return mInnerAdapter.getItemViewType(position);    }    @Override    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {        if (isEmpty()) {            return;        }        mInnerAdapter.onBindViewHolder(holder, position);    }    @Override    public int getItemCount() {        if (isEmpty()) {            return 1;        }        return mInnerAdapter.getItemCount();    }    public void setEmptyView(View emptyView) {        mEmptyView = emptyView;    }    public void setEmptyView(int layoutId) {        mEmptyLayoutId = layoutId;    }}

HeaderAndFooterWrapper.java

1
package com.xm597.common.utils.adapter.rvadapter.wrapper;import android.view.View;import android.view.ViewGroup;import androidx.collection.SparseArrayCompat;import androidx.recyclerview.widget.GridLayoutManager;import androidx.recyclerview.widget.RecyclerView;import com.xm597.common.utils.adapter.rvadapter.base.ViewHolder;import com.xm597.common.utils.adapter.rvadapter.utils.WrapperUtils;public class HeaderAndFooterWrapper<T> extends RecyclerView.Adapter<RecyclerView.ViewHolder> {    private static final int BASE_ITEM_TYPE_HEADER = 100000;    private static final int BASE_ITEM_TYPE_FOOTER = 200000;    private SparseArrayCompat<View> mHeaderViews = new SparseArrayCompat<>();    private SparseArrayCompat<View> mFootViews = new SparseArrayCompat<>();    private RecyclerView.Adapter mInnerAdapter;    public HeaderAndFooterWrapper(RecyclerView.Adapter adapter) {        mInnerAdapter = adapter;    }    @Override    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {        if (mHeaderViews.get(viewType) != null) {            ViewHolder holder = ViewHolder.createViewHolder(parent.getContext(), mHeaderViews.get(viewType));            return holder;        } else if (mFootViews.get(viewType) != null) {            ViewHolder holder = ViewHolder.createViewHolder(parent.getContext(), mFootViews.get(viewType));            return holder;        }        return mInnerAdapter.onCreateViewHolder(parent, viewType);    }    @Override    public int getItemViewType(int position) {        if (isHeaderViewPos(position)) {            return mHeaderViews.keyAt(position);        } else if (isFooterViewPos(position)) {            return mFootViews.keyAt(position - getHeadersCount() - getRealItemCount());        }        return mInnerAdapter.getItemViewType(position - getHeadersCount());    }    private int getRealItemCount() {        return mInnerAdapter.getItemCount();    }    @Override    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {        if (isHeaderViewPos(position)) {            return;        }        if (isFooterViewPos(position)) {            return;        }        mInnerAdapter.onBindViewHolder(holder, position - getHeadersCount());    }    @Override    public int getItemCount() {        return getHeadersCount() + getFootersCount() + getRealItemCount();    }    @Override    public void onAttachedToRecyclerView(RecyclerView recyclerView) {        WrapperUtils.onAttachedToRecyclerView(mInnerAdapter, recyclerView, new WrapperUtils.SpanSizeCallback() {            @Override            public int getSpanSize(GridLayoutManager layoutManager, GridLayoutManager.SpanSizeLookup oldLookup, int position) {                int viewType = getItemViewType(position);                if (mHeaderViews.get(viewType) != null) {                    return layoutManager.getSpanCount();                } else if (mFootViews.get(viewType) != null) {                    return layoutManager.getSpanCount();                }                if (oldLookup != null) {                    return oldLookup.getSpanSize(position);                }                return 1;            }        });    }    @Override    public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) {        mInnerAdapter.onViewAttachedToWindow(holder);        int position = holder.getLayoutPosition();        if (isHeaderViewPos(position) || isFooterViewPos(position)) {            WrapperUtils.setFullSpan(holder);        }    }    private boolean isHeaderViewPos(int position) {        return position < getHeadersCount();    }    private boolean isFooterViewPos(int position) {        return position >= getHeadersCount() + getRealItemCount();    }    public void addHeaderView(View view) {        mHeaderViews.put(mHeaderViews.size() + BASE_ITEM_TYPE_HEADER, view);    }    public void addFootView(View view) {        mFootViews.put(mFootViews.size() + BASE_ITEM_TYPE_FOOTER, view);    }    public int getHeadersCount() {        return mHeaderViews.size();    }    public int getFootersCount() {        return mFootViews.size();    }}

LoadMoreWrapper.java

1
package com.xm597.common.utils.adapter.rvadapter.wrapper;import android.view.View;import android.view.ViewGroup;import androidx.recyclerview.widget.GridLayoutManager;import androidx.recyclerview.widget.RecyclerView;import androidx.recyclerview.widget.StaggeredGridLayoutManager;import com.xm597.common.utils.adapter.rvadapter.base.ViewHolder;import com.xm597.common.utils.adapter.rvadapter.utils.WrapperUtils;public class LoadMoreWrapper<T> extends RecyclerView.Adapter<RecyclerView.ViewHolder> {    public static final int ITEM_TYPE_LOAD_MORE = Integer.MAX_VALUE - 2;    private RecyclerView.Adapter mInnerAdapter;    private View mLoadMoreView;    private int mLoadMoreLayoutId;    public LoadMoreWrapper(RecyclerView.Adapter adapter) {        mInnerAdapter = adapter;    }    private boolean hasLoadMore() {        return mLoadMoreView != null || mLoadMoreLayoutId != 0;    }    private boolean isShowLoadMore(int position) {        return hasLoadMore() && (position >= mInnerAdapter.getItemCount());    }    @Override    public int getItemViewType(int position) {        if (isShowLoadMore(position)) {            return ITEM_TYPE_LOAD_MORE;        }        return mInnerAdapter.getItemViewType(position);    }    @Override    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {        if (viewType == ITEM_TYPE_LOAD_MORE) {            ViewHolder holder;            if (mLoadMoreView != null) {                holder = ViewHolder.createViewHolder(parent.getContext(), mLoadMoreView);            } else {                holder = ViewHolder.createViewHolder(parent.getContext(), parent, mLoadMoreLayoutId);            }            //对外抛出ViewHolder            if (mGetViewHolderListener != null) {                mGetViewHolderListener.getViewHolder(holder);            }            return holder;        }        return mInnerAdapter.onCreateViewHolder(parent, viewType);    }    @Override    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {        if (isShowLoadMore(position)) {            if (mOnLoadMoreListener != null) {                mOnLoadMoreListener.onLoadMoreRequested();                holder.itemView.setOnClickListener(v ->mOnLoadMoreListener.onLoadMoreViewClick());            }            return;        }        mInnerAdapter.onBindViewHolder(holder, position);    }    @Override    public void onAttachedToRecyclerView(RecyclerView recyclerView) {        WrapperUtils.onAttachedToRecyclerView(mInnerAdapter, recyclerView, new WrapperUtils.SpanSizeCallback() {            @Override            public int getSpanSize(GridLayoutManager layoutManager, GridLayoutManager.SpanSizeLookup oldLookup, int position) {                if (isShowLoadMore(position)) {                    return layoutManager.getSpanCount();                }                if (oldLookup != null) {                    return oldLookup.getSpanSize(position);                }                return 1;            }        });    }    @Override    public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) {        mInnerAdapter.onViewAttachedToWindow(holder);        if (isShowLoadMore(holder.getLayoutPosition())) {            setFullSpan(holder);        }    }    private void setFullSpan(RecyclerView.ViewHolder holder) {        ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();        if (lp != null                && lp instanceof StaggeredGridLayoutManager.LayoutParams) {            StaggeredGridLayoutManager.LayoutParams p = (StaggeredGridLayoutManager.LayoutParams) lp;            p.setFullSpan(true);        }    }    @Override    public int getItemCount() {        return mInnerAdapter.getItemCount() + (hasLoadMore() ? 1 : 0);    }    public interface OnLoadMoreListener {        void onLoadMoreRequested();        //这个是根据使用场景自己添加的,是控件的点击事件        void onLoadMoreViewClick();    }    private OnLoadMoreListener mOnLoadMoreListener;    public LoadMoreWrapper setOnLoadMoreListener(OnLoadMoreListener loadMoreListener) {        if (loadMoreListener != null) {            mOnLoadMoreListener = loadMoreListener;        }        return this;    }    public interface getViewHolderListener{        //对外抛出ViewHolder        void getViewHolder(ViewHolder holder);    }    private getViewHolderListener mGetViewHolderListener;    public void setmGetViewHolderListener(getViewHolderListener mGetViewHolderListener) {        this.mGetViewHolderListener = mGetViewHolderListener;    }        public LoadMoreWrapper setLoadMoreView(View loadMoreView) {        mLoadMoreView = loadMoreView;        return this;    }    public LoadMoreWrapper setLoadMoreView(int layoutId) {        mLoadMoreLayoutId = layoutId;        return this;    }}

使用:给RecyclerView添加footer

背景:SmartRefreshLayout下有RecyclerView,SmartRefreshLayout已经有header、footer若对其进行动态替换footer也是可行的方案。

此处方案是给RecyclerView添加footer

特点:首次就会显示出来,在加载更多的时候也会存在,所以要酌情看需求使用

1
//define member variablesprivate LoadMoreWrapper<PositionSearchAdapter> mLoadMoreWrapper;private View noMoreView;private void initRecyclerView(){    //...    mHomePositionAdapter = new PositionSearchAdapter(getContext());    mLoadMoreWrapper = new LoadMoreWrapper<>(mHomePositionAdapter);    mLoadMoreWrapper.setLoadMoreView(R.layout.view_home_position_no_more);    mLoadMoreWrapper.setOnLoadMoreListener(new LoadMoreWrapper.OnLoadMoreListener(){        @Override        public void onLoadMoreRequested() {        }        @Override        public void onLoadMoreViewClick() {            if (fastClickAvoider != null && !fastClickAvoider.isFastClick()) {                //do click            }        }    });    mLoadMoreWrapper.setmGetViewHolderListener(holder -> {        if (holder != null) {            //获取到ViewHolder            noMoreView = holder.getView(R.id.linearLayout);        }    });    mBinding.rvHomePosition.setAdapter(mLoadMoreWrapper);    mHomePositionAdapter.setItemClickListener((binding, holder, position) -> {        if (fastClickAvoider != null && !fastClickAvoider.isFastClick()) {            //...        }    });}private void setData(){    //...    //fresh view    mLoadMoreWrapper.notifyDataSetChanged();    //对NoMoreWrapper的ViewHolder做些UI操作    if (noMoreView != null) {        noMoreView.setVisibility(View.GONE);    }}

其中布局文件:view_home_position_no_more.xml

1
2
3
4
5
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:app="http://schemas.android.com/apk/res-auto"    android:id="@+id/linearLayout"    android:layout_width="match_parent"    android:layout_height="wrap_content"    android:paddingTop="@dimen/viewSize10"    android:paddingBottom="@dimen/viewSize20"    android:gravity="center"    android:orientation="horizontal"    app:layout_constraintEnd_toEndOf="parent"    app:layout_constraintStart_toStartOf="parent"    app:layout_constraintTop_toTopOf="parent">    <View        android:layout_width="0dp"        android:layout_height="1dp"        android:layout_weight="1"        android:background="@drawable/shape_login_gradient_line_left" />    <TextView        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_marginStart="@dimen/viewSize32"        android:layout_marginEnd="@dimen/viewSize32"        android:text="前往搜索查看更多"        android:textColor="#00b2ee"        android:textSize="@dimen/textSize13" />    <View        android:layout_width="0dp"        android:layout_height="1dp"        android:layout_weight="1"        android:background="@drawable/shape_login_gradient_line_right" /></LinearLayout>
shape_login_gradient_line_left.xml
<?xml version="1.0" encoding="utf-8"?><shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> <gradient android:endColor="#601D1D1D" android:startColor="@android:color/transparent" /> <size android:width="120dp" android:height="1dp" /></shape>
shape_login_gradient_line_right.xml
<?xml version="1.0" encoding="utf-8"?><shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> <gradient android:endColor="@android:color/transparent" android:startColor="#601D1D1D" /> <size android:width="120dp" android:height="1dp" /></shape>

笔记

  • 要对列表的某控件加点击事件,须得在onBindViewHolder中获取对象并添加监听
  • notifyItemChanged(position)不触发onViewHolderBind方法,原来是rv外层有个LoadMoreWrapper容器。解决:在 setItemClickListener 中添加 mLoadMoreWrapper.notifyItemChanged(position);才会触发

RecyclerView问题

recyclerView没有数据问题

RecyclerView.setLayoutManager 不设置这个 就是不行的 就是没有数据的

RecyclerView.getChildAt(position)空指针

问题:程序中,屏幕可以获取到 6 个子 view,当 getChildAt(position)的position 为 6 或 7 的时候,会有空指针异常

原因:getChildAt()只能获取到屏幕上显示的部分

解决:View childView = recyclerView.getLayoutManager().findViewByPosition(position);

RecyclerView局部刷新,其他控件会闪动

  1. 怀疑是宽高重新计算导致的,但写死宽高页面还是会闪动

  2. 怀疑更新到了这个ImageView,然后glide又重新加载了一次图片

    在xml中写入android:src:"@mipmap/ic_launcher"还会闪动,排除2

  3. 怀疑刷新到了ImageView,这个item渲染了一次。

    源码中:默认传入的是null,表示全部刷新

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public void notifyItemRangeChanged(int positionStart, int itemCount, Object payload) {
    // since onItemRangeChanged() is implemented by the app, it could do anything, including
    // removing itself from {@link mObservers} - and that could cause problems if
    // an iterator is used on the ArrayList {@link mObservers}.
    // to avoid such problems, just march thru the list in the reverse order.
    for (int i = mObservers.size() - 1; i >= 0; i--) {
    mObservers.get(i).onItemRangeChanged(positionStart, itemCount, payload);
    }
    }

    解决:

    调用更改:

    1
    2
    //调用时,换个带payload参数的
    notifyItemChanged(position, 1);//这个1是随意的,其他值也可以

    adapter更改:(增加一个onBindViewHolder,带payloads参数)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    @Override
    public void onBindViewHolder(TestDownLoadHolder holder, int position, List<Object> payloads) {
    if (payloads.isEmpty()) {
    //走原来的逻辑
    onBindViewHolder(holder, position);
    } else {
    //有payloads的时候新的逻辑
    final AppInfoBean appInfoBean = data.get(position);
    if (appInfoBean != null) {
    holder.mPbDownProgress.setProgress(appInfoBean.getProgress());
    holder.mBtDownLoad.setText(appInfoBean.getDownloadPerSize());
    }
    }
    }

    @Override
    public void onBindViewHolder(final TestDownLoadHolder holder, final int position) {
    //原来的逻辑
    }

RecyclerView嵌套滑动冲突

解决方案一:

父recyclerView拦截并消耗了点击事件,那么就不要让父recyclerView拦截呗
自定义父recyclerView并重写onInterceptTouchEvent()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class ParentRecyclerView extends RecyclerView{
public ParentRecyclerView(@NonNull Context context){
super(context);
}
public ParentRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs){
super(context, attrs);
}
public ParentRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle){
super(context, attrs, defStyle);
}
//不拦截,继续分发下去
@Override
public boolean onInterceptTouchEvent(MotionEvent e){
return false;
}
}

解决方案二:

子布局通知父布局不要拦截事件,通过requestDisallowInterceptTouchEvent方法干预事件分发过程
重写dispatchTouchEvent()方法,通知通知父层ViewGroup不要拦截点击事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public ChildPresenter extends RecyclerView{
public ChildPresenter(@NonNull Context context){
super(context);
}
public ChildPresenter(@NonNull Context context, @Nullable AttributeSet attrs){
super(context, attrs);
}
public ChildPresenter(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle){
super(context, attrs, defStyle);
}

@Override
public boolean dispatchTouchEvent(MotionEvent ev){
//父层ViewGroup不要拦截点击事件
getParent().requestDisallowInterceptTouchEvent(true);
return super.dispatchTouchEvent(ev);
}
}

解决方案三:

通过事件分发规则我们知道,OnTouchListener优先级很高,可以通过这个来告诉父布局,不要拦截我的事件

1
2
3
4
5
6
7
8
holder.recyclerView.setOnTouchListener{ v, event ->
when(event.action){
//当用户按下的时候,我们告诉父组件,不要拦截我的事件(这个时候子组件是可以正常响应事件的),拿起之后就会告诉父组件可以阻止。
MotionEvent.ACTION_DOWN, MotionEvent.ACTION_MOVE -> v.parent.requestDisallowInterceptTouchEvent(true)
MotionEvent.ACTION_UP -> v.parent.requestDisallowInterceptTouchEvent(false)
}
false
}

ScrollView嵌套RecycleView导致滑动冲突或者显示不全的问题

  1. ScrollView替换成普通布局,然后RecycleView用的BaseMultiItemQuickAdapter多布局来写,也就是整个页面只有一个RecycleView,用来取代ScrollView,但是这样比较复杂。

  2. 对ScrollView和RecycleView的isNestedScrollingEnabled的值设置成false

    1
    2
    scrollViewTrans.isNestedScrollingEnabled = false
    RecyclerView.isNestedScrollingEnabled = false
  3. 如果方法2不行,那我们直接简单粗暴一点,用NestedScrollView替换ScrollView,保证你的recycleView滑动起来刷刷的

    Support库引用方式

    1
    android.support.v4.widget.NestedScrollView

    AndroidX引用方式

    1
    androidx.core.widget.NestedScrollView

NestedScrollView嵌套RecyclerView导致复用失效

NestedScrollView嵌套RecyclerView为解决滑动冲突通常为RecyclerView设置setNestedScrollingEnabled(false),这样解决了滑动冲突,但是导致RecyclerView复用回收机制失效,RecyclerView会一次性创建并加载所有item,数据量少的情况可以忽略不复用的问题,数据量多会导致页面卡顿严重。
解决方法:
一、RecyclerView设置固定高度,setNestedScrollingEnabled(true)。RecyclerView可以复用但是会出现滑动冲突问题。
二、去掉NestedScrollView,将RecyclerView作为最外层布局,其他布局作为RecyclerView的HeadView或FooterView
三、使用CoordinatorLayout嵌套RecyclerView,实现页面滑动。CoordinatorLayout嵌套RecyclerView会出现滑动抖动现象 解决方案

解决CoordinatorLayout+AppBarLayout滑动抖动(回弹)问题

最近发现项目中一个使用CoordinatorLayout+AppBarLayout的界面发生了滑动不顺畅的问题,界面中使用了CoordinatorLayout,AppBarLayout,CollapsingToolbarLayout,Toolbar,RecyclerView,当向上滑动AppBarLayout并在惯性滑动未停止的时候滑动RecyclerView发生回弹
一、xml布局文件

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/behavior_demo_coordinatorLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">

<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:elevation="0dp"
app:layout_behavior="com.example.utils.FlingBehavior">

<android.support.design.widget.CollapsingToolbarLayout
android:id="@+id/main.collapsing"
android:layout_width="match_parent"
android:layout_height="250dp"
android:fitsSystemWindows="true"
app:contentScrim="?attr/colorPrimary"
app:expandedTitleMarginEnd="64dp"
app:expandedTitleMarginStart="48dp"
app:layout_scrollFlags="scroll|exitUntilCollapsed">

<!--其他布局-->
<ImageView
android:id="@+id/main.backdrop"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:scaleType="centerCrop"
android:src="@drawable/img_bg"
app:layout_collapseMode="parallax" />

<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_collapseMode="pin"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light">

<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="15dp">

<ImageView
android:id="@+id/iv_back"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:paddingStart="15dp"
android:paddingEnd="15dp"
android:src="@drawable/ic_arrow_back_white_24dp" />

<ImageView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_alignParentRight="true"
android:paddingStart="15dp"
android:paddingEnd="15dp"
android:src="@drawable/ic_apps_white_24dp" />

</RelativeLayout>
</android.support.v7.widget.Toolbar>
</android.support.design.widget.CollapsingToolbarLayout>

<android.support.design.widget.TabLayout
android:id="@+id/tabs"
android:layout_width="match_parent"
android:layout_height="40dp"
android:layout_alignParentBottom="true"
android:background="?attr/colorPrimary"
app:tabIndicatorColor="@color/colorAccent"
app:tabIndicatorHeight="4dp"
app:tabSelectedTextColor="#000"
app:tabTextColor="#fff" />
</android.support.design.widget.AppBarLayout>

<android.support.v7.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent" />
</android.support.design.widget.CoordinatorLayout>

app:layout_behavior="com.example.utils.FlingBehavior"这个是自定义的用来解决抖动问题_

二、解决办法

自定义AppBarLayout.Behavior

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
35
36
37
38
39
40
41
42
43
44
45
public class FlingBehavior extends AppBarLayout.Behavior {
private static final int TOP_CHILD_FLING_THRESHOLD = 3;
private boolean isPositive;
public FlingBehavior() {
super();
}
public FlingBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}

//fling上滑appbar然后迅速fling下滑recycler时, HeaderBehavior的mScroller并未停止, 会导致上下来回晃动
@Override
public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target, int dx, int dy, int[] consumed, int type) {
if (type == ViewCompat.TYPE_NON_TOUCH && getTopAndBottomOffset() == 0) { //recyclerview的惯性比较大 ,会顶在头部一会儿, 到头直接干掉它的滑动
ViewCompat.stopNestedScroll(target, type);
}
super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed, type);
}

@Override
public boolean onInterceptTouchEvent(CoordinatorLayout parent, AppBarLayout child, MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
Object scroller = getSuperSuperField(this, "mScroller");
if (scroller != null && scroller instanceof OverScroller) {
OverScroller overScroller = (OverScroller) scroller;
overScroller.abortAnimation();
}
}
return super.onInterceptTouchEvent(parent, child, ev);
}

private Object getSuperSuperField(Object paramClass, String paramString) {
Field field = null;
Object object = null;
try {
field = paramClass.getClass().getSuperclass().getSuperclass().getDeclaredField(paramString);
field.setAccessible(true);
object = field.get(paramClass);
} catch (Exception e) {
e.printStackTrace();
}
return object;
}
// --------------------------- end
}

LinearLayoutManager内部对齐

自定义LinearLayoutManger,重写smoothScrollToPosition方法内部的getVerticalSnapPreference()getHorizontalSnapPreference()的值为SNAP_TO_START

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
public class SnapLayoutManager extends LinearLayoutManager {
private Context context;

public SnapLayoutManager(Context context) {
super(context);
this.context = context;
}

@Override
public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, int position) {
super.smoothScrollToPosition(recyclerView, state, position);

LinearSmoothScroller smoothScroller = new LinearSmoothScroller(context) {
@Override
protected int getVerticalSnapPreference() {
return SNAP_TO_START;
}

@Override
protected int getHorizontalSnapPreference() {
return SNAP_TO_START;
}
};
smoothScroller.setTargetPosition(position);
startSmoothScroll(smoothScroller);
}
}