RecyclerView

這篇文章分三個(gè)部分,簡(jiǎn)單跟大家講一下 RecyclerView 的常用方法與奇葩用法;工作原理與ListView比較;源碼解析;

常用方法

RecyclerView 與 ListView、GridView 類(lèi)似,都是可以顯示同一種類(lèi)型 View 的集合的控件。

首先看看最簡(jiǎn)單的用法,四步走:

0.接入 build.gradle 文件中加入

compile'com.android.support:recyclerview-v7:24.0.0'

1.創(chuàng)建對(duì)象

RecyclerViewrecyclerview = (RecyclerView) findViewById(R.id.recyclerview);

2.設(shè)置顯示規(guī)則

recyclerview.setLayoutManager(newLinearLayoutManager(this, LinearLayoutManager.VERTICAL,false));

RecyclerView 將所有的顯示規(guī)則交給一個(gè)叫LayoutManager的類(lèi)去完成了。

LayoutManager是一個(gè)抽象類(lèi),系統(tǒng)已經(jīng)為我們提供了三個(gè)默認(rèn)的實(shí)現(xiàn)類(lèi),分別是LinearLayoutManager、GridLayoutManagerStaggeredGridLayoutManager,從名字我們就能看出來(lái)了,分別是,線(xiàn)性顯示、網(wǎng)格顯示、瀑布流顯示。當(dāng)然你也可以通過(guò)繼承這些類(lèi)來(lái)擴(kuò)展實(shí)現(xiàn)自己的LayougManager。

3.設(shè)置適配器

recyclerview.setAdapter(adapter);

適配器,同ListView一樣,用來(lái)設(shè)置每個(gè)item顯示內(nèi)容的。

通常,我們寫(xiě)ListView適配器,都是首先繼承BaseAdapter,實(shí)現(xiàn)四個(gè)抽象方法,創(chuàng)建一個(gè)靜態(tài)ViewHolder,getView()方法中判斷convertView是否為空,創(chuàng)建還是獲取viewholder對(duì)象。

RecyclerView也是類(lèi)似的步驟,首先繼承RecyclerView.Adapter類(lèi),實(shí)現(xiàn)三個(gè)抽象方法,創(chuàng)建一個(gè)靜態(tài)的ViewHolder。不過(guò)RecyclerViewViewHolder創(chuàng)建稍微有些限制,類(lèi)名就是上面繼承的時(shí)候泛型中聲明的類(lèi)名(好像反了,應(yīng)該是上面泛型中的類(lèi)名應(yīng)該是這個(gè)holder的類(lèi)名);并且ViewHolder必須繼承自RecyclerView.ViewHolder類(lèi)。

publicclassDemoAdapterextendsRecyclerView.Adapter{privateList dataList;privateContext context;publicDemoAdapter(Context context, ArrayList datas){this.dataList = datas;this.context = context;? ? }@OverridepublicVHonCreateViewHolder(ViewGroup parent,intviewType){returnnewVH(View.inflate(context, android.R.layout.simple_list_item_2,null));? ? }@OverridepublicvoidonBindViewHolder(VH holder,intposition){? ? ? ? holder.mTextView.setText(dataList.get(position).getNum());? ? }@OverridepublicintgetItemCount(){returndataList.size();? ? }publicstaticclassVHextendsRecyclerView.ViewHolder{? ? ? ? TextView mTextView;publicVH(View itemView){super(itemView);? ? ? ? ? ? mTextView = (TextView) itemView.findViewById(android.R.id.text1);? ? ? ? }? ? }}

更多方法

除了常用方法,當(dāng)然還有不常用的。

瀑布流與滾動(dòng)方向

前面已經(jīng)介紹過(guò),RecyclerView實(shí)現(xiàn)瀑布流,可以通過(guò)一句話(huà)設(shè)置:recycler.setLayoutManager(new StaggeredGridLayoutManager(2, VERTICAL))就可以了。

其中StaggeredGridLayoutManager第一個(gè)參數(shù)表示列數(shù),就好像GridView的列數(shù)一樣,第二個(gè)參數(shù)表示方向,可以很方便的實(shí)現(xiàn)橫向滾動(dòng)或者縱向滾動(dòng)。

使用 demo 可以查看:Github 【RecyclerView簡(jiǎn)單使用

添加刪除 item 的動(dòng)畫(huà)

ListView每次修改了數(shù)據(jù)源后,都要調(diào)用notifyDataSetChanged()刷新每項(xiàng) item 類(lèi)似,只不過(guò)RecyclerView還支持局部刷新notifyItemInserted(index);、notifyItemRemoved(position)、notifyItemChanged(position)。

