自定義 LayoutManager,讓 RecyclerView 效果起飛

語言: CN / TW / HK

highlight: atelier-dune-light theme: vue-pro


1.背景

bfa811970c074319961170cc6200d521_tplv-k3u1fbpfcp-zoom-1.gif

RecyclerView可以說是每個Android開發者都不可避免的一個話題,它既可以實現普通的水平/垂直列表,還可以用極少的程式碼實現瀑布流效果,並且搭配谷歌提供的各種工具類,還可以在此基礎上實現多種自定義的動畫效果、滑動效果等。當你看到上面的列表效果時,第一反應是用RecyclerView的什麼工具來實現效果?比如嘗試一下自定義LayoutManager。

不過在自定義LayoutManager前,還需要先做一些準備工作才能實現一個既滿足展示需求也滿足效能需求的LayoutManager,比如搞清楚LayoutManager對RecyclerView來說意味著什麼,LayoutManager在RecyclerView中負責著哪些重要的工作,它在工作時又需要和RecyclerView中的哪些其它幫手共同合作,大家常說的RecyclerView快取和View複用又是誰負責的?接下來我們到RecyclerView的原始碼中嘗試尋找這些問題的答案。

本文內容主要是介紹自定義LayoutManager前需要了解的一些的前置工作,包括RecyclerView的整體設計及主要成員和作用,並總結了這些類與LayoutManager之間的聯絡,為後續自定義LayoutManager提供理論基礎。

2. RecyclerView的整體架構設計

RecyclerView類的註釋中對自己的解釋:它是一種可以在有限區域內展示大量資料的View。不妨先根據這句簡介盲猜一波RecyclerView可能擁有或需要擁有的一些能力: - RecyclerView的本質是ViewGroup,因此它需要將大量資料物件對映成View物件集合,將View物件集合中的View作為自己的子View在自身區域內佈局展示。 - 由於待展示的資料集可能是無限的,所以可能存在一個無限大的View物件集合,但RecyclerView自身區域有限,無法同時在這個區域內展示出所有子View,這意味著它需要提供按需在自身區域內佈局出應被展示的子View的能力,並通過手勢操作替換正在展示的子View的能力,相對應的,在替換展示的View時需要及時回收已經不被展示的View物件。 - 如果資料集合中的大多資料,都可以使用同一種View形態展示出來,則意味著對每一個數據物件都建立一個對應的View是浪費的,如果用type來區分資料物件期望的View展示形態,RecyclerView需要擁有根據type安排資料物件複用View物件的能力,即同一種type的不同資料物件可以在不同時機複用同一個View物件進行展示。

對上述能力,RecyclerView安排不同的類進行處理,依次分別對應: - Adapter - LayoutManager - Recycler

這三個類對RecyclerView來說是必不可少的,它們是RecyclerView可以正常工作的基礎。不過在實際使用過程中,Recycler的工作被封裝在RecyclerView內部完成,對使用者來說是透明的。同時谷歌提供了幾種LayoutManager的具體實現類,實現單列列表、瀑布流列表等效果,大多時候這幾種LayoutManager足夠滿足需求。使用者在日常使用時更多地是與Adapter打交道,通過為RecyclerView設定自定義的Adapter提供期望展示的子View樣式和資料繫結方式。

3.RecyclerView的重要成員

3.1 ViewHolder

在介紹第二節中提到的三個類之前,先了解RecyclerView中的基本單位:ViewHolder。如果用Item表示資料集中的一個數據物件,ItemView表示用來展示該Item的View物件。ViewHolder負責在RecyclerView中承載一個ItemView,除了維護View物件本身外,還維護著Item位置(position,此處的位置是指Item在資料集中的次序)、item型別、item Id等。大部分時候,RecyclerView內部或其輔助類並不會直接操作View,而是對ViewHolder進行操作。

在閱讀RecyclerView原始碼時發現,一些操作需要用View做引數,也有一些操作需要用ViewHolder做引數,實際上在RecyclerView中,可以通過任意一樣拿到另一樣,不必太過糾結RecyclerView的各個內部類的變數中究竟維護的是哪種型別。

但一般認為,Adapter負責根據資料Item建立對應的ViewHolder;Recycler負責管理ViewHolder,根據實際情況建立新的ViewHolder或複用已有的ViewHolder;LayoutManager可以通過Recycler直接獲取到View,負責將其新增到RecyclerView的佈局中,並通過Recycler回收已經不被展示的View。

3.2 Adapter

