嗨,你們好,最近我去淘了一些關於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
的問題下次有機會我會再詳細的說一下,配合圖片日誌。
最後但願你們好好鞏固知識,加油。
有一塊兒學習的小夥伴能夠關注下❤️個人公衆號——碼上積木,天天剖析一個知識點,咱們一塊兒積累知識。