recyclerview使用詳解,ListView和RecycleView的性能比對

 2023-09-30 阅读 23 评论 0

摘要:ListView相比RecyclerView,有一些優點: addHeaderView(), addFooterView()添加頭視圖和尾視圖。通過”android:divider”設置自定義分割線。setOnItemClickListener()和setOnItemLongClickListener()設置點擊事件和長按事件。 這些功能在RecyclerView中都沒有直

ListView相比RecyclerView,有一些優點

  • addHeaderView(), addFooterView()添加頭視圖和尾視圖。
  • 通過”android:divider”設置自定義分割線。
  • setOnItemClickListener()setOnItemLongClickListener()設置點擊事件和長按事件。

這些功能在RecyclerView中都沒有直接的接口,要自己實現(雖然實現起來很簡單),因此如果只是實現簡單的顯示功能,ListView無疑更簡單。

RecyclerView相比ListView,有一些明顯的優點

  • 默認實現View復用,不需要類似if(convertView == null)的實現,而且回收機制更加完善。
  • 默認支持局部刷新
  • 容易實現添加item、刪除item動畫效果。
  • 容易實現拖拽、側滑刪除等功能。

RecyclerView是一個插件式的實現,對各個功能進行解耦,從而擴展性比較好。

局部刷新

ListView實現局部刷新

我們都知道ListView通過adapter.notifyDataSetChanged()實現ListView的更新,這種更新方法的缺點是全局更新,即對每個Item View都進行重繪。但事實上很多時候,我們只是更新了其中一個Item的數據,其他Item其實可以不需要重繪。

recyclerview使用詳解?這里給出ListView實現局部更新的方法:

public void updateItemView(ListView listview, int position, Data data){int firstPos = listview.getFirstVisiblePosition();int lastPos = listview.getLastVisiblePosition();if(position >= firstPos && position <= lastPos){  //可見才更新,不可見則在getView()時更新//listview.getChildAt(i)獲得的是當前可見的第i個item的viewView view = listview.getChildAt(position - firstPos);VH vh = (VH)view.getTag();vh.text.setText(data.text);}
}

可以看出,我們通過ListView的getChildAt()來獲得需要更新的View,然后通過getTag()獲得ViewHolder,從而實現更新。

RecyclerView實現局部刷新

RecyclerView提供了notifyItemInserted(),notifyItemRemoved(),notifyItemChanged()等API更新單個或某個范圍的Item視圖。

緩存機制

ListView與RecyclerView緩存機制原理大致相似,如下圖所示:

image

recyclerview跳轉到指定位置,過程中,離屏的ItemView即被回收至緩存,入屏的ItemView則會優先從緩存中獲取,只是ListView與RecyclerView的實現細節有差異.(這只是緩存使用的其中一個場景,還有如刷新等)

緩存機制對比

1. 層級不同:

RecyclerView比ListView多兩級緩存,支持多個離ItemView緩存,支持開發者自定義緩存處理邏輯,支持所有RecyclerView共用同一個RecyclerViewPool(緩存池)。

具體來說:
ListView(兩級緩存):

image

listview用法?RecyclerView(四級緩存):

image

ListView和RecyclerView緩存機制基本一致:

1). mActiveViews和mAttachedScrap功能相似,意義在于快速重用屏幕上可見的列表項ItemView,而不需要重新createView和bindView;

2). mScrapView和mCachedViews + mReyclerViewPool功能相似,意義在于緩存離開屏幕的ItemView,目的是讓即將進入屏幕的ItemView重用.

listview和recyclerview的區別?3). RecyclerView的優勢在于

  • mCacheViews的使用,可以做到屏幕外的列表項ItemView進入屏幕內時也無須bindView快速重用;
  • mRecyclerPool可以供多個RecyclerView共同使用,在特定場景下,如viewpaper+多個列表頁下有優勢。

客觀來說,RecyclerView在特定場景下對ListView的緩存機制做了補強和完善。

2. 緩存不同:

1). RecyclerView緩存RecyclerView.ViewHolder,抽象可理解為:
View + ViewHolder(避免每次createView時調用findViewById) + flag(標識狀態);
2). ListView緩存View。

緩存不同,二者在緩存的使用上也略有差別,具體來說:
ListView獲取緩存的流程:

recyclerview是什么,image

RecyclerView獲取緩存的流程:

image

1). RecyclerView中mCacheViews(屏幕外)獲取緩存時,是通過匹配pos獲取目標位置的緩存,這樣做的好處是,當數據源數據不變的情況下,無須重新bindView:

