RecyclerView
优化
- 数据处理和视图加载分离:数据的处理逻辑尽可能放在异步处理,onBindViewHolder 方法中只处理数据填充到视图中。
- 数据优化:分页拉取远端数据,对拉取下来的远端数据进行缓存,提升二次加载速度;对于新增或者删除数据通过
DiffUtil
来进行局部刷新数据,而不是一味地全局刷新数据。
示例
1 | public class AdapterDiffCallback extends DiffUtil.Callback { |
1 | DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new AdapterDiffCallback(oldList, newList)); |
- DiffUtil.Callback
- 布局优化:减少布局层级,简化
ItemView
- 升级
RecycleView
版本到 25.1.0 及以上使用 Prefetch 功能 - 通过重写
RecyclerView.onViewRecycled(holder)
来回收资源 - 如果
Item
高度是固定的话,可以使用RecyclerView.setHasFixedSize(true);
来避免requestLayout
浪费资源【不是瀑布流的情况下可以加这句】 - 对
ItemView
设置监听器,不要对每个Item
都调用addXxListener
,应该大家公用一个XxListener
,根据ID
来进行不同的操作,优化了对象的频繁创建带来的资源消耗 - 如果多个
RecycledView
的Adapter
是一样的,比如嵌套的RecyclerView
中存在一样的Adapter
,可以通过设置RecyclerView.setRecycledViewPool(pool)
,来共用一个RecycledViewPool
。
二级列表
在布局文件中添加 ExpandableAdapter
,或在 Activity
中 extends ExpandableAdapter
在 Activity
的 oncreate
中
1 | //将默认的左边的箭头去掉 |
- 创建一个类继承
BaseExpandableListAdapter
- 先重写
getGroupCount
、getGroupView
再写getChildView
、getChildrenCount
- 在
activity
中创建适配器对象,并且设置适配器
思路:
继承 activity
- 创建两
xml
布局
布局1:有ExpandableListView
控件
布局2:子列表的样式 - 在
activity
的oncreate
方法中实例化ExpandableListView
控件 - 自定义可扩展适配器,创建一个类继承
BaseExpandableListAdapter
并添加为实现的方法 - 必须重写的方法:先重写
getGroupCount
、getGroupView
再写getChildView
、getChildrenCount
- 在
onCreate
方法中实例化可扩展适配器对象 - 调用
setadapter
设置适配器
注释:如果我们在自定义适配器时,注意是否需要加载元素样式布局(如果需要加载,这个时候必须获取布局过滤器对象)–>通常情况处理方式为:添加一个构造方法,里面的参数只收有 context c
在获取 view
,必须通过布局过滤器对象调用 inflate
方法,加载样式布局 返回 view
对象,记得赋值
如果继承 listactivity
。。。
可扩展适配器实现步骤
- 继承
activity
- 创建两
xml
布局
布局1:有ExpandableListView
控件
布局2:子列表元素的样式 - 在
activity
的oncreate
方法中实例化ExpandableListView
控件 - 自定义可扩展适配器,继承
BaseExpandableListAdapter
并添加未实现的方法 - 必须重写
getGroupView
、getGroupCount
、getChildView
、getChildrenCount
- 在
oncreate
方法中实例化自定义可扩展适配器对象 - 调用
setadapter
设置适配器
注释:如果我们在自定义适配器时,注意是否需要加载元素样式布局(如果需要加载,这个时候必须获取布局过滤器对象)—>通常情况处理方式为:添加一个构造方法,里面的参数至少有Context c 在获取view,必须通过布局过滤器对象调用inflate方法,加载样式布局 返回View对象,记得赋值
RecyclerView
RecyclerView
整体架构设计
RecyclerView
类注释中对自己的解释:它是一种可以在有限区域内展示大量数据的View
。
RecyclerView
本质是ViewGroup
Adapter
、LayoutManager
、Recycler
这三个对RecyclerView
是必不可少的Recycler
的工作被封装在RecyclerView
内部完成,对使用者来说是透明的。- 谷歌提供了集中
LayoutManager
,实现单列列表、瀑布流列表等效果
RecyclerView
的重要成员
ViewHolder
RecyclerView
的基本单位:ViewHolder
。
如果用Item
表示数据集中的一个数据对象,ItemView
表示用来展示该Item
的View
对象。ViewHolder
负责在RecyclerView
中承载一个ItemView
,除了维护View
对象本身外,还维护着Item
位置(position
,此处的位置是指Item
在数据集中的次序)、item
类型、item Id
等。大部分时候,RecyclerView
内部或其辅助类不会直接操作View
,而是操作ViewHolder
。
在阅读RecyclerView
源码时发现,一些操作需要用View做参数,也有一些操作需要用ViewHolder
做参数,实际上在RecyclerView
中,可以通过任意一样拿到另一样,不必太过纠结RecyclerView
的各个内部类的变量中究竟维护的是哪种类型。
但一般认为,
- Adapter负责根据数据Item创建对应的ViewHolder;
- Recycler负责管理ViewHolder,根据实际情况创建新的ViewHolder或复用已有的ViewHolder;
- LayoutManager可以通过Recycler直接获取到View,负责讲其添加到RecyclerView的布局中,并通过Recycler回收已经不被展示的View。
画了10张图,带你搞定RecyclerView的缓存复用机制
RecyclerView:回收其列表项视图以供重用。具体:当一个列表项被移出屏幕后,RecyclerView并不会销毁其视图,而是会缓存起来,以提供给新进入屏幕的列表项重用,这种重用可以:
- 避免重复创建不必要的视图。
- 避免重复执行昂贵的
findViewById
。RecyclerView是如何构建动态列表的?
与RecyclerView构建动态列表相关联的几个重要类中,Adapter与ViewHolder负责配合使用,共同定义RecyclerView列表项数据的展示方式,其中:
- ViewHolder是一个「包含列表项视图(itemView)的封装容器」,同时也是「RecyclerView缓存复用的主要对象」。
- Adapter则提供了「数据<->视图」 的“绑定”关系,其包含以下几个关键方法:
- onCreateViewHolder:负责创建并初始化ViewHolder及其关联的视图,但不会填充视图内容。
- onBindViewHolder:负责提取适当的数据,填充ViewHolder的视图内容。
然而,这2个方法并非每一个进入屏幕的列表项都会回调,相反,由于视图创建及findViewById执行等动作都主要集中在这2个方法,每次都要回调的话反而效率不佳。因此,我们应该通过对ViewHolder对象积极地缓存复用,来尽量减少对这2个方法的回调频次。
- 最优情况是——取得的缓存对象正好是原先的ViewHolder对象,这种情况下既不需要重新创建该对象,也不需要重新绑定数据,即拿即用。
- 次优情况是——取得的缓存对象虽然不是原先的ViewHolder对象,但由于二者的列表项类型(itemType)相同,其关联的视图可以复用,因此只需要重新绑定数据即可。
- 最后实在没办法了,才需要执行这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 {
...
}
1 | public final class Recycler { |
结合RecyclerView类中的源码及注释可知,Recycler会依次从mChangedScrap/mAttachedScrap
、mCachedViews
、mViewCacheExtension
、mRecyclerPool
中尝试获取指定位置或ID的ViewHolder对象以供重用,如果全都获取不到则直接重新创建。这其中涉及的几层缓存结构分别是:
mChangedScrap/mAttachedScrap
mChangedScrap/mAttachedScrap
主要用于「临时存放仍在当前屏幕可见、但被标记为「移除」或「重用」的列表项」,其均以ArrayList的形式持有着每个列表项的ViewHolder对象,大小无明确限制,但一般来讲,其最大数就是屏幕内总的可见列表项数。
1 | final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>(); |
但问题来了,既然是当前屏幕可见的列表项,为什么还需要缓存呢?又是什么时候列表项会被标记为「移除」或「重用」的呢?
这2个缓存结构实际上更多是为了避免出现像「局部刷新」这一类的操作,导致所有的列表项都需要重绘的情形。
区别在于,mChangedScrap
主要的使用场景是:
- 开启了列表项动画(itemAnimator),并且列表项动画的
canReuseUpdatedViewHolder(ViewHolder viewHolder)
方法返回false的前提下; - 调用了
notifyItemChanged
、notifyItemRangeChanged
这一类方法,通知列表项数据发生变化;fold 1
2
3
4
5
6
7
8
9
10
11
12
13boolean canReuseUpdatedViewHolder(ViewHolder viewHolder) {
return mItemAnimator == null || mItemAnimator.canReuseUpdatedViewHolder(viewHolder,
viewHolder.getUnmodifiedPayloads());
}
public boolean canReuseUpdatedViewHolder( ViewHolder viewHolder,
{ List<Object> payloads)
return canReuseUpdatedViewHolder(viewHolder);
}
public boolean canReuseUpdatedViewHolder( { ViewHolder viewHolder)
return true;
}canReuseUpdatedViewHolder
方法的返回值表示的不同含义如下:
- true,表示可以重用原先的ViewHolder对象。
- false,表示应该创建该ViewHolder的副本,以便itemAnimator利用两者来实现动画效果(例如交叉淡入淡出效果)。
简单讲就是,mChangedScrap主要是为列表项数据发生变化时的动画效果服务的。
而mAttachedScrap应对的则是剩下的绝大部分场景,比如: - 像
notifyItemMoved
、notifyItemRemoved
这种列表项发生移动,但列表项数据本身没有发生变化的场景。 - 关闭了列表项动画,或者列表项动画的
canReuseUpdatedViewHolder
方法返回true,即允许重用原先的ViewHolder对象的场景。
下面以一个简单的notifyItemRemoved(int position)
操作为例来演示:notifyItemRemoved(int position)
方法用于通知观察者,先前位于position的列表项已被移除, 其往后的列表项position都将往前移动1位。
为了简化问题、方便演示,我们的范例将会居于以下限制: - 列表项总个数没有铺满整个屏幕——意味着不会触发
mCachedViews
、mRecyclerPool
等结构的缓存操作。 - 去除列表项动画——意味着调用
notifyItemRemoved
后RecyclerView只会重新布局子视图一次。理想情况下,调用fold 1
recyclerView.itemAnimator = null
notifyItemRemoved(int position)
方法后,应只有位于position的列表项会被移除,其他的列表项,无论是位于position之前或之后,都最多只会调整position值,而不应发生视图的重新创建或数据的重新绑定,即不应该回调onCreateViewHolder
与onBindViewHolder
这2个方法。
为此,我们就需要将当前屏幕内的可见列表项暂时从当前屏幕剥离,临时缓存到mAttachedScrap
这个结构中去。
等到RecyclerView重新开始布局显示其子视图后,再遍历mAttachedScrap
找到对应position的ViewHolder对象进行复用。mCachedViews
mCachedViews
主要用于「存放已被移出屏幕、但有可能很快重新进入屏幕的列表项」。其同样是以ArrayList的形式持有着每个列表项的ViewHolder对象,默认大小限制为2。
1 | final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>(); |
比如像朋友圈这种按更新时间的先后顺序展示的Feed流,我们经常会在快速滑动中确定是否有自己感兴趣的内容,当意识到刚才滑走的内容可能比较有趣时,我们往往就会将上一条内容重新滑回来查看。
这种场景下我们追求的自然是上一条内容展示的实时性与完整性,而不应让用户产生“才滑走那么一会儿又要重新加载”的抱怨,也即同样不应发生视图的重新创建或数据的重新绑定。
我们用几张流程示意图来演示这种情况:
同样为了简化问题、方便描述,我们的范例将会居于以下限制:
- 关闭预拉取——意味着之后向上滑动时,都不会再预拉取「待进入屏幕区域」的一个列表项放入
mCachedView
了。fold 1
recyclerView.layoutManager?.isItemPrefetchEnabled = false
- 只存在一种类型的列表项,即所有列表项的itemType相同,默认都为0。
我们将图中的列表项分成了3块区域,分别是被滑出屏幕之外的区域、屏幕内的可见区域、随着滑动手势待进入屏幕的区域。
- 当position=0的列表项随着向上滑动的手势被移出屏幕后,由于
mCachedViews
初始容量为0,因此可直接放入; - 当position=1的列表项同样被移出屏幕后,由于未达到
mCachedViews
的默认容量大小限制,因此也可继续放入; - 此时改为向下滑动,position=1的列表项重新进入屏幕,Recycler就会依次从
mAttachedScrap
、mCachedViews
查找可重用于此位置的ViewHolder对象; mAttachedScrap
不是应对这种情况的,自然找不到。而mCachedViews
会遍历自身持有的ViewHolder对象,对比ViewHolder对象的position值与待复用位置的position值是否一致,是的话就会将ViewHolder对象从mCachedViews
中移除并返回;- 此处拿到的ViewHolder对象即可直接复用,即符合前面所述的「最优情况」。
- 另外,随着position=1的列表项重新进入屏幕,position=7的列表项也会被移出屏幕,该位置的列表项同样会进入
mCachedViews
,即RecyclerView是双向缓存的。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
11public 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
示意图基础上继续操作: - 假设目前存在于
mCachedViews
中的仍是position=0及position=1这两个列表项。 - 当我们继续向上滑动时,position=2的列表项会尝试进入
mCachedViews
,由于超出了mCachedViews
的容量限制,position=0的列表项会从mCachedViews
中被移出,并放入RecycledViewPool中itemType为0的ArrayList,即图中的情况①。 - 同时,底部的一个新的列表项也将随着滑动手势进入到屏幕内,但由于此时
mAttachedScrap
、mCachedViews
、mRecyclerPool
均没有合适的ViewHolder对象可以提供给其复用,因此该列表项只能执行onCreateViewHolder
与onBindViewHolder
这2个方法的回调,即图中的情况②。 - 等到position=2的列表项被完全移出了屏幕后,也就顺利进入了
mCachedViews
中。 - 我们继续保持向上滑动的手势,此时,由于下一个待进入屏幕的列表项与position=0的列表项的itemType相同,因此我们可以在走到从
mRecyclerPool
查找合适的ViewHolder对象这一步时,根据itemType找到对应的ArrayList,再取出其中的1个ViewHolder对象进行复用,即图中的情况①。 - 由于itemType类型一致,其关联的视图可以复用,因此只需要重新绑定数据即可,即符合前面所述的「次优情况」。
- ②③ 情况与前面的一致,此处不再赘余。
总结
RecyclerView缓存复用机制 对象 ViewHolder(包含列表项视图(itemView)的封装容器) 目的 减少对 onCreateViewHolder
、onBindViewHolder
这2个方法的回调好处 1. 避免重复创建不必要的视图
2. 避免重复执行昂贵的findViewById
效果 改善性能 、提升应用响应能力、降低功耗 核心类 Recycler
、RecyclerViewPool
缓存结构 mChangedScrap
/mAttachedScrap
、mCachedViews
、mViewCacheExtension
、mRecyclerPool
缓存结构 | 容器类型 | 容量限制 | 缓存用途 | 优先级顺序 (数值越小, 优先级越高) |
---|---|---|---|---|
mChangedScrap /mAttachedScrap |
ArrayList |
无,一般为屏幕内总的可见列表项数 | 临时存放仍在当前屏幕可见、但被标记为「移除」或「重用」的列表项 | 0 |
mCachedViews |
ArrayList |
默认为2 | 存放已被移出屏幕、但有可能很快重新进入屏幕的列表项 | 1 |
mViewCacheExtension |
开发者自己定义 | 无 | 提供额外的可由开发人员自由控制的缓存层级 | 2 |
mRecyclerPool |
SparseArray<ArrayList> |
每种ItemType 默认为5 |
按不同itemType 分别存放超出mCachedViews 限制的、被移出屏幕的列表项 |
3 |
代码:baseadapter-recyclerview
1 | ├── Common |
具体工具类代码
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>() { public int getItemViewLayoutId() { return layoutId; } public boolean isForViewType(T item, int position) { return true; } public void convert(ViewHolder holder, T t, int position) { CommonRvAdapter.this.convert(holder, t, position); } }); } 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() { 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 | <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> |
笔记
- 要对列表的某控件加点击事件,须得在
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局部刷新,其他控件会闪动
怀疑是宽高重新计算导致的,但写死宽高页面还是会闪动
怀疑更新到了这个ImageView,然后glide又重新加载了一次图片
在xml中写入
android:src:"@mipmap/ic_launcher"
还会闪动,排除2怀疑刷新到了ImageView,这个item渲染了一次。
源码中:默认传入的是null,表示全部刷新
1
2
3
4
5
6
7
8
9public 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
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());
}
}
}
public void onBindViewHolder(final TestDownLoadHolder holder, final int position) {
//原来的逻辑
}
RecyclerView嵌套滑动冲突
解决方案一:
父recyclerView拦截并消耗了点击事件,那么就不要让父recyclerView拦截呗
自定义父recyclerView并重写onInterceptTouchEvent()方法
1 | public class ParentRecyclerView extends RecyclerView{ |
解决方案二:
子布局通知父布局不要拦截事件,通过requestDisallowInterceptTouchEvent方法干预事件分发过程
重写dispatchTouchEvent()方法,通知通知父层ViewGroup不要拦截点击事件
1 | public ChildPresenter extends RecyclerView{ |
解决方案三:
通过事件分发规则我们知道,OnTouchListener优先级很高,可以通过这个来告诉父布局,不要拦截我的事件
1 | holder.recyclerView.setOnTouchListener{ v, event -> |
ScrollView嵌套RecycleView导致滑动冲突或者显示不全的问题
ScrollView替换成普通布局,然后RecycleView用的BaseMultiItemQuickAdapter多布局来写,也就是整个页面只有一个RecycleView,用来取代ScrollView,但是这样比较复杂。
对ScrollView和RecycleView的isNestedScrollingEnabled的值设置成false
1
2scrollViewTrans.isNestedScrollingEnabled = false
RecyclerView.isNestedScrollingEnabled = false如果方法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 |
|
app:layout_behavior="com.example.utils.FlingBehavior"
这个是自定义的用来解决抖动问题_
二、解决办法
自定义AppBarLayout.Behavior
1 | public class FlingBehavior extends AppBarLayout.Behavior { |
LinearLayoutManager内部对齐
自定义LinearLayoutManger
,重写smoothScrollToPosition
方法内部的getVerticalSnapPreference()
、getHorizontalSnapPreference()
的值为SNAP_TO_START
1 | public class SnapLayoutManager extends LinearLayoutManager { |