RecyclerView问题汇总
版权声明:
本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《
阿里云开发者社区用户服务协议》和
《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写
侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。
简介:
目录介绍
25.0.0.0 请说一下RecyclerView?adapter的作用是什么,几个方法是做什么用的?如何理解adapter订阅者模式?
25.0.0.1 ViewHolder的作用是什么?如何理解ViewHolder的复用?什么时候停止调用onCreateViewHolder?
25.
目录介绍
- 25.0.0.0 请说一下RecyclerView?adapter的作用是什么,几个方法是做什么用的?如何理解adapter订阅者模式?
- 25.0.0.1 ViewHolder的作用是什么?如何理解ViewHolder的复用?什么时候停止调用onCreateViewHolder?
- 25.0.0.2 ViewHolder封装如何对findViewById优化?ViewHolder中为何使用SparseArray替代HashMap存储viewId?
- 25.0.0.3 LayoutManager作用是什么?LayoutManager样式有哪些?setLayoutManager源码里做了什么?
- 25.0.0.4 SnapHelper主要是做什么用的?SnapHelper是怎么实现支持RecyclerView的对齐方式?
- 25.0.0.5 SpanSizeLookup的作用是干什么的?SpanSizeLookup如何使用?SpanSizeLookup实现原理如何理解?
- 25.0.0.6 ItemDecoration的用途是什么?自定义ItemDecoration有哪些重写方法?分析一下addItemDecoration()源码?
- 25.0.0.7 上拉加载更多的功能是如何做的?添加滚动监听事件需要注意什么问题?网格布局上拉加载如何优化?
- 25.0.0.8 RecyclerView绘制原理如何理解?性能优化本质是什么?RecyclerView绘制原理过程大概是怎样的?
- 25.0.0.9 RecyclerView的Recyler是如何实现ViewHolder的缓存?如何理解recyclerView三级缓存是如何实现的?
- 25.0.1.0 屏幕滑动(状态是item状态可见,不可见,即将可见变化)时三级缓存是如何理解的?adapter中的几个方法是如何变化?
- 25.0.1.1 SnapHelper有哪些重要的方法,其作用就是是什么?LinearSnapHelper中是如何实现滚动停止的?
- 25.0.1.2 LinearSnapHelper代码中calculateDistanceToFinalSnap作用是什么?那么out[0]和out[1]分别指什么?
- 25.0.1.3 如何实现可以设置分割线的颜色,宽度,以及到左右两边的宽度间距的自定义分割线,说一下思路?
- 25.0.1.4 如何实现复杂type首页需求?如果不封装会出现什么问题和弊端?如何提高代码的简便性和高效性?
- 25.0.1.5 关于item条目点击事件在onCreateViewHolder中写和在onBindViewHolder中写有何区别?如何优化?
- 25.0.1.6 RecyclerView滑动卡顿原因有哪些?如何解决嵌套布局滑动冲突?如何解决RecyclerView实现画廊卡顿?
- 25.0.1.7 RecyclerView常见的优化有哪些?实际开发中都是怎么做的,优化前后对比性能上有何提升?
- 25.0.1.8 如何解决RecyclerView嵌套RecyclerView条目自动上滚的Bug?如何解决ScrollView嵌套RecyclerView滑动冲突?
- 25.0.1.9 如何处理ViewPager嵌套水平RecyclerView横向滑动到底后不滑动ViewPager?如何解决RecyclerView使用Glide加载图片导致图片错乱问题?
25.0.0.0 请说一下RecyclerView?adapter的作用是什么,几个方法是做什么用的?如何理解adapter订阅者模式?
-
关于RecyclerView,大家都已经很熟悉了,用途十分广泛,大概结构如下所示
- RecyclerView.Adapter - 处理数据集合并负责绑定视图
- ViewHolder - 持有所有的用于绑定数据或者需要操作的View
- LayoutManager - 负责摆放视图等相关操作
- ItemDecoration - 负责绘制Item附近的分割线
- ItemAnimator - 为Item的一般操作添加动画效果,如,增删条目等
-
如图所示,直观展示结构
-
adapter的作用是什么
- RecyclerView.Adapter扮演的角色
- 一是,根据不同ViewType创建与之相应的的Item-Layout
- 二是,访问数据集合并将数据绑定到正确的View上
-
几个方法是做什么用的
public VH onCreateViewHolder(ViewGroup parent, int viewType)
创建Item视图,并返回相应的ViewHolder
public void onBindViewHolder(VH holder, int position)
绑定数据到正确的Item视图上。
public int getItemCount()
返回该Adapter所持有的Itme数量
public int getItemViewType(int position)
用来获取当前项Item(position参数)是哪种类型的布局
-
如何理解adapter订阅者模式
- 当时据集合发生改变时,我们通过调用.notifyDataSetChanged(),来刷新列表,因为这样做会触发列表的重绘。
- 注意这里需要理解什么是订阅者模式……
-
a.首先看.notifyDataSetChanged()源码
public final void notifyDataSetChanged() {
mObservable.notifyChanged();
}
-
b.接着查看.notifyChanged();源码
- 被观察者AdapterDataObservable,内部持有观察者AdapterDataObserver集合
static class AdapterDataObservable extends Observable<AdapterDataObserver> {
public boolean hasObservers() {
return !mObservers.isEmpty();
}
public void notifyChanged() {
for (int i = mObservers.size() - 1; i >= 0; i--) {
mObservers.get(i).onChanged();
}
}
public void notifyItemRangeChanged(int positionStart, int itemCount) {
notifyItemRangeChanged(positionStart, itemCount, null);
}
public void notifyItemRangeChanged(int positionStart, int itemCount, Object payload) {
for (int i = mObservers.size() - 1; i >= 0; i--) {
mObservers.get(i).onItemRangeChanged(positionStart, itemCount, payload);
}
}
public void notifyItemRangeInserted(int positionStart, int itemCount) {
for (int i = mObservers.size() - 1; i >= 0; i--) {
mObservers.get(i).onItemRangeInserted(positionStart, itemCount);
}
}
}
- 观察者AdapterDataObserver,具体实现为RecyclerViewDataObserver,当数据源发生变更时,及时响应界面变化
public static abstract class AdapterDataObserver {
public void onChanged() {
// Do nothing
}
public void onItemRangeChanged(int positionStart, int itemCount) {
// do nothing
}
public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
onItemRangeChanged(positionStart, itemCount);
}
}
-
c.接着查看setAdapter()源码中的setAdapterInternal(adapter, false, true)方法
public void setAdapter(Adapter adapter) {
// bail out if layout is frozen
setLayoutFrozen(false);
setAdapterInternal(adapter, false, true);
requestLayout();
}
- setAdapterInternal(adapter, false, true)源码
private void setAdapterInternal(Adapter adapter, boolean compatibleWithPrevious,
boolean removeAndRecycleViews) {
if (mAdapter != null) {
mAdapter.unregisterAdapterDataObserver(mObserver);
mAdapter.onDetachedFromRecyclerView(this);
}
if (!compatibleWithPrevious || removeAndRecycleViews) {
removeAndRecycleViews();
}
mAdapterHelper.reset();
final Adapter oldAdapter = mAdapter;
mAdapter = adapter;
if (adapter != null) {
//注册一个观察者RecyclerViewDataObserver
adapter.registerAdapterDataObserver(mObserver);
adapter.onAttachedToRecyclerView(this);
}
if (mLayout != null) {
mLayout.onAdapterChanged(oldAdapter, mAdapter);
}
mRecycler.onAdapterChanged(oldAdapter, mAdapter, compatibleWithPrevious);
mState.mStructureChanged = true;
markKnownViewsInvalid();
}
-
d.notify……方法被调用,刷新数据
- 当数据变更时,调用notify**方法时,Adapter内部的被观察者会遍历通知已经注册的观察者的对应方法,这时界面就会响应变更。博客
25.0.0.1 ViewHolder的作用是什么?如何理解ViewHolder的复用?什么时候停止调用onCreateViewHolder?
-
ViewHolder作用大概有这些:
- adapter应当拥有ViewHolder的子类,并且ViewHolder内部应当存储一些子view,避免时间代价很大的findViewById操作
- 其RecyclerView内部定义的ViewHolder类包含很多复杂的属性,内部使用场景也有很多,而我们经常使用的也就是onCreateViewHolder()方法和onBindViewHolder()方法,onCreateViewHolder()方法在RecyclerView需要一个新类型。item的ViewHolder时调用来创建一个ViewHolder,而onBindViewHolder()方法则当RecyclerView需要在特定位置的item展示数据时调用。博客
-
如何理解ViewHolder的复用
-
在复写RecyclerView.Adapter的时候,需要我们复写两个方法:博客
- onCreateViewHolder
- onBindViewHolder
- 这两个方法从字面上看就是创建ViewHolder和绑定ViewHolder的意思
-
复用机制是怎样的?
- 模拟场景:只有一种ViewType,上下滑动的时候需要的ViewHolder种类是只有一种,但是需要的ViewHolder对象数量并不止一个。所以在后面创建了9个ViewHolder之后,需要的数量够了,无论怎么滑动,都只需要复用以前创建的对象就行了。那么逗比程序员们思考一下,为什么会出现这种情况呢
- 看到了下面log之后,第一反应是在这个ViewHolder对象的数量“够用”之后就停止调用onCreateViewHolder方法,但是onBindViewHolder方法每次都会调用的