在添加或刪除了數(shù)據(jù)后,RecyclerView還提供了一個(gè)默認(rèn)的動(dòng)畫(huà)效果,來(lái)改變顯示。同時(shí),你也可以定制自己的動(dòng)畫(huà)效果:模仿DefaultItemAnimator或直接繼承這個(gè)類(lèi),實(shí)現(xiàn)自己的動(dòng)畫(huà)效果,并調(diào)用recyclerview.setItemAnimator(new DefaultItemAnimator());設(shè)置上自己的動(dòng)畫(huà)。

使用 demo 可以查看:Github 【RecyclerView默認(rèn)動(dòng)畫(huà)

LayoutManager的常用方法

findFirstVisibleItemPosition()返回當(dāng)前第一個(gè)可見(jiàn) Item 的 position

findFirstCompletelyVisibleItemPosition()返回當(dāng)前第一個(gè)完全可見(jiàn) Item 的 position

findLastVisibleItemPosition()返回當(dāng)前最后一個(gè)可見(jiàn) Item 的 position

findLastCompletelyVisibleItemPosition()返回當(dāng)前最后一個(gè)完全可見(jiàn) Item 的 position.

scrollBy()滾動(dòng)到某個(gè)位置。

adapter封裝

其實(shí)很早之前寫(xiě)過(guò)一篇關(guān)于RecyclerView適配器的封裝,所以這不再贅述了,傳送門(mén):RecyclerView的通用適配器

使用 demo 可以查看:Github 【RecyclerView通用適配器演示

吐槽

OnItemTouchListener 什么鬼?

用習(xí)慣了ListViewOnItemClickListener,RecyclerView你的OnItemClickListener呢?

Tell me where do I find, something like ListView listener ?

好吧,翻遍了 API 列表,就找到了個(gè)OnItemTouchListener,這特么什么鬼,我干嘛要對(duì)每個(gè) item 監(jiān)聽(tīng)觸摸屏事件。

萬(wàn)萬(wàn)沒(méi)想到,最終我還是在 Google IO 里面的介紹找到了原因。原來(lái)是 Google 的工程師分不清究竟是改給 listview 的 item 添加點(diǎn)擊事件,還是應(yīng)該給每個(gè) item 的 view 添加點(diǎn)擊事件,索性就不給OnItemClickListener了,然后在 support demo 里面,你就會(huì)發(fā)現(xiàn),RecyclerView的 item 點(diǎn)擊事件都是寫(xiě)在了 adapter 的 ViewHolder 里面。

當(dāng)然,除了 support demo 包里面使用的在 ViewHolder 里面設(shè)置點(diǎn)擊事件以外,我還寫(xiě)好了一個(gè)RecyclerView使用的OnItemClickListener代碼請(qǐng)見(jiàn):RecyclerItemClickListener.java

需要一提的是,網(wǎng)上有很多這種類(lèi)似的ItemClickListener,在使用的時(shí)候一定注意一個(gè)問(wèn)題,就是循環(huán)引用問(wèn)題。比如 listener 里面持有了一個(gè) recyclerview, 而這個(gè) recyclerview 在調(diào)用 setListener() 的時(shí)候又持有了一個(gè) listener。盡管 Java 虛擬機(jī)現(xiàn)在可以解決這種問(wèn)題了,但作為代碼編寫(xiě)者,這種寫(xiě)法還是應(yīng)該盡量避免的。

divider 跑哪了?

在ListView中設(shè)置divider非常簡(jiǎn)單,只需要在 XML 文件中設(shè)置就可以了,同時(shí)還可以設(shè)置divider高度。

android:divider="@android:color/black"android:dividerHeight="2dp"

而在RecyclerView里面,想實(shí)現(xiàn)這兩種需求,稍微復(fù)雜一點(diǎn),需要自己繼承RecyclerView.ItemDecoration來(lái)實(shí)現(xiàn)想要實(shí)現(xiàn)的方法。

雖說(shuō)這樣寫(xiě)靈活多了,但是要額外寫(xiě)一個(gè)類(lèi)去做難免麻煩,這里大家可以看我已經(jīng)實(shí)現(xiàn)好的一個(gè)封裝,包括顯示純色divider顯示圖片divider、divider的上下左右的間距、寬高設(shè)置應(yīng)該可以滿(mǎn)足基本需求了:Divider.java

使用 demo 可以查看:Github 【自定義 Divider 使用

五虎上將工作原理

借用 Google IO 視頻中的一張截圖:

視頻的完整地址可查看:RecyclerView ins and outs - Google I/O 2016

其實(shí)上圖中并沒(méi)有寫(xiě)完整,大 bossRecyclerView應(yīng)該有這五虎上將:

