嗨,你们好,最近我去淘了一些关于RecycleView的面试真题,你们一块儿看看吧,此次的问题若是都弄懂了,下次面试再遇到RecycleView应该就没啥可担忧的了。java
RecyclerView
的缓存机制,滑动10个,再滑回去,会有几个执行onBindView
。缓存的是什么?cachedView
会执行onBindView吗?RecyclerView
预取机制RecyclerView
的局部更新,用过payload
吗,notifyItemChange方法中的参数?RecyclerView
嵌套RecyclerView
滑动冲突,NestScrollView嵌套RecyclerView。这两个问题都是关于缓存的,我就一块儿说了。面试
1)首先说下RecycleView的缓存结构:缓存
Recycleview有四级缓存,分别是mAttachedScrap(屏幕内),mCacheViews(屏幕外),mViewCacheExtension(自定义缓存),mRecyclerPool(缓存池)
性能优化
mAttachedScrap(屏幕内)
,用于屏幕内itemview快速重用,不须要从新createView和bindViewmCacheViews(屏幕外)
,保存最近移出屏幕的ViewHolder,包含数据和position信息,复用时必须是相同位置的ViewHolder才能复用,应用场景在那些须要来回滑动的列表中,当往回滑动时,能直接复用ViewHolder数据,不须要从新bindView。mViewCacheExtension(自定义缓存)
,不直接使用,须要用户自定义实现,默认不实现。mRecyclerPool(缓存池)
,当cacheView满了后或者adapter被更换,将cacheView中移出的ViewHolder放到Pool中,放以前会把ViewHolder数据清除掉,因此复用时须要从新bindView。2)四级缓存按照顺序须要依次读取。因此完整缓存流程是:ide
itemView
时,先把屏幕内的ViewHolder保存至AttachedScrap
中CacheView
,CacheView大小默认是2,超过数量的话按照先入先出原则,移出头部的itemview保存到RecyclerPool缓存池
(若是有自定义缓存就会保存到自定义缓存里),RecyclerPool缓存池会按照itemview的itemtype
进行保存,每一个itemType缓存个数为5个,超过就会被回收。CacheView
中获取,也是经过pos获取holder缓存自定义缓存
中获取缓存——>获取失败,从mRecyclerPool
中获取viewholder
——createViewHolder并bindview。3)了解了缓存结构和缓存流程,咱们再来看看具体的问题
滑动10个,再滑回去,会有几个执行onBindView?布局
onBindView
的只有一种缓存区,就是缓存池mRecyclerPool
。因此咱们假设从加载RecyclView
开始盘的话(页面假设能够容纳7条数据):post
onCreateViewHolder
和onBindViewHolder
。mCacheViews
中。此时mCacheViews
缓存区数量为1,mRecyclerPool
数量为0。而后新出现的position=7的数据经过postion在mCacheViews
中找不到对应的ViewHolder
,经过itemtype
也在mRecyclerPool
中找不到对应的数据,因此会调用onCreateViewHolder
和onBindViewHolder
方法。mCacheViews
中,可是因为mCacheViews
缓存区默认容量为2,因此position=0的数据会被清空数据而后放到mRecyclerPool
缓存池中。而新出现的position=9数据因为在mRecyclerPool
中仍是找不到相应type的ViewHolder,因此仍是会走onCreateViewHolder
和onBindViewHolder
方法。因此此时mCacheViews
缓存区数量为2,mRecyclerPool
数量为1。mRecyclerPool
中找到相同viewtype的ViewHolder了。因此就直接复用了,并调用onBindViewHolder
方法绑定数据。mCacheViews
中,再出现的时候是不会调用onBindViewHolder方法,而复用的第三条数据是从mRecyclerPool
中取得,就会调用onBindViewHolder
方法了。4)因此这个问题就得出结论了(假设mCacheViews
容量为默认值2):性能
若是一开始滑动的是新数据,那么滑动10个,就会走10个bindview
方法。而后滑回去,会走10-2个bindview
方法。一共18次调用。学习
若是一开始滑动的是老数据,那么滑动10-2个,就会走8个bindview
方法。而后滑回去,会走10-2个bindview
方法。一共16次调用。fetch
可是可是,实际状况又有点不同。由于Recycleview
在v25版本引入了一个新的机制,预取机制
。
预取机制
,就是在滑动过程当中,会把将要展现的一个元素提早缓存到mCachedViews
中,因此滑动10个元素的时候,第11个元素也会被建立,也就多走了一次bindview
方法。可是滑回去的时候不影响,由于就算提早取了一个缓存数据,只是把bindview
方法提早了,并不影响总的绑定item数量。
因此滑动的是新数据的状况下就会多一次调用bindview
方法。
5)总结,问题怎么答呢?
bindview
能够是19次调用,能够是16次调用。viewholder
。cachedView
就是mCacheViews
缓存区中的view,是不须要从新绑定数据的。关于RecycleView的数据更新,主要有如下几个方法:
notifyDataSetChanged()
,刷新所有可见的item。notifyItemChanged(int)
,刷新指定item。notifyItemRangeChanged(int,int)
,从指定位置开始刷新指定个item。notifyItemInserted(int)、notifyItemMoved(int)、notifyItemRemoved(int)
。插入、移动一个并自动刷新。notifyItemChanged(int, Object)
,局部刷新。能够看到,关于view的局部刷新就是notifyItemChanged(int, Object)方法,下面具体说说:
notifyItemChange
有两个构造方法:
其中payload
参数能够认为是你要刷新的一个标示,好比我有时候只想刷新itemView
中的textview
,有时候只想刷新imageview
?又或者我只想某一个view的文字颜色进行高亮设置?那么我就能够经过payload
参数来标示这个特殊的需求了。
具体怎么作呢?好比我调用了notifyItemChanged(14,"changeColor")
,那么在onBindViewHolder
回调方法中作下判断便可:
@Override public void onBindViewHolder(ViewHolderholder, int position, List<Object> payloads) { if (payloads.isEmpty()) { // payloads为空,说明是更新整个ViewHolder onBindViewHolder(holder, position); } else { // payloads不为空,这只更新须要更新的View便可。 String payload = payloads.get(0).toString(); if ("changeColor".equals(payload)) { holder.textView.setTextColor(""); } } }
1)RecyclerView
嵌套RecyclerView
的状况下,若是二者都要上下滑动,那么就会引发滑动冲突。默认状况下外层的RecycleView可滑,内层不可滑。
以前说过解决滑动冲突的办法有两种:内部拦截法和外部拦截法。
这里我提供一种内部拦截法,还有一些其余的办法你们能够本身思考下。
holder.recyclerView.setOnTouchListener { v, event -> when(event.action){ //当按下操做的时候,就通知父view不要拦截,拿起操做就设置能够拦截,正常走父view的滑动。 MotionEvent.ACTION_DOWN,MotionEvent.ACTION_MOVE -> v.parent.requestDisallowInterceptTouchEvent(true) MotionEvent.ACTION_UP -> v.parent.requestDisallowInterceptTouchEvent(false) } false}
2)关于ScrclerView
的滑动冲突仍是一样的解决办法,就是进行事件拦截。
还有一个办法就是用Nestedscrollview
代替ScrollView
,Nestedscrollview
是官方为了解决滑动冲突问题而设计的新的View。它的定义就是支持嵌套滑动的ScrollView。
因此直接替换成Nestedscrollview
就能保证二者都能正常滑动了。可是要注意设置RecyclerView.setNestedScrollingEnabled(false)
这个方法,用来取消RecyclerView自己的滑动效果。
这是由于RecyclerView默认是setNestedScrollingEnabled(true)
,这个方法的含义是支持嵌套滚动的。也就是说当它嵌套在NestedScrollView
中时,默认会随着NestedScrollView
滚动而滚动,放弃了本身的滚动。因此给咱们的感受就是滞留、卡顿。因此咱们将它设置为false就解决了卡顿问题,让他正常的滑动,不受外部影响。
bindViewHolder
方法是在UI线程进行的,此方法不能耗时操做,否则将会影响滑动流畅性。好比进行日期的格式化。diffutil
进行局部刷新,少用全局刷新itemVIew
进行布局优化,好比少嵌套等。 Prefetch
功能,也就是预取功能,嵌套时且使用的是LinearLayoutManager,子RecyclerView可经过setInitialPrefatchItemCount设置预取个数RecyclerView缓存
,好比cacheview大小默认为2,能够设置大点,用空间来换取时间,提升流畅度setHasFixedSize(true)
来避免requestLayout浪费资源,不然每次更新数据都会从新测量高度。void onItemsInsertedOrRemoved() { if (hasFixedSize) layoutChildren(); else requestLayout(); }
RecycledView
的 Adapter 是同样的,好比嵌套的 RecyclerView 中存在同样的 Adapter,能够经过设置 RecyclerView.setRecycledViewPool(pool);
来共用一个 RecycledViewPool
。这样就减小了建立VIewholder的开销。getExtraLayoutSpace
方法便可。new LinearLayoutManager(this) { @Override protected int getExtraLayoutSpace(RecyclerView.State state) { return size; } };
RecyclerView.addOnScrollListener();
来在滑动过程当中中止加载的操做。CreateView
里面去建立监听,由于CreateView调用要少于bindview。这样就减小了对象建立所形成的消耗notifyDataSetChange
时,适配器不知道整个数据集中的那些内容以及存在,再从新匹配ViewHolder
时会花生闪烁。设置adapter.setHasStableIds(true),并重写getItemId()
来给每一个Item一个惟一的ID,也就是惟一标识,就使itemview的焦点固定,解决了闪烁问题。今天聊了很多,关于RecycleView重要的知识点应该都涉及到了,其中bindview
的问题下次有机会我会再详细的说一下,配合图片日志。
最后但愿你们好好巩固知识,加油。
有一块儿学习的小伙伴能够关注下❤️个人公众号——码上积木,天天剖析一个知识点,咱们一块儿积累知识。