Adapter負責將資料物件對映為View物件。待展示的資料集維護在Adapter內,Adapter除了負責將資料對映為View外,也會向外分發資料集中資料的變化。

將資料物件對映為可用來展示的View物件,在RecyclerView體系中被拆分為兩步:

  • 步驟1:根據itemType建立符合預期的ViewHolder物件,此處更關注於View的結構樣式。
  • 步驟2:根據position從Adapter維護的資料集中獲取資料物件,將資料物件與ViewHolder中的View進行繫結,此處更關注View展示出的資料內容。

在程式碼實現中,RecyclerView.Adapter基類有兩個待使用者實現的回撥方法,這兩個方法會分別被Adapter.createViewHolder和Adapter.bindViewHolder呼叫,對應著步驟1與步驟2。

```java public abstract VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType);

public abstract void onBindViewHolder(@NonNull VH holder, int position); ```

現在Adapter已經擁有了將資料物件對映為View物件的能力,那麼它是在什麼時機執行這個能力的?將目光移到下一部分的內容:Recycler。

3.3 Recycler

Recycler的谷歌翻譯是:回收商。正如字面含義,Recyler負責管理ViewHolder,它可以回收起已經不被展示的ViewHolder,並在恰當的時候複用這些ViewHolder。它最重要的一個能力就是根據position提供一個ViewHolder/View,使用者無需關心這個ViewHolder是新建立的還是複用已有的,Recycler幫助RecyclerView處理ViewHolder的快取。

關於RecyclerView中有幾級快取,大家定義不同看法不一,一些人認為是四級快取,包括一級螢幕內快取、兩級螢幕外快取和一級自定義快取;還有人認為RecyclerView是三級快取,其中螢幕內快取與一級螢幕外快取合為一級,和另外一級螢幕外快取與自定義快取共同構成三級快取;還有一些人也認為是三級快取,但這三級快取中不包括螢幕內快取,而是兩級螢幕外快取和一級自定義快取。本文介紹的是最後一種觀點。

在瞭解Recycler處理快取的機制前,會先介紹View被新增到ViewGroup後的幾種狀態變化,再介紹Recycler中對View兩種不同的處理方法,最後介紹RecyclerView的快取機制。

3.3.1 View的detach vs remove

在瞭解Recycler對View的兩種處理方式前,我們先看一下View被新增到父View後其狀態的流轉。View被add到parent後,除了可以被remove外,還有一個更輕量級的detach操作,detached表示一種臨時狀態,意味著這個View在之後會馬上被重新attach或徹底remove。如果一個View處於detached狀態,像被remove一樣它也無法通過其parent的getChildAt方法獲得。

3.3.2 Recycle的scrap vs recycle

相對應的,Recycler對ViewHolder也有兩種處理方式:scrap和recycle。

scrap通常和detach操作共同使用,如果使用Recycler對一個View進行scrap操作,表示期望該View已經處於detach狀態(而不是removed狀態),持有這個View的ViewHolder會被標記為scrap狀態,然後臨時存放到Recycler.mAttachedScrap列表中,等待進一步的處理(unScrap或recycle)。scrap是一種臨時操作,通常表示該View之前在螢幕中展示,並且之後大概率也會繼續展示,不希望被remove回收掉。mAttachedScrap是一個ArrayList,存放著沒有被remove的子View的ViewHolder。

recycle通常和remove操作共同使用,如果使用Recycler對一個View進行recyle操作,表示期望該View已經從其parent中remove掉,並且持有該View的ViewHolder是unScrap狀態。當ViewHolder及其View的狀態都滿足條件後,RecyclerView會將這個ViewHolder放入Recycler的快取池中。recycle操作只針對已經被remove掉的View,它之前是被展示在螢幕中的,但由於滑動操作或資料集改變等因素,該View不再繼續展示,此時它可以被回收起來等待複用。這也是本文認為RecyclerView是3級快取的原因,只有被remove掉的View才有機會被回收快取。

RecyclerView.Recycler的原始碼中,有一些方法或變數的命名也與scrap有關,但觀察其使用,實際上都在做recycle的工作。

3.3.3 快取與回收

瞭解detach/remove和scrap/recycle的區別後,RecyclerView的快取機制變得更易讀一些,快取實際上是Recycler中存放ViewHolder的集合的變數,Recycler中用來表示三級快取的變數的優先順序從高到低分別為:mCacheViews、mViewCacheExtension和mRecyclerPool。其中mViewCacheExtension是自定義快取,本文不做展開,只看mCacheView和mRecyclerPool,首先需要明確的是,這兩者快取的內容都是已經不在螢幕內展示的ViewHolder。