類(lèi)名作用

RecyclerView.LayoutManager負(fù)責(zé)Item視圖的布局的顯示管理

RecyclerView.ItemDecoration給每一項(xiàng)Item視圖添加子View,例如可以進(jìn)行畫(huà)分隔線(xiàn)之類(lèi)

RecyclerView.ItemAnimator負(fù)責(zé)處理數(shù)據(jù)添加或者刪除時(shí)候的動(dòng)畫(huà)效果

RecyclerView.Adapter為每一項(xiàng)Item創(chuàng)建視圖

RecyclerView.ViewHolder承載Item視圖的子布局

LayoutManager工作原理

java.lang.Object

? android.view.View

? android.view.ViewGroup

? android.support.v7.widget.RecyclerView

首先是RecyclerView繼承關(guān)系,可以看到,與 ListView 不同,他是一個(gè) ViewGroup。既然是一個(gè) View,那么就不可少的要經(jīng)歷onMeasure()、onLayout()、onDraw()這三個(gè)方法。 實(shí)際上,RecyclerView就是將onMeasure()、onLayout()交給了 LayoutManager 去處理,因此如果給 RecyclerView 設(shè)置不同的 LayoutManager 就可以達(dá)到不同的顯示效果,因?yàn)閛nMeasure()、onLayout()都不同了嘛。

ItemDecoration 工作原理

ItemDecoration是為了顯示每個(gè) item 之間分隔樣式的。它的本質(zhì)實(shí)際上就是一個(gè) Drawable。當(dāng) RecyclerView 執(zhí)行到onDraw()方法的時(shí)候,就會(huì)調(diào)用到他的onDraw(),這時(shí),如果你重寫(xiě)了這個(gè)方法,就相當(dāng)于是直接在 RecyclerView 上畫(huà)了一個(gè) Drawable 表現(xiàn)的東西。 而最后,在他的內(nèi)部還有一個(gè)叫g(shù)etItemOffsets()的方法,從字面就可以理解,他是用來(lái)偏移每個(gè) item 視圖的。當(dāng)我們?cè)诿總€(gè) item 視圖之間強(qiáng)行插入繪畫(huà)了一段 Drawable,那么如果再照著原本的邏輯去繪 item 視圖,就會(huì)覆蓋掉 Decoration 了,所以需要getItemOffsets()這個(gè)方法,讓每個(gè) item 往后面偏移一點(diǎn),不要覆蓋到之前畫(huà)上的分隔樣式了。

ItemAnimator

每一個(gè) item 在特定情況下都會(huì)執(zhí)行的動(dòng)畫(huà)。說(shuō)是特定情況,其實(shí)就是在視圖發(fā)生改變,我們手動(dòng)調(diào)用notifyxxxx()的時(shí)候。通常這個(gè)時(shí)候我們會(huì)要傳一個(gè)下標(biāo),那么從這個(gè)標(biāo)記開(kāi)始一直到結(jié)束,所有 item 視圖都會(huì)被執(zhí)行一次這個(gè)動(dòng)畫(huà)。

Adapter工作原理

首先是適配器,適配器的作用都是類(lèi)似的,用于提供每個(gè) item 視圖,并返回給RecyclerView作為其子布局添加到內(nèi)部。

但是,與ListView不同的是,ListView 的適配器是直接返回一個(gè) View,將這個(gè) View 加入到 ListView 內(nèi)部。而 RecyclerView 是返回一個(gè) ViewHolder 并且不是直接將這個(gè) holder 加入到視圖內(nèi)部,而是加入到一個(gè)緩存區(qū)域,在視圖需要的時(shí)候去緩存區(qū)域找到 holder 再間接的找到 holder 包裹的 View。

ViewHolder

每個(gè)ViewHolder的內(nèi)部是一個(gè) View,并且ViewHolder必須繼承自RecyclerView.ViewHolder類(lèi)。 這主要是因?yàn)?RecyclerView 內(nèi)部的緩存結(jié)構(gòu)并不是像 ListView 那樣去緩存一個(gè) View,而是直接緩存一個(gè) ViewHolder ,在 ViewHolder 的內(nèi)部又持有了一個(gè) View。既然是緩存一個(gè) ViewHolder,那么當(dāng)然就必須所有的 ViewHolder 都繼承同一個(gè)類(lèi)才能做到了。

緩存與復(fù)用的原理

還是一張截圖

RecyclerView 的內(nèi)部維護(hù)了一個(gè)二級(jí)緩存,滑出界面的 ViewHolder 會(huì)暫時(shí)放到 cache 結(jié)構(gòu)中,而從 cache 結(jié)構(gòu)中移除的 ViewHolder,則會(huì)放到一個(gè)叫做RecycledViewPool的循環(huán)緩存池中。

