1.什么是RecyclerView?adapter的作用是什么,几个方法是做什么用的?如何理解adapter订阅者模式?
- RecyclerView .adapter --处理数据集合并负责绑定视图
- ViewHolder --持有所有的用于绑定数据或者需要操作的view
- LayoutManager --负责摆放视图等相关操作
- itemDroitemDecoration --负责绘制item附近的分割线
- ItemItemAnimation -- 为item 的一般操作添加动画效果 如 增删条目等
RecyclerView.adapter
① 根据不同的ViewType创建与之对应的item-Layout
② 访问数据集合并将数据绑定到正确的view上
几个方法的作用
onCreateViewViewHolder(ViewGroup parent ,int viewType) --创建Item视图,并放回相应的ViewHolder
onBindViewHolder(VH holder ,int position)--绑定数据到正确的item上
getItemCount() --返回该item所持有的数量
getItemViewType(int position) --用来获取当前项Item(int 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();
}
当数据变更时,调用notify**方法时,Adapter内部的被观察者会遍历通知已经注册的观察者的对应方法,这时界面就会响应变更。
2. ViewHolder的作用是什么?如何理解ViewHolder的复用?什么时候停止调用onCreateViewHolder?
ViewHolder作用:
- adapter拥有ViewHolder的子类,且ViewHolder内部存储子View,避免耗时的findViewById的操作
- recyclerView定义的内部类包含很多属性种类多,使用场景也很多,经常使用到的onCreateViewHolder和onBindViewHolder方法,onCreateViewHolder()在recyclerViewHolder中需要一个新类型,item的ViewHolder调用时创建一个ViewHolder,而onBindViewHolder则需要recyclerView在特定位置的item展示数据时调用
ViewHolder复用:就是复写onCreateViewHolder 和onBindViewHolder的方法
3. ViewHolder中为何使用SparseArray替代HashMap存储viewId?
hashmapHashMap.Entry的数组,entry类可以包含的字段
- 一个非基本数据类型key
- 一个非基本数据类型的value
- 保存对象的哈希值
- 指向下一个entry的指针
当有键值对插入时,键的哈希值会被计算出来,这个值会被赋给entry类的hashcode变量,使用这个哈希值找到它将要被存入的数组中“”的索引
在Android 中 涉及都快速响应的应用时,持续的分发和释放内存会触发垃圾回收机制,因此会拖慢引用运行,而垃圾回收机制会影响应用性能表现,回收时间段内,应用不会运行,因此会造成应用卡顿
sparseArray:使用两个数组int[] keys 和 object[] values,来存储key和value
相较于hashmap,舍弃了entry和object类型的key,放弃hashcode并使用二分法查找,在添加和操作的时候有更好的性能
4. LayoutManager作用是什么?LayoutManager样式有哪些?setLayoutManager源码里做了什么?
layoutManager 作用:拜访item位置,并负责决定何时回收和重用item ,recyclerView 允许自定义规则去放置view,控制者就是layoutManager,RecyclerView要展示内容,就必须设置layoutManager
layoutManager样式:
- LinearLayoutManager --水平或垂直item视图
- GridLayoutManager --网格item视图
- StaggeredGridLayoutManager --交错item视图
setLayoutManager 源码
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();
}
如果之前设置过LayoutManager,移除之前的视图并缓存视图在recyclerView ,将新的mLayout对象与recyclerView绑定,更新缓存view的数量,最后调用requestLayout重新请求measure 、layout 、draw
5. SnapHelper主要是做什么用的?SnapHelper是怎么实现支持RecyclerView的对齐方式?
6. SpanSizeLookup的作用是干什么的?SpanSizeLookup如何使用?SpanSizeLookup实现原理如何理解?
请参考:https://juejin.im/post/5cce410551882541e40e471d
8. 上拉加载更多的功能是如何做的?添加滚动监听事件需要注意什么问题?网格布局上拉加载如何优化?
上拉加载功能:
- 添加recyclerView滑动事件:设置滑动监听,RecyclerView自带scrollListener,获取最后一个完全显示的itemposition,然后判断是否滑动到了最后一个
- 上拉加载分页数据:更新上拉加载更多数据的方法,可以使用notifyItemRangeInserted方法,
- 设置底部上拉加载footer布局:在adapter中可以上拉加载时处理footView 的逻辑
- 显示和隐藏footer布局
网格布局上拉加载优化:在adapter的onAtteronAttachedToRecycleView方法中处理网格布局情况。逻辑如果当前是footer的位置,该item占据两个单元格,正常情况下是占据一个单元格
@Override
public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) {
super.onAttachedToRecyclerView(recyclerView);
RecyclerView.LayoutManager manager = recyclerView.getLayoutManager();
if (manager instanceof GridLayoutManager) {
final GridLayoutManager gridManager = ((GridLayoutManager) manager);
gridManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
// 如果当前是footer的位置,那么该item占据2个单元格,正常情况下占据1个单元格
return getItemViewType(position) == footType ? gridManager.getSpanCount() : 1;
}
});
}
}
如何实现自动进行上拉刷新?设置滑动监听,判断是否滑动到底部,是否是最后一条数据,如果是开始加载下一页数据,并且显示加载下一页loading,当加载数据成功后,隐藏该布局
如何实现手动上拉刷新?当滑动到最后一条数据时,显示加载更多布局,然后设置它的点击事件,点击之后开始加载下一页数据,加载完成后隐藏该布局
9. RecyclerView绘制原理如何理解?性能优化本质是什么?RecyclerView绘制原理过程大概是怎样的?
10 RecyclerView的Recyler是如何实现ViewHolder的缓存?如何理解recyclerView三级缓存是如何实现的?
public final class Recycler {
final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
ArrayList<ViewHolder> mChangedScrap = null;
final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();
private final List<ViewHolder>
mUnmodifiableAttachedScrap = Collections.unmodifiableList(mAttachedScrap);
private int mRequestedCacheMax = DEFAULT_CACHE_SIZE;
int mViewCacheMax = DEFAULT_CACHE_SIZE;
RecycledViewPool mRecyclerPool;
private ViewCacheExtension mViewCacheExtension;
static final int DEFAULT_CACHE_SIZE = 2;
}
三级缓存实现:recyclerView在设计的时候对象分为了三级,每次创建ViewHolder的时候,会按照优先级依次查询缓存创建ViewHolder。
- 一级缓存:返回布局和内容都有效的ViewHolder
- 按照position或者id进行匹配
- 一级缓存无效onCreateViewHolder和onBindViewHolder
- onAttachedScrap 在adapter.noti..的时候调用
- mChangedScrap 在每次View绘制的时候用到,getViewHolderForPosition非调用多次
- mCamCachedView :用来解决滑动抖动的情况,默认值为2
- 二级缓存:返回view
- 按照position或者type匹配
- 直接返回view
- 需要继承自己的viewCacheExtention实现
- 位置固定,内容不发生改变情况,比如说headr如果内容固定,就可以使用
- 三集缓存:返回布局有效,内容无效的ViewHolder
- 按照type进行匹配,type默认值为5
- layout是有效的,但内容是无效的
- 多个recyclerView可共享,可用于多个recyclerView的优化
11 屏幕滑动(状态是item状态可见,不可见,即将可见变化)时三级缓存是如何理解的?adapter中的几个方法是如何变化?
12 SnapHelper有哪些重要的方法,其作用就是是什么?LinearSnapHelper中是如何实现滚动停止的?
13 如何实现可以设置分割线的颜色,宽度,以及到左右两边的宽度间距的自定义分割线,说一下思路?
14 如何实现复杂type首页需求?如果不封装会出现什么问题和弊端?如何提高代码的简便性和高效性?
15 关于item条目点击事件在onCreateViewHolder中写和在onBindViewHolder中写有何区别?如何优化?
16 RecyclerView滑动卡顿原因有哪些?如何解决嵌套布局滑动冲突?如何解决RecyclerView实现画廊卡顿?
滑动卡顿原因:
- 嵌套布局滑动冲突:子控件消费了滑动事件,父控件就不会在处理这个事件,如果内部滑动控件消费了滑动操作,外部的滑动操作将不会执行
- 嵌套布局层次太深:过度测量和过度绘制
- recyclerView实现画廊,加载大图,如果快速滑动,会出现卡顿现象,主要是加载图片需要时间,
- onCreateViewHolder和onBindViewHolder中进行了耗时操作
解决滑动冲突:可参考
https://www.jianshu.com/p/5dfc90656665
17 RecyclerView常见的优化有哪些?实际开发中都是怎么做的,优化前后对比性能上有何提升
优化:
- DiffUtil优化:分页拉取远端数据,并对数据进行缓存,提升二次加载速度,对于新增或删除的数据通过DiffUtil进行局部刷新
- 布局优化: 减少item文件inflate
- 减少view对象的创建
- 对itemView中子View的点击事件进行优化
18 如何解决RecyclerView嵌套RecyclerView条目自动上滚的Bug?如何解决ScrollView嵌套RecyclerView滑动冲突
- recyclerView去除焦点
recyclerview.setFocusableInTouchMode(false); recyclerview.requestFocus();
- 直接在父布局中添加descendantFocusability属性 :android:descendantFocusability="beforeDescendants"
-
beforeDescendants:viewgroup会优先其子类控件而获取到焦点
afterDescendants:viewgroup只有当其子类控件不需要获取焦点时才获取焦点
blocksDescendants:viewgroup会覆盖子类控件而直接获得焦点
-