mCacheViews是更高效的快取,既不需要建立ViewHolder步驟,也不需要重新繫結ViewHolder步驟,這意味著只有在資料物件完全匹配的時候,即待展示的資料Item與快取的ViewHolder中維護的資料Item完全匹配時(ItemType與Item都相同),才會複用mCacheViews中的ViewHolder。

mRecyclerPool中快取的ViewHolder物件的使用條件,相較於mCacheViews要求更低,只需ItemType匹配,即可複用ViewHolder,但使用時需要重新繫結ViewHolder。

簡單介紹mCacheViews和mRecyclerPool資料結構上的區別。mCacheViews是一個ArrayList,可以存放ViewHolder型別的物件,mRecyclerPool是RecycledViewPool物件,此處先簡單理解成一種Map型別的資料結構(實際上RecyclerView中並不是用Map實現的),Int表示itemType,ArrayList用來存放該itemType的ViewHolder物件。

Recycler回收ViewHolder的規則為: - 如果mCacheViews.size未達到最大size,則將該ViewHolder物件add到mCacheViews中;如果size已經達到最大值,則移除mCacheViews中最先被add的ViewHolder,再將待回收的ViewHolder新增到mCacheViews中。 - 如果mCacheViews.size已經達到最大size,將最先被add到mCacheViews的ViewHolder物件從mCacheViews移除後,嘗試將其回收到mRecyclerPool中,無論該ViewHolder是否成功回收到mRecyclerPool中,都會將這個ViewHolder物件從mCacheViews中移除。 - 如果mRecyclerPool中可以存放這個ViewHolder的itemType的List的size(預設為5)已經達到最大值,則直接拋掉該ViewHolder物件,否則add到這個List中。

3.3.4 Recycler獲取View

最後介紹Recycler獲取ViewHolder的過程,Recycler可以根據一個給定的position獲得一個可以直接用來展示的ViewHolder。在3.2中指出,Adapter將資料物件對映為View物件分成了兩步進行,即建立View和繫結資料,Recycler從自己內部不同的地方獲取ViewHolder,呼叫Adapter的步驟也略有區別。

  • Recycler先嚐試從mAttachedScrap中獲取可用的ViewHolder(可以認為該ViewHolder在複用前與複用後對應著同一個Item資料物件,且這個資料物件無變化),這裡獲取到的ViewHolder可以直接使用,既不需要執行Adapter.createViewHolder,也不需要執行Adapter.bindViewHolder。
  • 如果未從mAttachedScrap中取到可用的ViewHolder,Recycler會嘗試去快取中獲取,本文省略自定義快取一層的介紹,Recycler會先從mCacheViews中嘗試獲取到符合要求的ViewHolder物件,與從mAttachedScrap中獲取到的ViewHolder相似,該ViewHolder可以直接使用。
  • 如果mCacheViews中依然沒有滿足條件的ViewHolder,則嘗試從mRecyclerPool中獲取到符合要求的ViewHolder,這裡獲得的ViewHolder itemType可以匹配,即View的結構樣式滿足需求,但需要重新進行資料繫結,即不需要執行Adapter.createViewHolder,但需要執行Adapter.bindViewHolder。
  • 如果Recycler沒有從快取中得到符合要求的ViewHolder,會完整的執行Adapter的兩個步驟。

3.3.5 Recycler小結

此時我們已經大致瞭解怎樣通過position(此處的position依然指代一個數據物件在Adapter維護的資料集中的次序,換句話說,我們可以用position表示一個特定的資料物件)來獲得一個可用的ViewHolder,並且也清楚Recycler擁有兩種操作View/ViewHolder的能力:scrap和recycle,來臨時儲存或快取一些ViewHolder。那麼是誰,在什麼時候希望獲取到可用來展示的ViewHolder?又是誰在什麼時機會呼叫Recycler臨時儲存或回收ViewHolder?將目光放到RecyclerView必不可少成員的最後一員:LayoutManager。

3.4 LayoutManager

LayoutManager是RecyclerView中實際決定ItemView擺放規則與滑動規則的執行者,甚至可以決定ItemView的一些佈局引數。LayoutManager中有幾個待實現的抽象方法和空方法,給使用者充分的自由通過擴充套件LayoutManager實現自己想要的列表效果或滾動效果, 關於LayoutManager的具體工作在第四節詳細介紹。