順帶一說(shuō),RecycledView 的性能并不比 ListView 要好多少,它最大的優(yōu)勢(shì)在于其擴(kuò)展性。但是有一點(diǎn),在 RecycledView 內(nèi)部的這個(gè)第二級(jí)緩存池RecycledViewPool是可以被多個(gè) RecyclerView 共用的,這一點(diǎn)比起直接緩存 View 的 ListView 就要高明了很多,但也正是因?yàn)樾枰欢鄠€(gè) RecyclerView 公用,所以我們的 ViewHolder 必須繼承自同一個(gè)基類(lèi)(即RecyclerView.ViewHolder)。

默認(rèn)的情況下,cache 緩存 2 個(gè) holder,RecycledViewPool 緩存 5 個(gè) holder。對(duì)于二級(jí)緩存池中的 holder 對(duì)象,會(huì)根據(jù) viewType 進(jìn)行分類(lèi),不同類(lèi)型的 viewType 之間互不影響。

源碼解析

onMeasure

既然是一個(gè)View,我們先從onMeasure()開(kāi)始看。

之前我們就說(shuō)了RecyclerView的 measure 和 layout 都是交給了LayoutManager去做的,來(lái)看一下為什么:

if(mLayout.mAutoMeasure) {finalintwidthMode = MeasureSpec.getMode(widthSpec);finalintheightMode = MeasureSpec.getMode(heightSpec);finalbooleanskipMeasure = widthMode == MeasureSpec.EXACTLY? ? ? ? ? ? && heightMode == MeasureSpec.EXACTLY;? ? mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);}else{? ? mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);}

不論是否啟用 mAutoMeasure 最終都會(huì)執(zhí)行到 mLayout.onMeasure() 方法中,而這個(gè) mLayout 就是一個(gè) LayoutManager 對(duì)象。

我們挑選LinearLayoutManager來(lái)看

發(fā)現(xiàn)它并沒(méi)有onMeasure()方法,LinearLayoutManager 直接繼承自 LayoutManager,所以又回到了父類(lèi) LayoutManager 中。

voiddefaultOnMeasure(intwidthSpec,intheightSpec){// calling LayoutManager here is not pretty but that API is already public and it is better// than creating another method since this is internal.finalintwidth = LayoutManager.chooseSize(widthSpec,? ? ? ? ? ? getPaddingLeft() + getPaddingRight(),? ? ? ? ? ? ViewCompat.getMinimumWidth(this));finalintheight = LayoutManager.chooseSize(heightSpec,? ? ? ? ? ? getPaddingTop() + getPaddingBottom(),? ? ? ? ? ? ViewCompat.getMinimumHeight(this));? ? setMeasuredDimension(width, height);}

有一句非常奇葩的注釋?zhuān)涸谶@里直接調(diào)用 LayoutManager 靜態(tài)方法并不完美,因?yàn)楸旧砭褪窃陬?lèi)內(nèi)部,更好的辦法調(diào)用一個(gè)單獨(dú)的方法。但反正這段代碼也已經(jīng)公開(kāi)了,你們自己看著辦。。。。。。

如果這不是歷史遺留問(wèn)題,那肯定是臨時(shí)工寫(xiě)的,你寫(xiě)的時(shí)候都意識(shí)到這問(wèn)題了,你還把一大堆類(lèi)都寫(xiě)在一個(gè)類(lèi)里面,造成了 RecyclerView 一個(gè)類(lèi)有一萬(wàn)多行代碼。我猜你是為了類(lèi)之間跨類(lèi)調(diào)用方便一點(diǎn),可是你就不能設(shè)置一個(gè)包訪(fǎng)問(wèn)權(quán)限,所有類(lèi)成員方法都包內(nèi)調(diào)用嗎,一個(gè)類(lèi)干了六個(gè)類(lèi)的活,網(wǎng)上居然還有人說(shuō)這是高內(nèi)聚的表現(xiàn)。

接著是chooseSize()方法,很簡(jiǎn)單,直接根據(jù)測(cè)量值和模式返回了最適大小。

publicstaticintchooseSize(intspec,intdesired,intmin){finalintmode = View.MeasureSpec.getMode(spec);finalintsize = View.MeasureSpec.getSize(spec);switch(mode) {caseView.MeasureSpec.EXACTLY:returnsize;caseView.MeasureSpec.AT_MOST:returnMath.min(size, Math.max(desired, min));caseView.MeasureSpec.UNSPECIFIED:default:returnMath.max(desired, min);? ? }}