?

recyclerview嵌套recyclerview、

?

而同樣是離屏緩存,ListView從mScrapViews根據pos獲取相應的緩存,但是并沒有直接使用,而是重新getView(即必定會重新bindView),相關代碼如下:

//AbsListView源碼:line2345
//通過匹配pos從mScrapView中獲取緩存
final View scrapView = mRecycler.getScrapView(position);
//無論是否成功都直接調用getView,導致必定會調用createView
final View child = mAdapter.getView(position, scrapView, this);
if (scrapView != null) {if (child != scrapView) {mRecycler.addScrapView(scrapView, position);} else {...}
}

2). ListView中通過pos獲取的是view,即pos–>view;
RecyclerView中通過pos獲取的是viewholder,即pos –> (view,viewHolder,flag);
從流程圖中可以看出,標志flag的作用是判斷view是否需要重新bindView,這也是RecyclerView實現局部刷新的一個核心.

局部刷新

由上文可知,RecyclerView的緩存機制確實更加完善,但還不算質的變化,RecyclerView更大的亮點在于提供了局部刷新的接口,通過局部刷新,就能避免調用許多無用的bindView.

?

recyclerview緩存機制。


(RecyclerView和ListView添加,移除Item效果對比)

?

結合RecyclerView的緩存機制,看看局部刷新是如何實現的:
以RecyclerView中notifyItemRemoved(1)為例,最終會調用requestLayout(),使整個RecyclerView重新繪制,過程為:
onMeasure()–>onLayout()–>onDraw()

其中,onLayout()為重點,分為三步:

  1. dispathLayoutStep1():記錄RecyclerView刷新前列表項ItemView的各種信息,如Top,Left,Bottom,Right,用于動畫的相關計算;
  2. dispathLayoutStep2():真正測量布局大小,位置,核心函數為layoutChildren();
  3. dispathLayoutStep3():計算布局前后各個ItemView的狀態,如Remove,Add,Move,Update等,如有必要執行相應的動畫.

其中,layoutChildren()流程圖:

recyclerview原理?

image

image

當調用notifyItemRemoved時,會對屏幕內ItemView做預處理,修改ItemView相應的pos以及flag(流程圖中紅色部分):

recyclerView,image

當調用fill()中RecyclerView.getViewForPosition(pos)時,RecyclerView通過對pos和flag的預處理,使得bindview只調用一次.

需要指出,ListView和RecyclerView最大的區別在于數據源改變時的緩存的處理邏輯,ListView是”一鍋端”,將所有的mActiveViews都移入了二級緩存mScrapViews,而RecyclerView則是更加靈活地對每個View修改標志位,區分是否重新bindView。

回收機制源碼分析

ListView回收機制

ListView為了保證Item View的復用,實現了一套回收機制,該回收機制的實現類是RecycleBin,他實現了兩級緩存:

  • View[] mActiveViews: 緩存屏幕上的View,在該緩存里的View不需要調用getView()
  • ArrayList<View>[] mScrapViews;: 每個Item Type對應一個列表作為回收站,緩存由于滾動而消失的View,此處的View如果被復用,會以參數的形式傳給getView()

接下來我們通過源碼分析ListView是如何與RecycleBin交互的。其實ListView和RecyclerView的layout過程大同小異,ListView的布局函數是layoutChildren(),實現如下:

void layoutChildren(){//1. 如果數據被改變了,則將所有Item View回收至scrapView  //(而RecyclerView會根據情況放入Scrap Heap或RecyclePool);否則回收至mActiveViewsif (dataChanged) {for (int i = 0; i < childCount; i++) {recycleBin.addScrapView(getChildAt(i), firstPosition+i);}} else {recycleBin.fillActiveViews(childCount, firstPosition);}//2. 填充switch(){case LAYOUT_XXX:fillXxx();break;case LAYOUT_XXX:fillXxx();break;}//3. 回收多余的activeViewmRecycler.scrapActiveViews();
}

橫向recyclerview、其中fillXxx()實現了對Item View進行填充,該方法內部調用了makeAndAddView(),實現如下:

View makeAndAddView(){if (!mDataChanged) {child = mRecycler.getActiveView(position);if (child != null) {return child;}}child = obtainView(position, mIsScrap);return child;
}

其中,getActiveView()是從mActiveViews中獲取合適的View,如果獲取到了,則直接返回,而不調用obtainView(),這也印證了如果從mActiveViews獲取到了可復用的View,則不需要調用getView()