java // 建立ItemView預設的LayoutParams public abstract LayoutParams generateDefaultLayoutParams(); // 佈局RecyclerView的子View public void onLayoutChildren(Recycler recycler, State state) { Log.e(TAG, "You must override onLayoutChildren(Recycler recycler, State state) "); } // RecyclerView是否支援水平滑動 public boolean canScrollHorizontally() { return false; } // RecyclerView是否支援垂直滑動 public boolean canScrollVertically() { return false; } // 處理RecyclerView的水平滑動 public int scrollHorizontallyBy(int dx, Recycler recycler, State state) { return 0; } // 處理RecyclerView的垂直滑動 public int scrollVerticallyBy(int dy, Recycler recycler, State state) { return 0; }

3.5 ItemDecoration

除了上述提及的幾個對於RecyclerView來說必備的輔助類外, RecyclerView還提供了一些可以在基本功能外達到更佳實踐效果的類。本文特別摘出ItemDecoration做介紹,這個類是我們在日常使用RecyclerView時較常使用的類之一,並且它與LayoutManager處理佈局有一定的關係。

ItemDecoration可以讓使用者向ItemView新增特殊的繪製和佈局偏移。 此處先不對繪製進行展開,著重介紹佈局偏移。ItemDecoration可以通過重寫getItemOffsets方法自定義ItemView的間距,getItemOffsets方法中使用的Rect記錄四個值,這四個值類似於ItemView的padding或margin的概念,分別對應left、right、top、bottom。該方法會在LayoutManager measure ItemView時呼叫,並將值對應的新增到ItemView measure後的寬高結果中。

需要注意的是:為RecyclerView設定ItemDecoration的方法是add而不是set,在RecyclerView中,維護的是ItemDecoration集合,layout過程中measure ItemView時,會累積計算ItemDecoration集合中offset的值。個人在使用ItemDecoration時,出現過重複對RecyclerView設定同一個ItemDecoration,導致間距表現不符預期的情況,多發生在頁面重新整理場景。

3.6 ItemAniamtor

最後再簡單介紹ItemAniamtor,它用來定義Adapter中維護的資料集發生變化時ItemView需要執行的動畫效果,例如刪除某個正在展示中的ItemView對應的Item資料時,該ItemView需要執行的消失動畫,以及由於它的消失其它ItemView需要執行的位移動畫等。

此處介紹它是為了糾正望文生義而對ItemAnimator產生的錯誤預期,為了完成背景中提到的delay位移動畫效果,曾寄希望於ItemAnimator幫助實現,實際上基於4.2節與4.3節中的分析可以知道,RecyclerView在正常的列表滾動時不會觸發ItemAnimator中定義的動畫,只有RecyclerView佈局時才可能觸發ItemAnimator的動畫。由於ItemAnimator的內容也及其龐大,本文不會對這部分內容繼續展開。

4.LayoutManager的工作

上面介紹了這麼多RecyclerView的成員,終於輪到了LayoutManager。相信每個人都聽說過:LayoutManager負責RecyclerView的佈局。但這句是說LayoutManager取代了RecyclerView的onLayout方法嗎?並不是,LayoutManager的工作實際上是幫助RecyclerView決定子View的位置,並且這項並不一定只在RecyclerView.onLayout方法中完成。這一節的內容是瞭解LayoutManager在什麼時機需要做什麼樣的工作。

4.1 RecyclerView如何實現佈局與繪製

為了瞭解LayoutManager是在什麼時機開始佈局ItemView,可以先回到RecyclerView中,RecyclerView作為一個ViewGroup,逃不掉measure、layout、draw三大流程。

4.1.1 measure

RecyclerView為LayoutManager提供了自定義onmMeasure方法的機會,如果LayoutManager期望RecyclerView使用自定義的onMeasure方法,可以通過重寫isAutoMeasureEnabled方法返回false禁用RecyclerView的autoMeasure策略,實際上,該方法預設返回false,但大多情況下,常用的LayoutManager此處都返回true。需要特別注意的是,當isAutoMeasureEnabled返回true時,不應重寫LayoutManager的onMeasure方法。如市面上大多文章一樣,本文也省略了非autoMeasure部分的分析,著重討論autoMeasure的機制。

RecyclerView的onMeasure的autoMeasure處理相對簡單,包含以下兩個分支: 1. 如果RecyclerView固定寬高,則呼叫RecyclerView的defaultOnMeasure方法結束onMeasure。 2. 如果RecyclerView是自適應的寬高,則需要提前佈局ItemView,才能確定RecyclerView的寬高,因此RecyclerView的onMeasure方法中會提前進行layout的部分過程。