緊接著是對(duì)子控件 measure ,調(diào)用了:dispatchLayoutStep2()調(diào)用了相同的方法,子控件的 measure 在 layout 過(guò)程中講解

onLayout

然后我們來(lái)看 layout 過(guò)程. 在onLayout()方法中間接的調(diào)用到了這么一個(gè)方法:dispatchLayoutStep2(),在它之中又調(diào)用到了mLayout.onLayoutChildren(mRecycler, mState);

我們重點(diǎn)看這個(gè)onLayoutChildren()方法。

這個(gè)方法在 LayoutManager 中的實(shí)現(xiàn)是空的,那么想必是在子類(lèi)中實(shí)現(xiàn)了吧。還是找LinearLayoutManager,跟上面 measure 過(guò)程一樣,調(diào)用了dispatchLayoutStep2()跟進(jìn)去發(fā)現(xiàn)這么一個(gè)方法:

fill(recycler, mLayoutState, state,false);

onLayoutChildren() 中有一個(gè)非常重要的方法:fill()

recycler,是一個(gè)全局的回收復(fù)用池,用于對(duì)每個(gè)itemview回收以及復(fù)用提供支持。稍后會(huì)詳細(xì)講這個(gè)。

while((layoutState.mInfinite || remainingSpace >0) && layoutState.hasMore(state)) {? ? layoutChunk(recycler,state, layoutState, layoutChunkResult);? ? layoutState.mOffset += layoutChunkResult.mConsumed * layoutState.mLayoutDirection;if(layoutState.mScrollingOffset != LayoutState.SCOLLING_OFFSET_NaN) {? ? ? ? layoutState.mScrollingOffset += layoutChunkResult.mConsumed;if(layoutState.mAvailable <0) {? ? ? ? ? ? layoutState.mScrollingOffset += layoutState.mAvailable;? ? ? ? }? ? ? ? recycleByLayoutState(recycler, layoutState);? ? }}

fill() 作用就是根據(jù)當(dāng)前狀態(tài)決定是應(yīng)該從緩存池中取 itemview 填充 還是應(yīng)該回收當(dāng)前的 itemview。

其中,layoutChunk() 負(fù)責(zé)從緩存池 recycler 中取 itemview,并調(diào)用View.addView()將獲取到的 ItemView 添加到 RecyclerView 中去,并調(diào)用 itemview 自身的 layout 方法去布局 item 位置。

同時(shí)在這里,還調(diào)用了measureChildWithMargins()來(lái)測(cè)繪子控件大小以及設(shè)置顯示位置。這一步,我們到下面的 draw 過(guò)程還要講。

而這全部的添加邏輯都放在一個(gè) while 循環(huán)里面,不停的添加 itemview 到 recyclerview 里面,直到塞滿(mǎn)所有可見(jiàn)區(qū)域?yàn)橹埂?/p>

onDraw

@OverridepublicvoidonDraw(Canvas c){super.onDraw(c);finalintcount = mItemDecorations.size();for(inti =0; i < count; i++) {? ? ? ? mItemDecorations.get(i).onDraw(c,this, mState);? ? }}

在onDraw()中,除了繪制自己以外,還多調(diào)了一個(gè)mItemDecorations的 onDraw() 方法,這個(gè)mItemDecorations就是前面吐槽的分隔線(xiàn)的集合。

之前在講 RecyclerView 的五虎上將的時(shí)候就講過(guò)這個(gè) ItemDecoration。 當(dāng)時(shí)我們還重寫(xiě)了一個(gè)方法叫g(shù)etItemOffsets()目的是為了不讓 itemview 擋住分隔線(xiàn)。那他是在哪調(diào)用的呢?

還記得 layout 時(shí)說(shuō)的那個(gè)measureChildWithMargins()嗎,就是在這里:

publicvoidmeasureChildWithMargins(View child,intwidthUsed,intheightUsed){finalRect insets = mRecyclerView.getItemDecorInsetsForChild(child);? ? widthUsed += insets.left + insets.right;? ? heightUsed += insets.top + insets.bottom;if(shouldMeasureChild(child, widthSpec, heightSpec, lp)) {? ? ? ? child.measure(widthSpec, heightSpec);? ? }}

在 itemview measure 的時(shí)候,會(huì)把偏移量也計(jì)算進(jìn)來(lái),也就是說(shuō):其實(shí)ItemDecoration的寬高是計(jì)算在 itemview 中的,只不過(guò) itemview 本身繪制區(qū)域沒(méi)有那么大,留出來(lái)的地方正好的透明的,于是就透過(guò) itemview 顯示出了 ItemDecoration。那么就很有意思了,如果我故意在 ItemDecoration 的偏移量中寫(xiě)成0,那么 itemview 就會(huì)擋住 ItemDecoration,而在 itemview 的增加或刪除的時(shí)候,會(huì)短暫的消失(透明),這時(shí)候就又可以透過(guò) itemview 看到 ItemDecoration 的樣子。使用這種組合還可以做出意想不到的動(dòng)畫(huà)效果。