obtainView()是從mScrapViews中獲取合適的View,然后以參數形式傳給了getView(),實現如下:

View obtainView(int position){final View scrapView = mRecycler.getScrapView(position);  //從RecycleBin中獲取復用的Viewfinal View child = mAdapter.getView(position, scrapView, this);
}

接下去我們介紹getScrapView(position)的實現,該方法通過position得到Item Type,然后根據Item Type從mScrapViews獲取可復用的View,如果獲取不到,則返回null,具體實現如下:

class RecycleBin{private View[] mActiveViews;    //存儲屏幕上的Viewprivate ArrayList<View>[] mScrapViews;  //每個item type對應一個ArrayListprivate int mViewTypeCount;            //item type的個數private ArrayList<View> mCurrentScrap;  //mScrapViews[0]View getScrapView(int position) {final int whichScrap = mAdapter.getItemViewType(position);if (whichScrap < 0) {return null;}if (mViewTypeCount == 1) {return retrieveFromScrap(mCurrentScrap, position);} else if (whichScrap < mScrapViews.length) {return retrieveFromScrap(mScrapViews[whichScrap], position);}return null;}private View retrieveFromScrap(ArrayList<View> scrapViews, int position){int size = scrapViews.size();if(size > 0){return scrapView.remove(scrapViews.size() - 1);  //從回收列表中取出最后一個元素復用} else{return null;}}
}

RecyclerView回收機制

RecyclerView和ListView的回收機制非常相似,但是ListView是以View作為單位進行回收RecyclerView是以ViewHolder作為單位進行回收
Recycler是RecyclerView回收機制的實現類,他實現了四級緩存:

  • mAttachedScrap: 緩存在屏幕上的ViewHolder。
  • mCachedViews: 緩存屏幕外的ViewHolder,默認為2個。ListView對于屏幕外的緩存都會調用getView()
  • mViewCacheExtensions: 需要用戶定制,默認不實現。
  • mRecyclerPool: 緩存池,多個RecyclerView共用。

recyclerview多布局、在上文Layout Manager中已經介紹了RecyclerView的layout過程,但是一筆帶過了getViewForPosition(),因此此處介紹該方法的實現。

View getViewForPosition(int position, boolean dryRun){if(holder == null){//從mAttachedScrap,mCachedViews獲取ViewHolderholder = getScrapViewForPosition(position,INVALID,dryRun); //此處獲得的View不需要bind}final int type = mAdapter.getItemViewType(offsetPosition);if (mAdapter.hasStableIds()) { //默認為falseholder = getScrapViewForId(mAdapter.getItemId(offsetPosition), type, dryRun);}if(holder == null && mViewCacheExtension != null){final View view = mViewCacheExtension.getViewForPositionAndType(this, position, type); //從if(view != null){holder = getChildViewHolder(view);}}if(holder == null){holder = getRecycledViewPool().getRecycledView(type);}if(holder == null){  //沒有緩存,則創建holder = mAdapter.createViewHolder(RecyclerView.this, type); //調用onCreateViewHolder()}if(!holder.isBound() || holder.needsUpdate() || holder.isInvalid()){mAdapter.bindViewHolder(holder, offsetPosition);}return holder.itemView;
}

從上述實現可以看出,依次從mAttachedScrap, mCachedViews, mViewCacheExtension, mRecyclerPool尋找可復用的ViewHolder,如果是從mAttachedScrap或mCachedViews中獲取的ViewHolder,則不會調用onBindViewHolder(),mAttachedScrap和mCachedViews也就是我們所說的Scrap Heap;而如果從mViewCacheExtension或mRecyclerPool中獲取的ViewHolder,則會調用onBindViewHolder()

RecyclerView局部刷新的實現原理也是基于RecyclerView的回收機制,即能直接復用的ViewHolder就不調用onBindViewHolder()


轉載:https://www.jianshu.com/p/4f9591291365

版权声明:本站所有资料均为网友推荐收集整理而来,仅供学习和研究交流使用。

原文链接:https://hbdhgg.com/4/106902.html

发表评论:

本站为非赢利网站,部分文章来源或改编自互联网及其他公众平台,主要目的在于分享信息,版权归原作者所有,内容仅供读者参考,如有侵权请联系我们删除!

Copyright © 2022 匯編語言學習筆記 Inc. 保留所有权利。

底部版权信息