4.1.2 layout

RecyclerView的layout過程分為3個步驟,且三個步驟對應的方法命名也非常簡單粗暴: - dispatchLayoutStep1 - dispatchLayoutStep2 - dispatchLayoutStep3

與之相對應的是RecyclerVIew中State類(State類中記錄各種可能會使用到的資訊)中的mLayoutStep變數可能的三個取值(mLayoutStep變數實際上是int型別,為了便於閱讀這裡列出其可能取值的常量名): - STEP_START - STEP_LAYOUT - STEP_ANIMATIONS

RecyclerView中一次完整的layout過程需要至少呼叫一次dispatchLayoutStep1、dispatchLayoutStep2、dispatchLayoutStep3,其中dispatchLayoutStep2可能被多次呼叫。在3.1.1中提到,onMeasure中可能會提前進行layout的部分過程,是指dispatchLayoutStep1和dispatchLayoutStep2,如果onMeasure中已經完成layout的前兩步工作,大多數情況下onLayout中僅需執行dispatchLayoutStep3即可,如果onMeasure中未提前進行layout的前兩步,則需要在onLayout中完整的執行一次layout過程。

雖然經常說RecyclerView將layout的能力外包給LayoutManager處理,但實際上RecyclerView只是將佈局子View的能力交由LayoutManager處理,RecyclerView在layout過程中還會進行預佈局pre-layout等其它操作。

在layout過程中,第二步即dispatchLayoutStep2中會呼叫LayoutManager的onLayoutChildren方法,這一步通常也被認為是實際的佈局過程post-layout,在這一步將需要在螢幕上展示的ItemView新增到RecyclerView中,並進行ItemView的measure和layout;layout過程中的第一步與第三步則主要是為了服務於RecyclerView的動畫(ItemAnimator),在第一步先進行一次pre-layout,再在第三步比較pre-layout和post-layout的區別,進而觸發ItemAnimator的動畫執行。

4.1.3 draw

RecyclerView的繪製過程中特殊處理相對較少,本文只對ItemDecoration相關的流程簡單介紹。在繪製ItemView前,RecyclerView會先遍歷其維護的ItemDecoration列表,執行ItemDecoration的onDraw方法,繪製出的內容在ItemView的下層;ItemView完成繪製後,執行ItemDecoration的onDrawOver方法,繪製出的內容在ItemView的上層。

4.2 滾動處理

本文主要目標是為實現自定義LayoutManager做必要準備,因此不展開RecyclerView複雜的巢狀滾動和慣性滾動等邏輯,只考慮LayoutManager在普通的滾動處理中起到的作用。列表的滾動通常是使用者的手勢操作引起的,先將目光放到RecyclerView.onTouchEvent方法中。

我們知道,使用者的手指在列表上移或下移,會導致列表滾動,因此我們到RecyclerView.onTouchEvent的ACTION_MOVE分支中,觀察它有沒有與scroll相關的處理,發現RecyclerView在接收到ACTION_MOVE的訊息後,經過了一系列的計算與判斷,可以得到手勢滑動導致的列表水平方向和垂直方向的位移dx與dy,然後呼叫RecyclerView內部的scrollByInternal方法處理滾動的位移值dx與dy,最終進入scrollStep方法,根據dx與dy分別呼叫LayoutManager的scrollHorizontallyBy/scrollVerticallyBy方法,把滾動導致的子View的移動和佈局工作外包給了LayoutManager處理,同時LayoutManager在處理滾動時也需要及時的使用Recycler處理不在螢幕中繼續展示的View。

關於RecyclerView滾動需要注意的是,以谷歌提供的LinearLayoutManager為例,它在處理滾動時,是呼叫View提供的offsetTopAndBottom方法平移已經展示在螢幕中的ItemView,並使用fill方法向滾動產生的空白區域新增View和處理已經不在螢幕展示的View,在這個過程中,與LayoutManager.onLayoutChildren方法並無關聯。一次正常的滾動過程不會導致RecyclerView的重複佈局,因此一次正常的列表滾動不會觸發ItemAnimator的任何動畫。

4.3 資料更新處理

RecyclerView在設定Adapter時,會建立RecyclerViewDataObserver物件註冊監聽Adapter中的Observable。RecyclerViewDataObserver做的事情其實就是在Adapter的資料集傳送改變或其中的某個資料發生改變時,在合適的情況下requestLayout,重新完成一次RecyclerView的layout過程,這個時候才是觸發ItemAnimator相應動畫的時機。