滾動(dòng)

前面我們已經(jīng)完整的走完了 RecyclerView 的繪制流程。接下來(lái)我們?cè)倏纯此跐L動(dòng)的時(shí)候代碼又是怎么調(diào)用的。

說(shuō)到滾動(dòng),自然要看onTouch()方法的 MOVE 狀態(tài)。

caseMotionEvent.ACTION_MOVE: {finalintindex = MotionEventCompat.findPointerIndex(e, mScrollPointerId);finalintx = (int) (MotionEventCompat.getX(e, index) +0.5f);finalinty = (int) (MotionEventCompat.getY(e, index) +0.5f);intdx = mLastTouchX - x;intdy = mLastTouchY - y;if(dispatchNestedPreScroll(dx, dy, mScrollConsumed, mScrollOffset)) { ...? ? }if(mScrollState != SCROLL_STATE_DRAGGING) {...if(startScroll) {? ? ? ? ? ? setScrollState(SCROLL_STATE_DRAGGING);? ? ? ? }? ? }if(mScrollState == SCROLL_STATE_DRAGGING) {? ? ? ? mLastTouchX = x - mScrollOffset[0];? ? ? ? mLastTouchY = y - mScrollOffset[1];if(scrollByInternal(? ? ? ? ? ? ? ? canScrollHorizontally ? dx :0,? ? ? ? ? ? ? ? canScrollVertically ? dy :0,? ? ? ? ? ? ? ? vtev)) {? ? ? ? ? ? getParent().requestDisallowInterceptTouchEvent(true);? ? ? ? }? ? }}break;

看到這段代碼的時(shí)候,特意去搜了一下,MotionEventCompat這個(gè)類(lèi)是干嘛的。 他是 v4 包里面提供的一個(gè)工具類(lèi),用于兼容低版本的觸摸屏手勢(shì)。平時(shí)用的時(shí)候更多的是用它來(lái)處理多點(diǎn)觸控的情況,當(dāng)成MotionEvent就可以了。

dispatchNestedPreScroll()用于處理嵌套邏輯,例如在 ScrollView 里面放一個(gè) RecyclerView ,如果是以前用 ListView ,還得要把高度寫(xiě)死,禁止 ListView 的復(fù)用和滾動(dòng)邏輯,而 RecyclerView 則完全不需要更多處理,直接用就是了。而且有一個(gè)非常好的地方,如果放到 ScrollView 里面,ListView 的 ItemView 是不會(huì)復(fù)用的,而 RecyclerView 因?yàn)槭侨止靡惶拙彺娉?,雖說(shuō)嵌套到 ScrollView 效率會(huì)低很多,但比起 ListView 嵌套要好很多,之后講緩存池的時(shí)候,我們繼續(xù)講。

再之后,如果在相應(yīng)方向上手指move的距離達(dá)到最大值,則認(rèn)為需要滾動(dòng),并設(shè)置為滾動(dòng)狀態(tài)(SCROLL_STATE_DRAGGING),這個(gè)最大距離默認(rèn)是 8 個(gè)像素。

接著走出 if 塊,如果是滾動(dòng)狀態(tài),則調(diào)用滾動(dòng)方法scrollByInternal()執(zhí)行相應(yīng)方向的滾動(dòng)。滾動(dòng)的距離當(dāng)然就是手指移動(dòng)的距離。跟進(jìn)去看,果然是調(diào)用了LinearLayoutManager.scrollBy()方法,又印證了前面【更多操作】里面講 LayoutManager 可以滾動(dòng) RecyclerView 的方法。

以上就是滾動(dòng)的邏輯了。 但是沒(méi)完,就像 ListView,在手指劃過(guò)以后,手指離開(kāi)了屏幕,相關(guān)性一樣,View 自己依舊可以自己滾動(dòng)一段距離。

既然手指離開(kāi)了屏幕,那就去 UP 或者 CANCEL 狀態(tài)去找。

caseMotionEvent.ACTION_CANCEL: {? ? cancelTouch();}break;caseMotionEvent.ACTION_UP: {? ? mVelocityTracker.addMovement(vtev);? ? mVelocityTracker.computeCurrentVelocity(1000, mMaxFlingVelocity);finalfloatyvel = canScrollVertically ?? ? ? ? ? ? -VelocityTrackerCompat.getYVelocity(mVelocityTracker, mScrollPointerId) :0;if(!((xvel !=0|| yvel !=0) && fling((int) xvel, (int) yvel))) {? ? ? ? setScrollState(SCROLL_STATE_IDLE);? ? }? ? resetTouch();}break;