-
查看一下createViewHolder源代码
public final VH createViewHolder(ViewGroup parent, int viewType) {
TraceCompat.beginSection(TRACE_CREATE_VIEW_TAG);
final VH holder = onCreateViewHolder(parent, viewType);
holder.mItemViewType = viewType;
TraceCompat.endSection();
return holder;
}
-
对于ViewHolder对象的数量“够用”之后就停止调用onCreateViewHolder方法,可以查看
- 获取为给定位置初始化的视图。博客
- 此方法应由{@link LayoutManager}实现使用,以获取视图来表示来自{@LinkAdapter}的数据。
- 如果共享池可用于正确的视图类型,则回收程序可以重用共享池中的废视图或分离视图。如果适配器没有指示给定位置上的数据已更改,则回收程序将尝试发回一个以前为该数据初始化的报废视图,而不进行重新绑定。
public View getViewForPosition(int position) {
return getViewForPosition(position, false);
}
View getViewForPosition(int position, boolean dryRun) {
return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView;
}
@Nullable
ViewHolder tryGetViewHolderForPositionByDeadline(int position,boolean dryRun, long deadlineNs) {
//代码省略了,有需要的小伙伴可以自己看看,这里面逻辑实在太复杂呢
}
25.0.0.2 ViewHolder封装如何对findViewById优化?ViewHolder中为何使用SparseArray替代HashMap存储viewId?
-
ViewHolder封装如何对findViewById优化?
class MyViewHolder extends RecyclerView.ViewHolder {
private SparseArray<View> viewSparseArray;
private TextView tvTitle;
MyViewHolder(final View itemView) {
super(itemView);
if(viewSparseArray==null){
viewSparseArray = new SparseArray<>();
}
tvTitle = (TextView) viewSparseArray.get(R.id.tv_title);
if (tvTitle == null) {
tvTitle = itemView.findViewById(R.id.tv_title);
viewSparseArray.put(R.id.tv_title, tvTitle);
}
}
}
-
为何使用SparseArray替代HashMap存储viewId
-
HashMap
- 基本上就是一个 HashMap.Entry 的数组(Entry 是 HashMap 的一个内部类)。更准确来说,Entry 类中包含以下字段:
- 一个非基本数据类型的 key
- 一个非基本数据类型的 value
- 保存对象的哈希值
- 指向下一个 Entry 的指针
-
当有键值对插入时,HashMap 会发生什么 ?
- 首先,键的哈希值被计算出来,然后这个值会赋给 Entry 类中对应的 hashCode 变量。
- 然后,使用这个哈希值找到它将要被存入的数组中“桶”的索引。
- 如果该位置的“桶”中已经有一个元素,那么新的元素会被插入到“桶”的头部,next 指向上一个元素——本质上使“桶”形成链表。
- 现在,当你用 key 去查询值时,时间复杂度是 O(1)。虽然时间上 HashMap 更快,但同时它也花费了更多的内存空间。
-
缺点:
- 自动装箱的存在意味着每一次插入都会有额外的对象创建。这跟垃圾回收机制一样也会影响到内存的利用。
- HashMap.Entry 对象本身是一层额外需要被创建以及被垃圾回收的对象。
- “桶” 在 HashMap 每次被压缩或扩容的时候都会被重新安排。这个操作会随着对象数量的增长而变得开销极大
- 在Android中,当涉及到快速响应的应用时,内存至关重要,因为持续地分发和释放内存会出发垃圾回收机制,这会拖慢应用运行。垃圾回收机制会影响应用性能表现,垃圾回收时间段内,应用程序是不会运行的,最终应用使用上就显得卡顿。
-
SparseArray博客
- 它里面也用了两个数组。一个int[] mKeys和Object[] mValues。从名字都可以看得出来一个用来存储key一个用来保存value的。
-
当保存一对键值对的时候:
- key(不是它的hashcode)保存在mKeys[]的下一个可用的位置上。所以不会再对key自动装箱了。
- value保存在mValues[]的下一个位置上,value还是要自动装箱的,如果它是基本类型。
-
查找的时候:
- 查找key还是用的二分法查找。也就是说它的时间复杂度还是O(logN)
- 知道了key的index,也就可以用key的index来从mValues中检索出value。
- 相较于HashMap,我们舍弃了Entry和Object类型的key,放弃了HashCode并依赖于二分法查找。在添加和删除操作的时候有更好的性能开销。
25.0.0.3 LayoutManager作用是什么?LayoutManager样式有哪些?setLayoutManager源码里做了什么?
-
LayoutManager作用是什么?
- LayoutManager的职责是摆放Item的位置,并且负责决定何时回收和重用Item。博客
- RecyclerView 允许自定义规则去放置子 view,这个规则的控制者就是 LayoutManager。一个 RecyclerView 如果想展示内容,就必须设置一个 LayoutManager
-
LayoutManager样式有哪些?
- LinearLayoutManager 水平或者垂直的Item视图。
- GridLayoutManager 网格Item视图。
- StaggeredGridLayoutManager 交错的网格Item视图。
-
setLayoutManager(LayoutManager layout)源码
- 分析:当之前设置过 LayoutManager 时,移除之前的视图,并缓存视图在 Recycler 中,将新的 mLayout 对象与 RecyclerView 绑定,更新缓存 View 的数量。最后去调用 requestLayout ,重新请求 measure、layout、draw。
public void setLayoutManager(LayoutManager layout) {
if (layout == mLayout) {
return;
}
// 停止滑动
stopScroll();
if (mLayout != null) {
// 如果有动画,则停止所有的动画
if (mItemAnimator != null) {
mItemAnimator.endAnimations();
}
// 移除并回收视图
mLayout.removeAndRecycleAllViews(mRecycler);
// 回收废弃视图
mLayout.removeAndRecycleScrapInt(mRecycler);
//清除mRecycler
mRecycler.clear();
if (mIsAttached) {
mLayout.dispatchDetachedFromWindow(this, mRecycler);
}
mLayout.setRecyclerView(null);
mLayout = null;
} else {
mRecycler.clear();
}
mChildHelper.removeAllViewsUnfiltered();
mLayout = layout;
if (layout != null) {
if (layout.mRecyclerView != null) {
throw new IllegalArgumentException("LayoutManager " + layout +
" is already attached to a RecyclerView: " + layout.mRecyclerView);
}
mLayout.setRecyclerView(this);
if (mIsAttached) {
mLayout.dispatchAttachedToWindow(this);
}
}
//更新新的缓存数据
mRecycler.updateViewCacheSize();
//重新请求 View 的测量、布局、绘制
requestLayout();
}
25.0.0.4 SnapHelper主要是做什么用的?SnapHelper是怎么实现支持RecyclerView的对齐方式?
25.0.0.5 SpanSizeLookup的作用是干什么的?SpanSizeLookup如何使用?SpanSizeLookup实现原理如何理解?
-
SpanSizeLookup的作用是干什么的?
- RecyclerView 可以通过 GridLayoutManager 实现网格布局, 但是很少有人知道GridLayoutManager 还可以用来设置网格中指定Item的列数,类似于合并单元格的功能,而所有的这些我们仅仅只需通过定义一个RecycleView列表就可以完成,要实现指定某个item所占列数的功能我们需要用到GridLayoutManager.SpanSizeLookup这个类,该类是一个抽象类,里面包含了一个getSpanSize(int position)的抽象方法,该方法的返回值就是指定position所占的列数
-
SpanSizeLookup如何使用?
- 先是定义了一个6列的网格布局,然后通过GridLayoutManager.SpanSizeLookup这个类来动态的指定某个item应该占多少列。博客
- 比如getSpanSize返回6,就表示当前position索引处的item占用6列,那么显示就只会展示一个ItemView【占用6列】。
- 比如getSpanSize返回3,就表示当前position索引处的item占用3列
GridLayoutManager manager = new GridLayoutManager(this, 6);
manager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
SpanModel model = mDataList.get(position);
if (model.getType() == 1) {
return 6;
} else if(model.getType() == 2){
return 3;
}else if (model.getType() == 3){
return 2;
}else if (model.getType() == 4){
return 2;
} else {
return 1;
}
}
});
25.0.0.6 ItemDecoration的用途是什么?自定义ItemDecoration有哪些重写方法?分析一下addItemDecoration()源码?
-
ItemDecoration的用途是什么?
- 通过设置recyclerView.addItemDecoration(new DividerDecoration(this));来改变Item之间的偏移量或者对Item进行装饰。
- 当然,你也可以对RecyclerView设置多个ItemDecoration,列表展示的时候会遍历所有的ItemDecoration并调用里面的绘制方法,对Item进行装饰。博客
-
自定义ItemDecoration有哪些重写方法
public void onDraw(Canvas c, RecyclerView parent)
装饰的绘制在Item条目绘制之前调用,所以这有可能被Item的内容所遮挡
public void onDrawOver(Canvas c, RecyclerView parent)
装饰的绘制在Item条目绘制之后调用,因此装饰将浮于Item之上
public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent)
与padding或margin类似,LayoutManager在测量阶段会调用该方法,计算出每一个Item的正确尺寸并设置偏移量。
-
分析一下addItemDecoration()源码?
-
a.通过下面代码可知,mItemDecorations是一个ArrayList,我们将ItemDecoration也就是分割线对象,添加到其中。
public void addItemDecoration(ItemDecoration decor) {
addItemDecoration(decor, -1);
}
//主要看这个方法,我的GitHub:https://github.com/yangchong211/YCBlogs
public void addItemDecoration(ItemDecoration decor, int index) {
if (mLayout != null) {
mLayout.assertNotInLayoutOrScroll("Cannot add item decoration during a scroll or"
+ " layout");
}
if (mItemDecorations.isEmpty()) {
setWillNotDraw(false);
}
if (index < 0) {
mItemDecorations.add(decor);
} else {
// 指定添加分割线在集合中的索引
mItemDecorations.add(index, decor);
}
markItemDecorInsetsDirty();
// 重新请求 View 的测量、布局、绘制
requestLayout();
}
-
总结概括博客
- 可以看到在 View 的以上两个方法中,分别调用了 ItemDecoration 对象的 onDraw onDrawOver 方法。
- 这两个抽象方法,由我们继承 ItemDecoration 来自己实现,他们区别就是 onDraw 在 item view 绘制之前调用,onDrawOver 在 item view 绘制之后调用。
- 所以绘制顺序就是 Decoration 的 onDraw,ItemView的 onDraw,Decoration 的 onDrawOver。
25.0.0.7 上拉加载更多的功能是如何做的?添加滚动监听事件需要注意什么问题?网格布局上拉加载如何优化?
25.0.0.8 RecyclerView绘制原理如何理解?性能优化本质是什么?RecyclerView绘制原理过程大概是怎样的?
25.0.0.9 RecyclerView的Recyler是如何实现ViewHolder的缓存?如何理解recyclerView三级缓存是如何实现的?
25.0.1.0 屏幕滑动(状态是item状态可见,不可见,即将可见变化)时三级缓存是如何理解的?adapter中的几个方法是如何变化?
25.0.1.1 SnapHelper有哪些重要的方法,其作用就是是什么?LinearSnapHelper中是如何实现滚动停止的?
25.0.1.2 LinearSnapHelper代码中calculateDistanceToFinalSnap作用是什么?那么out[0]和out[1]分别指什么?
-
calculateDistanceToFinalSnap的作用是什么
- 如果是水平方向滚动的,则计算水平方向需要移动的距离,否则水平方向的移动距离为0
- 如果是竖直方向滚动的,则计算竖直方向需要移动的距离,否则竖直方向的移动距离为0
- distanceToCenter方法主要作用是:计算水平或者竖直方向需要移动的距离
@Override
public int[] calculateDistanceToFinalSnap(
@NonNull RecyclerView.LayoutManager layoutManager, @NonNull View targetView) {
int[] out = new int[2];
if (layoutManager.canScrollHorizontally()) {
out[0] = distanceToCenter(layoutManager, targetView,
getHorizontalHelper(layoutManager));
} else {
out[0] = 0;
}
if (layoutManager.canScrollVertically()) {
out[1] = distanceToCenter(layoutManager, targetView,
getVerticalHelper(layoutManager));
} else {
out[1] = 0;
}
return out;
}
-
接着看看distanceToCenter方法
- 计算对应的view的中心坐标到RecyclerView中心坐标之间的距离
- 首先是找到targetView的中心坐标
- 接着也就是找到容器【RecyclerView】的中心坐标
- 两个中心坐标的差值就是targetView需要滚动的距离
private int distanceToCenter(@NonNull RecyclerView.LayoutManager layoutManager,
@NonNull View targetView, OrientationHelper helper) {
final int childCenter = helper.getDecoratedStart(targetView)
+ (helper.getDecoratedMeasurement(targetView) / 2);
final int containerCenter;
if (layoutManager.getClipToPadding()) {
containerCenter = helper.getStartAfterPadding() + helper.getTotalSpace() / 2;
} else {
containerCenter = helper.getEnd() / 2;
}
return childCenter - containerCenter;
}
-
那么out[0]和out[1]分别指什么
- 返回的是一个长度为2的int 数组out,out[0]是x方向对齐要移动的距离,out[1]是y方向对齐要移动的距离。
25.0.1.3 如何实现可以设置分割线的颜色,宽度,以及到左右两边的宽度间距的自定义分割线,说一下思路?
-
需要实现的分割线功能
- 可以设置分割线的颜色,宽度,以及到左右两边的宽度间距。item默认分割线的颜色不可改变,那么只有重写onDraw方法,通过设置画笔point颜色来绘制分割线颜色。而设置分割线左右的间隔是通过getItemOffsets方法实现的。
-
几个重要的方法说明
- 需要自定义类实现RecyclerView.ItemDecoration类,并选择重写合适方法。注意下面这三个方法有着强烈的因果关系!
//获取当前view的位置信息,该方法主要是设置条目周边的偏移量
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state)
//在item背后draw
public void onDraw(Canvas c, RecyclerView parent, State state)
//在item上边draw
public void onDrawOver(Canvas c, RecyclerView parent, State state)
-
注意的是三个方法的调用顺序
- 首先调用的是getItemOffsets会被多次调用,在layoutManager每次测量可摆放的view的时候回调用一次,在当前状态下需要摆放多少个view这个方法就会回调多少次。
- 其次会调用onDraw方法,ItemDecoration的onDraw方法是在RecyclerView的onDraw方法中调用的,注意这时候传入的canvas是RecyclerView的canvas,要时刻注意这点,它是和RecyclerView的边界是一致的。这个时候绘制的内容相当于背景,会被item覆盖。
- 最后调用的是onDrawOver方法,ItemDecoration的onDrawOver方法是在RecyclerView的draw方法中调用的,同样传入的是RecyclerView的canvas,这时候onlayout已经调用,所以此时绘制的内容会覆盖item。
-
为每个item实现索引的思路
- 要实现上面的可以设置分割线颜色和宽度,肯定是要绘制的,也就是需要使用到onDraw方法。那么在getItemOffsets方法中需要让view摆放位置距离bottom的距离是分割线的宽度。博客
- 然后通过parent.getChildCount()方法拿到当前显示的view的数量[注意,该方法并不会获取不显示的view的数量],循环遍历后,直接用paint画笔进行绘制[注意至于分割线的颜色就是需要设置画笔的颜色]。
25.0.1.4 如何实现复杂type首页需求?如果不封装会出现什么问题和弊端?如何提高代码的简便性和高效性?
25.0.1.5 关于item条目点击事件在onCreateViewHolder中写和在onBindViewHolder中写有何区别?如何优化?
25.0.1.6 RecyclerView滑动卡顿原因有哪些?如何解决嵌套布局滑动冲突?如何解决RecyclerView实现画廊卡顿?
-
RecyclerView滑动卡顿原因有哪些
-
第一种:嵌套布局滑动冲突
- 导致嵌套滑动难处理的关键原因在于当子控件消费了事件, 那么父控件就不会再有机会处理这个事件了, 所以一旦内部的滑动控件消费了滑动操作, 外部的滑动控件就再也没机会响应这个滑动操作了
-
第二种:嵌套布局层次太深,比如六七层等
- 第三种:比如用RecyclerView实现画廊,加载比较大的图片,如果快速滑动,则可能会出现卡顿,主要是加载图片需要时间
- 第四种:在onCreateViewHolder或者在onBindViewHolder中做了耗时的操作导致卡顿。
-
如何解决嵌套布局滑动冲突
-
如何解决RecyclerView实现画廊卡顿?
- RecyclerView 滑动时不让 Glide 加载图片。滚动停止后才开始恢复加载图片。
//RecyclerView.SCROLL_STATE_IDLE //空闲状态
//RecyclerView.SCROLL_STATE_FLING //滚动状态
//RecyclerView.SCROLL_STATE_TOUCH_SCROLL //触摸后状态
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
LoggerUtils.e("initRecyclerView"+ "恢复Glide加载图片");
Glide.with(ImageBrowseActivity.this).resumeRequests();
}else {
LoggerUtils.e("initRecyclerView"+"禁止Glide加载图片");
Glide.with(ImageBrowseActivity.this).pauseRequests();
}
}
});
-
在onCreateViewHolder或者在onBindViewHolder中做了耗时的操作导致卡顿
- 按stackoverflow上面比较通俗的解释:RecyclerView.Adapter里面的onCreateViewHolder()方法和onBindViewHolder()方法对时间都非常敏感。类似I/O读写,Bitmap解码一类的耗时操作,最好不要在它们里面进行。
25.0.1.7 RecyclerView常见的优化有哪些?实际开发中都是怎么做的,优化前后对比性能上有何提升?
-
RecyclerView常见的优化有哪些
-
DiffUtil刷新优化
- 分页拉取远端数据,对拉取下来的远端数据进行缓存,提升二次加载速度;对于新增或者删除数据通过 DiffUtil 来进行局部刷新数据,而不是一味地全局刷新数据。
-
布局优化
-
减少 xml 文件 inflate 时间
- 这里的 xml 文件不仅包括 layout 的 xml,还包括 drawable 的 xml,xml 文件 inflate 出 ItemView 是通过耗时的 IO 操作,尤其当 Item 的复用几率很低的情况下,随着 Type 的增多,这种 inflate 带来的损耗是相当大的,此时我们可以用代码去生成布局,即 new View() 的方式,只要搞清楚 xml 中每个节点的属性对应的 API 即可。
-
减少 View 对象的创建
- 一个稍微复杂的 Item 会包含大量的 View,而大量的 View 的创建也会消耗大量时间,所以要尽可能简化 ItemView;设计 ItemType 时,对多 ViewType 能够共用的部分尽量设计成自定义 View,减少 View 的构造和嵌套。博客
-
对itemView中孩子View的点击事件优化
- onBindViewHolder() 中频繁创建新的 onClickListener 实例没有必要,建议实际开发中应该在 onCreateViewHolder() 中每次为新建的 View 设置一次就行。
-
其他的一些优化点
25.0.1.8 如何解决RecyclerView嵌套RecyclerView条目自动上滚的Bug?如何解决ScrollView嵌套RecyclerView滑动冲突?
25.0.1.9 如何处理ViewPager嵌套水平RecyclerView横向滑动到底后不滑动ViewPager?如何解决RecyclerView使用Glide加载图片导致图片错乱问题?
-
ViewPager嵌套水平RecyclerView横向滑动到底后不滑动ViewPager
- 继承RecyclerView,重写dispatchTouchEvent,根据ACTION_MOVE的方向判断是否调用getParent().requestDisallowInterceptTouchEvent去阻止父view拦截点击事件
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
/*---解决垂ViewPager嵌套直RecyclerView嵌套水平RecyclerView横向滑动到底后不滑动ViewPager start ---*/
ViewParent parent = this;
while(!((parent = parent.getParent()) instanceof ViewPager));
// 循环查找viewPager
parent.requestDisallowInterceptTouchEvent(true);
return super.dispatchTouchEvent(ev);
}
-
如何解决RecyclerView使用Glide加载图片导致图片错乱问题