關於觀察者模式此處不做贅述,只需明確註冊監聽後,RecyclerView可以接收到Adapter.notifyXXX的訊息即可,然後將注意力放到RecyclerViewDataObserver中,關注其對notify訊息的具體處理。

首先明確資料更新的幾種型別:

  • 資料集全量更新(DataSetChanged)
  • 資料集區域性更新
    • 區域性Item改變(ItemChanged/ItemRangeChanged)
    • 新的Item插入(ItemInserted)
    • 已有Item刪除(ItemRemoved)
    • 已有Item移動(ItemMoved)

觀察RecyclerViewDataObserver中用來處理資料更新的方法,發現這些方法中都使用到了同一個幫助類:AdapterHelper。在AdapterHelper中,將資料更新行為抽象成UpdateOp類,每個UpdateOp物件表示一次資料更新操作,AdapterHelper中維護著一個待處理的更新操作列表mPendingUpdates(ArrayList)。

如果Adapter觸發了一次全量更新,那麼RecyclerViewDataObserver中的處理方法會在mPendingUpdates列表為空時requestLayout,進而觸發RecyclerView的重新佈局;如果Adapter觸發了局部更新(包括ItemChange/ItemInsert/ItemRemove/ItemMove等),那麼RecyclerViewDataObserver中的處理方法會在mPendingUpdates列表的size為1時requestLayout觸發RecyclerView的重新佈局。

4.小結

至此,已經瞭解RecyclerView的幾個重要成員和它們的基本職責,以及它們與LayoutManager之間的關聯:

  1. Adapter根據資料物件的type提供View,並提供View和資料間的繫結關係,LayoutManager不需要與Adapter打交道。
  2. Recyler可以根據position提供一個可以直接用來展示的View,它還負責管理已經不被展示的View。LayoutManager需要直接與Recycler打交道,它在onLayoutChildren時向Recycler索要可以用來展示的View,並在處理滑動時將不再展示的View交由Recycler處理。
  3. ItemDecoration可以處理ItemView的佈局偏移,LayoutManager在measure ItemView時會將其計算在內。
  4. ItemAnimator用來定義資料集發生改變時ItemView需要執行的動畫,LayoutManager與其並無直接的聯絡。ItemAnimator定義的動畫的執行時機是由RecyclerView的layout過程觸發的,正常的列表滑動不會觸發RecyclerView的重複佈局,因此列表滑動時也不會觸發ItemAnimator的執行。

另外,從上述描述中可以知道LayoutManager需要完成的兩個重要工作: 1. 在onLayoutChildren方法中處理ItemView的佈局。 2. 在scrollHorizontallyBy和scrollVerticallyBy方法中處理列表滾動時ItemView的平移以及ItemView的補充和回收。

此時我們瞭解到,LayoutManager可以處理子View的measure和layout過程,它可以按自己的需要measure child,並把子View放在它期望的位置上(甚至可以把所有子View都疊放在同一個位置);LayoutManager還可以接管處理滾動的過程(如果願意的話我們甚至可以在scroll方法中重新佈局子View而不觸發RecyclerView的layout過程)。

再次回看第一節中的demo,需求是在佈局或滑動時,保證第一個完全可見的ItemView是大卡片態,其它ItemView是小卡片態,而佈局和滑動恰好可以在LayoutManager進行處理,於是需求變成了讓LayoutManager measure第一個完全可見的ItemView時處理為大卡片態,其它ItemView measure為小卡片態,並讓LayoutManager將這些ItemView佈局到正確的位置上。在後續文章中,我們將帶領大家從0到1分析如何實現一個滿足我們滾動動畫需求的LayoutManager。

hi, 我是快手電商的魚塘

快手電商無線技術團隊正在招賢納士🎉🎉🎉! 我們是公司的核心業務線, 這裡雲集了各路高手, 也充滿了機會與挑戰. 伴隨著業務的高速發展, 團隊也在快速擴張. 歡迎各位高手加入我們, 一起創造世界級的電商產品~

熱招崗位: Android/iOS 高階開發, Android/iOS 專家, Java 架構師, 產品經理(電商背景), 測試開發... 大量 HC 等你來呦~

內部推薦請發簡歷至 >>>我們的郵箱: [email protected] <<<, 備註我的花名成功率更高哦~ 😘