ACTION_CANCEL 里面只有一個(gè) cancelTouch() ,那么自然是在 UP 狀態(tài)里面實(shí)現(xiàn)的慣性滾動(dòng)。

看到了一個(gè) mVelocityTracker 對(duì)象,大概原理也就清楚了,慣性滾動(dòng)多長(zhǎng),肯定是跟手指移動(dòng)的速度有關(guān)了。

再往下,跟進(jìn)fling()方法里面看:調(diào)用了mViewFlinger.fling(velocityX, velocityY);

再進(jìn):

mScroller.fling(0, 0, velocityX, velocityY,

Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE);

原來(lái)是調(diào)用了 Scroller 類(lèi)的fling()方法,再仔細(xì)看一下,發(fā)現(xiàn)是ScrollerCompat看名字,估計(jì)又是用來(lái)兼容舊版本的 support 包里面的 Scroller 類(lèi)。關(guān)于這個(gè)Scroller類(lèi),他是一個(gè)可以用來(lái)實(shí)現(xiàn)平滑滾動(dòng)效果的類(lèi),其實(shí)內(nèi)部實(shí)現(xiàn)也是通過(guò)一點(diǎn)一點(diǎn)移動(dòng) view,利用了人眼的視覺(jué)暫留。

回收與復(fù)用

前面講 layout、滾動(dòng)的時(shí)候,都出現(xiàn)了一個(gè)東西,叫Recycler,現(xiàn)在我們就來(lái)看看他到底是個(gè)什么。

publicfinalclassRecycler{finalArrayList mAttachedScrap =newArrayList<>();privateArrayList mChangedScrap =null;finalArrayList mCachedViews =newArrayList();privatefinalList? ? ? ? mUnmodifiableAttachedScrap = Collections.unmodifiableList(mAttachedScrap);privateRecycledViewPool mRecyclerPool;privateViewCacheExtension mViewCacheExtension;

這么多的集合,還有什么Pool,ViewCache。看來(lái)他就是一個(gè)超大型的緩存器了。

事實(shí)上他確實(shí)就是一個(gè)超大型的緩存器,擁有三級(jí)緩存(如果算上創(chuàng)建的那一次,應(yīng)該是四級(jí)了),這么大的緩存系統(tǒng),究竟是如何完成的?

第一級(jí)緩存:

就是上面的一系列 mCachedViews。如果仍依賴(lài)于 RecyclerView (比如已經(jīng)滑動(dòng)出可視范圍,但還沒(méi)有被移除掉),但已經(jīng)被標(biāo)記移除的 ItemView 集合會(huì)被添加到 mAttachedScrap 中。然后如果 mAttachedScrap 中不再依賴(lài)時(shí)會(huì)被加入到 mCachedViews 中。 mChangedScrap 則是存儲(chǔ) notifXXX 方法時(shí)需要改變的 ViewHolder 。

第二級(jí)緩存:

ViewCacheExtension是一個(gè)抽象靜態(tài)類(lèi),用于充當(dāng)附加的緩存池,當(dāng) RecyclerView 從第一級(jí)緩存找不到需要的 View 時(shí),將會(huì)從ViewCacheExtension中找。不過(guò)這個(gè)緩存是由開(kāi)發(fā)者維護(hù)的,如果沒(méi)有設(shè)置它,則不會(huì)啟用。通常我們也不會(huì)去設(shè)置他,系統(tǒng)已經(jīng)預(yù)先提供了兩級(jí)緩存了,除非有特殊需求,比如要在調(diào)用系統(tǒng)的緩存池之前,返回一個(gè)特定的視圖,才會(huì)用到他。

第三級(jí)緩存:

最強(qiáng)大的緩存器。之前講了,與 ListView 直接緩存 ItemView 不同,從上面代碼里我們也能看到,RecyclerView 緩存的是 ViewHolder。而 ViewHolder 里面包含了一個(gè) View 這也就是為什么在寫(xiě) Adapter 的時(shí)候 必須繼承一個(gè)固定的 ViewHolder 的原因。首先來(lái)看一下 RecycledViewPool:

publicstaticclassRecycledViewPool{// 根據(jù) viewType 保存的被廢棄的 ViewHolder 集合,以便下次使用privateSparseArray> mScrap =newSparseArray>();/**

* 從緩存池移除并返回一個(gè) ViewHolder

*/publicViewHoldergetRecycledView(intviewType){? ? final ArrayList scrapHeap = mScrap.get(viewType);if(scrapHeap !=null&& !scrapHeap.isEmpty()) {? ? ? finalintindex = scrapHeap.size() -1;? ? ? final ViewHolder scrap = scrapHeap.get(index);? ? ? scrapHeap.remove(index);returnscrap;? ? }returnnull;? ? }publicvoidputRecycledView(ViewHolder scrap){? ? finalintviewType = scrap.getItemViewType();? ? final ArrayList scrapHeap = getScrapHeapForType(viewType);if(mMaxScrap.get(viewType) <= scrapHeap.size()) {return;? ? }? ? scrap.resetInternal();? ? scrapHeap.add(scrap);? }/**

* 根據(jù) viewType 獲取對(duì)應(yīng)緩存池

*/privateArrayListgetScrapHeapForType(intviewType){? ? ArrayList scrap = mScrap.get(viewType);if(scrap ==null) {? ? ? ? scrap =newArrayList<>();? ? ? ? mScrap.put(viewType, scrap);if(mMaxScrap.indexOfKey(viewType) <0) {? ? ? ? ? ? mMaxScrap.put(viewType, DEFAULT_MAX_SCRAP);? ? ? ? ? }? ? ? }returnscrap;? }}

從名字來(lái)看,他是一個(gè)緩存池,實(shí)現(xiàn)上,是通過(guò)一個(gè)默認(rèn)為 5 大小的 ArrayList 實(shí)現(xiàn)的。這一點(diǎn),同 ListView 的RecyclerBin這個(gè)類(lèi)一樣。很奇怪為什么不用 LinkedList 來(lái)做,按理說(shuō)這種不需要索引讀取的緩存池,用鏈表是最合適的。

然后每一個(gè) ArrayList 又都是放在一個(gè) Map 里面的,SparseArray這個(gè)類(lèi)我們?cè)谥v性能優(yōu)化的時(shí)候已經(jīng)多次提到了,就是兩個(gè)數(shù)組,用來(lái)替代 Map 的。

把所有的 ArrayList 放在一個(gè) Map 里面,這也是 RecyclerView 最大的亮點(diǎn),這樣根據(jù) itemType 來(lái)取不同的緩存 Holder,每一個(gè) Holder 都有對(duì)應(yīng)的緩存,而只需要為這些不同 RecyclerView 設(shè)置同一個(gè) Pool 就可以了。

這一點(diǎn)我們?cè)?Pool 的 setter 方法上可以看到注釋?zhuān)?/p>

/**

* Recycled view pools allow multiple RecyclerViews to share a common pool of scrap views.

* This can be useful if you have multiple RecyclerViews with adapters that use the same

* view types, for example if you have several data sets with the same kinds of item views

* displayed by a {@link android.support.v4.view.ViewPager ViewPager}.

*

* @param pool Pool to set. If this parameter is null a new pool will be created and used.

*/publicvoidsetRecycledViewPool(RecycledViewPool pool){? ? mRecycler.setRecycledViewPool(pool);}

在類(lèi)似 ViewPager 這種視圖中,所有 RecyclerView 的 holder 是共存于同一個(gè) Pool 中的。

寫(xiě)了這么多累死我了,就這樣吧,最后發(fā)一個(gè) demo 地址:RecyclerViewDemo

和一份內(nèi)部分享的 PPT 地址:RecyclerView PPT

本文原創(chuàng),轉(zhuǎn)載請(qǐng)以鏈接形式注明地址:http://kymjs.com/code/2016/07/10/01

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • RecyclerView 源碼分析 本文原創(chuàng),轉(zhuǎn)載請(qǐng)注明出處。歡迎關(guān)注我的 簡(jiǎn)書(shū) ,關(guān)注我的專(zhuān)題 Android ...
    MeloDev閱讀 10,265評(píng)論 6 49
  • 簡(jiǎn)介: 提供一個(gè)讓有限的窗口變成一個(gè)大數(shù)據(jù)集的靈活視圖。 術(shù)語(yǔ)表: Adapter:RecyclerView的子類(lèi)...
    酷泡泡閱讀 5,358評(píng)論 0 16
  • 一、概述 對(duì)于RecyclerView的學(xué)習(xí),主要是需要掌握以下幾點(diǎn): 數(shù)據(jù):Adapter 使用:Recycle...
    澤毛閱讀 7,602評(píng)論 1 23
  • 3.14 RecyclerView詳解 RecyclerView作為L(zhǎng)istView和GridView的替代,但是...
    jianhuih閱讀 6,893評(píng)論 1 5
  • It rained again today, that the second day in a row so th...
    李子菲閱讀 181評(píng)論 1 1

友情鏈接更多精彩內(nèi)容