1. 程式人生 > >關於RecyclerView你知道的不知道的都在這了(下)

關於RecyclerView你知道的不知道的都在這了(下)

scroller fyi 個數 收起 手動 類繼承 兩個 要去 csdn

目錄

  • 目錄
  • 正文
    • 6. Recycler
    • 7. ItemAnimator
    • 8. ItemDecoration
    • 9. OnFlingListener

目錄

由於本篇篇幅特長,特意做了個目錄,讓大夥對本篇內容先有個大概的了解。

另外,由於有些平臺可能不支持 `` 解析,所以建議大夥可借助本篇目錄,或平臺的目錄索引進行快速查閱。

  1. LayoutManager

    1.1 LinearLayoutManager
    • 基本效果介紹
    • findFirstCompletelyVisibleItemPosition()
    • findFirstVisibleItemPosition()
    • findLastCompletelyVisibleItemPosition()
    • findLastVisibleItemPosition()
    • setRecycleChildrenOnDetach()
    1.2 GridLayoutManager
    • 基本效果介紹
    • setSpanSizeLookUp()
    1.3 StaggeredGridLayoutManager
    • 基本效果介紹
    • setFullSpan()
    • findXXX() 系列方法介紹
  2. ViewHolder
    • getAdapterPosition()
    • getLayoutPosition()
    • setIsRecyclable()
  3. LayoutParams

  4. Adapter

    • 基本用法介紹
    • onViewRecycled()
    • onViewAttachedFromWindow()
    • onViewDetachedFromWindow()
    • onAttachedToRecyclerView()
    • onDetachedFromRecyclerView()
    • registerAdapterDataObserver()
    • unregisterAdapterDataObserver()
  5. RecyclerView

    • addOnItemTouchListener()
    • addOnScrollListener()
    • setHasFixedSize()
    • setLayoutFrozen()
    • setPreserveFocusAfterLayout()
    • findChildViewUnder()
    • findContainingItemView()
    • findContainingViewHolder()
    • findViewHolderXXX()
  6. Recycler

    • setItemViewCacheSize()
    • setViewCacheExtension()
    • setRecycledViewPool()
    • setRecyclerListener()
  7. ItemAnimator

    7.1 SimpleItemAnimator

    7.2 DefaultItemAnimator

  8. ItemDecoration

    8.1 DividerItemDecoration

    8.2 ItemTouchHelper

    8.3 FastScroller

  9. OnFlingListener

    9.1 SnapHelper

    9.2 LinearSnapHelper

    9.3 PagerSnapHelper

正文

閱讀須知:

  • 本篇力求列舉 RecyclerView 所有功能的使用示例,由於篇幅原因,並不會將實現代碼全部貼出,只貼出關鍵部分的代碼。
  • 本篇所使用的 RecyclerView 的版本是 26.0.0。
  • 下列標題中,但凡是斜體字,表示該知識點目前暫時沒理清楚,留待後續繼續補充。
  • 第 1 章至第 5 章節內容在上一篇中:關於RecyclerView你知道的不知道的都在這了(上)

6. Recycler

Recycler 是 RecyclerView 的一個內部類,主要職責就是處理回收復用相關工作的。

回收復用的單位是 ViewHolder,至於 Item 移出屏幕是怎樣回收,回收到哪裏,Item 移進屏幕時是怎樣復用,整個流程是先復用再回收,還是先回收再復用,還是兩邊同時進行等等一系列的工作都是交由 Recycler 來處理。

關於回收復用機制的部分原理,之前已經梳理過一篇文章了:基於滑動場景解析RecyclerView的回收復用機制原理,感興趣的可以先去看看。

本篇側重點是介紹各個接口的含義和使用場景,至於回收復用機制,後續肯定還會繼續深入去分析,敬請期待。

6.1 setItemViewCacheSize()

有看到上面鏈接那篇文章的應該就清楚,當 item 被移出屏幕外時,其實這個 item 的 ViewHolder 會被回收掉,而 Recycler 裏有一種分級緩存的概念。

分級緩存,說得白點,就是不同的容器,容器之間有優先級,回收時先將 ViewHolder 緩存到高優先級的容器中,容器滿了的話,那就將容器騰出個位置來,被騰出來的 ViewHolder 這時就可以放到優先級較低的容器中。分級緩存的概念就是這樣。

移出屏幕的 ViewHolder 會被緩存到兩個容器中,按優先級高到低分別是:mCachedViewsmRecyclerPool

該方法就是用於設置 mCachedViews 容器的大小,默認值為 2,可通過該方法隨時改變緩存容器的大小。

應用場景

要搞清楚應用場景,那得先明白 mCachedViews 這一級的緩存有什麽作用,建議還是到上面給出的鏈接的那篇文章看一看,就明白了。

這裏大概說一下,個人對於 mCachedViews 這一級緩存的理解:這一級的緩存僅僅就只是用來緩存而已,裏面存儲的 ViewHolder 並沒有通用的能力。換句話說也就是,只有原來位置的 Item 可復用這級容器裏的 ViewHolder,其他位置的 Item 沒辦法使用。效果就好像是 ViewPager 之類的緩存一樣,所以我才說它僅僅只有緩存的功能。

這樣能達到的效果就是:當某個 Item 剛被移出屏幕外,下一步又立馬移進屏幕時,此時並不會去觸發到 Adapter 的 onBindXXX 的調用,也就是說,這一級緩存裏的 ViewHolder 可直接 addView 到 RecyclerView 上面,不需要重新去設置數據,因為它原本攜帶的數據都還處於正常狀態,並沒有被重置掉。

6.2 setViewCacheExtension()

當 Item 要被移進屏幕時,Recycler 會先去那些不需要重新調用 onBindViewHolder() 的緩存容器中尋找是否有可直接復用的 Item,如果沒找到,那麽會接著調用開發者自定義擴展的復用工作,如果在這裏也沒找到,那麽才會去 RecyclerViewPool 中根據 type 來尋找可復用的,再沒找到最後就直接調用 onCreateViewHolder() 新建一個來使用。

先來看看開發者要怎麽自定義擴展:

/**
 * 我刪了一些註釋,留下一些困惑的點
 * 1. Note that, Recycler never sends Views to this method to be cached. It is developers
 * responsibility to decide whether they want to keep their Views in this custom cache
 * or let the default recycling policy handle it.
 */
public abstract static class ViewCacheExtension {
    /**
     * 2.This method should not create a new View. Instead, it is expected to return
     * an already created View that can be re-used for the given type and position.
     */
    public abstract View getViewForPositionAndType(Recycler recycler, int position, int type);
}

看著好像很簡單是吧,就只需要實現一個方法,返回指定 position 和 type 下的 Item 的 View 即可,網上所有分析到回收復用機制時也全部都是這麽一筆帶過。

但實際上,存在很多困惑點,這個到底該怎麽用?

註釋 1 裏說了,Recycler 永遠也不會將 ItemView 發送到這個類裏來緩存,然後還說由開發者自行決定是要自己維護這些緩存還說交由 Recycler 來處理。

困惑1:交由 Recycler 來處理我能理解,畢竟 Recycler 只在復用的過程中開了個接口給開發者擴展使用,但回收的過程並沒有開任何接口給開發者擴展。也正是基於這點,我就不理解官方說的讓開發者自行維護,怎麽維護?

註釋 2 中,官方告訴我們在這個方法中,不要去新建一個新的 ItemView,而是直接從舊的裏面拿一個復用。

困惑2:我又不知道怎麽自己去維護 ViewHolder,那不新建一個 ItemView 又該如何使用,直接借助 Recycler?但 Recycler 不是只開放了 getViewForPosition()?本來內部在復用時就是自己調了這個方法,我們在這個方法內部走到開發者擴展自定義擴展的流程時再重新調一下?那不是就陷入嵌套循環裏了?有什麽意義或者應用場景麽?

最最困惑的一點,國內居然找不到任何一篇講解如何使用這個自定義緩存的相關文章!?

不清楚是由於他們文章的標題太過抽象沒加入我的關鍵詞過濾中,還是我關鍵詞提取太爛,總之就是找不到任何一篇相關文章。所以,這小節先埋個坑,我打算後續抽時間自己來研究一下,到底應該如何使用自定義 RecyclerView 的緩存策略,到底都有哪些應用場景。

6.3 setRecycledViewPool()

最後一級緩存就是 RecyclerViewPool,這個容器有三個特性:

  • 緩存到 RecyclerViewPool 中的 ViewHolder,攜帶的信息都會被重置,因此從這個容器中取 ViewHolder 去復用時,都會觸發 onBindViewHolder() 重新綁定數據。
  • 多個 RecyclerView 可共用同一個 RecyclerViewPool 容器。
  • 該容器以 Item 的 type 區分緩存,每種 type 的默認存儲容量為 5。

一般當我們需要修改這個緩存容器的大小,或者需要設置多個 RecyclerView 共用一個 RecyclerViewPool 時才需要調用到該方法。並且,官方在註釋中也給出一種應用場景:使用 ViewPager 時各頁面中的 RecyclerView 有相同的 Item 布局結構。

應用場景:

我們來舉個例子,就不用官方給的例子了,我額外補充一種場景:界面上存在多行可分別左右滑動的列表控件,即每行是一個 RecyclerView,每行裏的 Item 布局樣式一致,這時候就可以讓每一行的 RecyclerView 共用一個 RecyclerViewPool 緩存池了,如下:

技術分享圖片

以上布局的實現是外層一個豎直方向的 RecyclerView,它的每一個 Item 都是占據一行的水平方向的 RecyclerView,也就是嵌套 RecyclerView 的方式,實現可上下滑動且每一行均可左右滑動的效果。

這裏的每一行的 RecyclerView 裏的每個 Item 項的樣式均一致,那麽這種場景下,可以讓每一行的 RecyclerView 都共用同一個 RecyclerViewPool 緩存池。這樣的好處是,當某一行被移出屏幕時,可以將這一行的每個卡位都回收起來,供其他行使用,而不至於每一行每次都是重新創建。

但有些註意事項

  • 外層 RecyclerView 緩存復用的應該僅僅是每一行的 RecyclerView 控件而已,不應該包括每一行 RecyclerView 內部的卡位控件,因為各行卡位的個數並不一定相同。
  • 對於外層 RecyclerView 來說,它的 Item 是每一行的 RecyclerView 控件,所以當某一行被移出屏幕時,它僅僅是將這一行的 RecyclerView 控件從它本身 remove 掉,並回收起來。因此,此時這一行的 RecyclerView 還是攜帶著它的卡位子 View 的,所以需要我們手動去將這些卡位回收、並從父控件上 remove 掉。
  • 這個操作可以在外層 RecyclerView 的 adapter 的 onViewRecycled() 回調中進行,也可以在內層每個 RecyclerView 的 adapter 的 onViewDetachedFromWindow() 回調中進行。
  • 移除並回收卡位可通過 setAdapter(null) 配合 RecyclerView 本身的 removeAllView() 或者 LayoutManager 的 removeAllView() 實現 。或者直接使用 LinearLayoutManager 的 setRecycleChildrenOnDetach() 功能。

6.4 setRecyclerListener()

//RecyclerView$Recycler#
void dispatchViewRecycled(ViewHolder holder) {
    if (mRecyclerListener != null) {
        mRecyclerListener.onViewRecycled(holder);
    }
    if (mAdapter != null) {
        mAdapter.onViewRecycled(holder);
    }
    ...
}

所以,這個方法設置的監聽 Item 的回收,回調的時機跟 adapter 的 onViewRecycled() 一模一樣,都是在 mCachedViews 容器滿了之後,放入 RecyclerViewPool 之前被回調。

7. ItemAnimator

RecyclerView 是支持對每個 item 做各種各樣動畫的,那麽什麽時候才該去執行這些 item 動畫呢?說白了,也就是 adapter 數據源發生變化的時候,那麽變化的方式無外乎就是四種:add, remove, change, move。相對應的,也就是這些狀態時的 item 動畫。

所以當要自定義實現 ItemAnimator 時,需要實現的方法如下:

public abstract boolean animateAppearance(@NonNull ViewHolder viewHolder, @Nullable ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo);  
public abstract boolean animateDisappearance(@NonNull ViewHolder viewHolder, @Nullable ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo);
public abstract boolean animatePersistence(@NonNull ViewHolder viewHolder, @NonNull ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo);
public abstract boolean animateChange(@NonNull ViewHolder oldHolder, @NonNull ViewHolder newHolder, @NonNull ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo);
public abstract void runPendingAnimations();
public abstract void endAnimation(ViewHolder item);
public abstract void endAnimations();
public abstract boolean isRunning();

看起來,要實現一個自定義的 Item 好像很復雜,要實現這麽多方法。網上這方面的文章已經非常多了,也都跟你說清了每個方法的含義是什麽,在這裏寫些什麽,甚至流程都幫你列出來了。

但大夥會不會好奇,這幫牛人是咋這麽清楚的呢?

其實,Google 內部已經封裝好了一個默認動畫的實現,有時間大夥可以自己過一下源碼,看看默認動畫是怎麽做的,理解清楚了後,舉一反三下,其實也就懂了。

我目前也不懂,但我就是帶著這麽一種想法,也是打算這麽去做的。雖然跟著大神的文章,最後確實能實現想要的效果,但其實掌握並不是很牢,並不大清楚為什麽需要這麽寫,只是因為大神說這裏這麽寫,然後就這麽寫了。

所以,有時間有精力,還是建議深入源碼中去學習,自己梳理出來的知識終歸是自己的。

7.1 SimpleItemAnimator

當 adapter 數據源發生變化,通知了 RecyclerView 去刷新界面時,RecyclerView 會去通知 ItemAnimaotr 此時相應的動畫行為。

比如 add 了一個 Item,那麽就會去觸發 ItemAnimator 的 animateAppearance() 方法,並將這個 ItemView 在刷新前後不同的信息,如默認攜帶的信息是 RecyclerView 內部類 ItemHolderInfo,這個類裏有關於這個 Item 的坐標信息。

那麽,開發者就可以在這些回調方法裏自行判斷刷新前後的 Item 的不同信息來決定是否需要進行相對應的動畫。

而 SimpleItemAnimator 其實就是幫忙處理這件事,也就是說,它在四個回調中,如 animateAppearance() 中,根據 Item 前後的坐標信息來判斷該 Item 需要進行的動畫類型

比如 animateAppearance()

@Override
public boolean animateAppearance(@NonNull ViewHolder viewHolder, @Nullable ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo) {
    if (preLayoutInfo != null && (preLayoutInfo.left != postLayoutInfo.left
        || preLayoutInfo.top != postLayoutInfo.top)) {
         // slide items in if before/after locations differ
         return animateMove(viewHolder, preLayoutInfo.left, preLayoutInfo.top,
                postLayoutInfo.left, postLayoutInfo.top);
    } else {
         return animateAdd(viewHolder);
    }
}

對於 RecyclerView 回調了 animateAppearance() 方法後,SimpleItemAnimator 內部對其進行的分類,根據參數判斷,最終是要執行 animateMove() 類型的動畫,還是執行 animateAdd() 類型的動畫。

同理,對於另外三個回調方法,SimpleItemAnimator 內部同樣對其進行了封裝處理,簡單的通過刷新前後 Item 的坐標信息來進行動畫類型的區分。

所以,這個類並沒有實現任何動畫的邏輯,它只是將動畫的準備工作做好,簡化開發者開發。所以,如果想要自定義 ItemAnimator,其實沒必要從零開始繼承自 ItemAnimator 自己寫,是可以借助 SimpleItemAnimator 這個類的。

7.2 DefaultItemAnimator

RecyclerView 默認有提供 Item 的動畫,而 SimpleItemAnimator 只是處理跟動畫無關的準備工作,那麽具體的默認動畫的實現就是在 DefaultItemAnimator 這個類中實現的了。

先看一下這個類的結構:

技術分享圖片

它是繼承自 SimpleItemAnimator 的,我們如果想要自定義實現一些 Item 動畫,需要寫的東西,大概就跟上圖類似。

想要自定義 Item 動畫,真的可以來參考、借鑒這個類的實現,能學到的東西很多。

我也還沒深入去仔細學習,大概過了一眼,這裏就大概說下:

這個類用了很多集合來維護各種不同類型的動畫,在四個 animateXXX() 方法中通過集合記錄相對應類型的動畫和做了動畫的初始化工作。

然後在 runPendingAnimations() 方法中,依次遍歷這些集合,將記錄的動畫取出來執行,動畫的實現方式是通過 View.animate() 方式實現,這種方式的動畫本質上是借助了 ValueAnimator 機制,在每幀的回調過程中手動調用 setXXX() 來實現的動畫效果。具體分析可參考我之前寫的一篇文章:View.animate()動畫ViewPropertyAnimator原理解析。

大體上的流程原理就是這樣,當然,這個類做的事肯定不止這些,還包括了集合的清理維護工作,動畫的維護等等,所以很值得去借鑒學習一番。但這裏就只給出大概的流程,本篇重點不在這裏。

下面就來列舉下,默認實現的各類型的動畫分別是什麽:

animateAdd -> 透明度 0 ~ 1 的動畫,默認動畫時長 120 ms

animateChange -> 涉及兩個 ItemView,舊的跟新的,默認動畫時長 250ms

舊 ItemView:透明度從原有值 ~ 0,位置從原坐標移動到新 ItemView 坐標的動畫組合

新 ItemView:透明度從 0 ~ 1,位置從舊 ItemView 坐標移動到新坐標的動畫組合

animateMove -> 從原坐標位置移動到新坐標位置的移動動畫,默認動畫時長 250 ms

animateRemove -> 從原有透明度 ~ 0 的動畫,默認動畫時長 120 ms

所以,RecyclerView 默認的 Item 動畫其實也就透明度和移動動畫兩種,而且大多數情況下都只是單一的動畫,只有 change 類型時才會是組合動畫。

效果展示:

首先,可通過下列方式修改動畫時長,這裏將動畫時長延長,方便查看效果

mRecyclerView.getItemAnimator().setAddDuration(1000);

技術分享圖片

可以看到,動畫基本就只有透明度動畫跟移動動畫兩種。

另外,只有通過 notifyItemXXX() 方式更新數據源時才會觸發動畫行為,如果是通過 notifyDataSetChange() 方式,則不會觸發動畫。

8. ItemDecoration

RecyclerView 支持為每個 Item 之間自定義間隔樣式,是要空段距離,還是要以分割線隔開,還是要唯美唯幻的邊框,想長啥樣都行,自己寫得出來就可以了,它給我們提供了這個接口。

本節就先介紹下系統內置的幾種樣式,內置裏一共有三個類繼承該類,分別是 DividerItemDecoration,ItemTouchHelper,FastScroller。前兩個都是 public 權限,最後一個包權限,下面分別看看它們都有哪些效果,最後再來看看如何自定義。

8.1 DividerItemDecoration

看一下這個類的註釋:

/**
 * DividerItemDecoration is a {@link RecyclerView.ItemDecoration} that can be used as a divider
 * between items of a {@link LinearLayoutManager}. It supports both {@link #HORIZONTAL} and
 * {@link #VERTICAL} orientations.
 * <pre>
 *     mDividerItemDecoration = new DividerItemDecoration(recyclerView.getContext(),
 *             mLayoutManager.getOrientation());
 *     recyclerView.addItemDecoration(mDividerItemDecoration);
 * </pre>
 */
public class DividerItemDecoration extends RecyclerView.ItemDecoration {
    /**
     * Sets the {@link Drawable} for this divider.
     *
     * @param drawable Drawable that should be used as a divider.
     */
    public void setDrawable(@NonNull Drawable drawable) {
        if (drawable == null) {
            throw new IllegalArgumentException("Drawable cannot be null.");
        }
        mDivider = drawable;
    }
    ...
}

怎麽用,類註釋也給我們示例了,有點可惜的是,它只能用於 LinearLayoutManager 這種布局,而 GridLayoutManager 繼承自 LinearLayoutManager,所以它也可以用,但需要註意的是,它只有一個方向會生效。來看看如何使用和效果:

DividerItemDecoration itemDecoration = new DividerItemDecoration(mContext, LinearLayoutManager.HORIZONTAL);
itemDecoration.setDrawable(getResources().getDrawable(R.drawable.divider_space));
mRecyclerView.addItemDecoration(itemDecoration);

//R.drawable.divider_space
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <size android:width="20dp" android:height="20dp"/>
</shape>

我們在 xml 中寫寬度為 20dp 的空隙,然後調用 setDrawable() 應用,看看效果:

技術分享圖片

這樣就可以達到將 item 隔離開的效果了,中間這個空隙的樣式你可以自己通過 xml 寫,也可以直接使用圖片,都可以,只要是 Drawable 類型的即可。

雖然說,RecyclerView 不像 ListView 只要設置個屬性就可以達到設置 Item 之間空隙的樣式,但它也內置了基本的實現,其實也已經方便了我們的使用。

8.2 ItemTouchHelper

這是一個工具類,也是 Google 為了方便開發人員實現 item 的拖拽和移動等等效果所提供的一個輔助工具類。借助這個類可以很容易實現 item 的側滑刪除、長按拖拽等功能。

由於這部分我沒有研究過,日常也較少接觸,所以暫時先從網上搜索一篇文章,以下的效果圖來自大神的博客,會給出鏈接,侵權刪。後續有接觸相關需求時再自行來研究一番。

推薦博客: ItemTouchHelper源碼分析

技術分享圖片

技術分享圖片

8.3 FastScroller

這個類也是繼承自 ItemDecoration,但它的類權限只是包權限,不開放給外部使用,稍微看了下註釋,說是用來處理動畫以及快速滑動相關的支持,具體原理是什麽,如何生效,留待後續深入研究時再來分析。

8.4 自定義ItemDecoration

上面說過系統默認提供的 DividerItemDecoration 只支持用於 LinearLayoutManager,而如果用於 GridLayoutManager 時,只有一個方向會生效,那麽下面我們就以 GridLayoutManager 為例,來看看,如何自定義寫 ItemDecoration。

用 GridLayoutManager 實現一個四列的布局,然後讓除了四個邊的 Item 外,內部的每個 Item 之間相互間隔 20 dp 的空隙。為了能更明顯看出,將 20dp 的空隙用紅色繪制出來。

先來看下效果:

  • 4 列布局 & 2 行布局

技術分享圖片

代碼:

public class MyItemDecoration extends RecyclerView.ItemDecoration {
    private int spanCount;//幾行或幾列
    private int orientation;//方向
    private int itemSpace;//空隙大小
    
    private Rect mBounds = new Rect();
    private Paint mPaint;//用來將空隙繪制成紅色的畫筆

    public MyItemDecoration(GridLayoutManager gridLayoutManager) {
        spanCount = gridLayoutManager.getSpanCount();
        orientation = gridLayoutManager.getOrientation();
        initPaint();
    }

    private void initPaint() {
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setColor(Color.RED);
    }

    public void setItemSpace(int space) {
        itemSpace = space;
    }

    @Override
    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
        c.save();
        final int childCount = parent.getChildCount();
        for (int i = 0; i < childCount; i++) {
            final View child = parent.getChildAt(i);
            parent.getLayoutManager().getDecoratedBoundsWithMargins(child, mBounds);
            c.drawRect(mBounds, mPaint);
        }
        c.restore();
    }

    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        //獲取當前view的layoutPosition
        int itemPosition = parent.getChildLayoutPosition(view);
        //計算該View位於哪一行哪一列
        int positionOfGroup = itemPosition % spanCount;
        int itemGroup = itemPosition / spanCount;
        
        //根據不同方向進行不同處理,最終效果都要實現除四周的View 外,內部的View之間橫豎都以相同空隙間隔開
        //實現方式,以水平方向為例:
        //每個view的left和bottom都設置相同間隙
        //去掉第1列的left,和最後一行的bottom,也就實現了除四周外內部view都以相同間隙空隔開
        if (orientation == LinearLayoutManager.HORIZONTAL) {
            outRect.set(itemSpace, 0, 0, itemSpace);
            if (itemGroup == 0) {
                outRect.left = 0;
            }
            if (positionOfGroup == (spanCount - 1)) {
                outRect.bottom = 0;
            }
        } else if (orientation == LinearLayoutManager.VERTICAL) {
            outRect.set(0, itemSpace, itemSpace, 0);
            if (itemGroup == 0) {
                outRect.top = 0;
            }
            if (positionOfGroup == (spanCount - 1)) {
                outRect.right = 0;
            }
        }
    }
}

註意事項:由於 GridLayoutManager 會根據設置的 Orientation 方向,默認為 VERTICAL 數值方向,以及 RecyclerView 的寬高模式來決定是否自動將某一方向的空隙平均分配給各 Item,這點需要註意一下。

以上的例子想說明,如果要自定義寫 Iiem 間的空隙,那麽關鍵點在於重寫兩個方法:

  • getItemOffsets()
  • onDraw()

第 1 個方法會攜帶很多參數,最重要的是 outRect 這個參數,它是一個 Rect 類型的對象,重寫這個方法並設置了這個 outRect 的 left, top, right, bottom,就相當於設置了對應這個 view 的四周分別有多大的空隙。

其他的參數是用來給我們輔助使用,如果不需要區分對待,每個 item 的四周都是同樣的間隔空隙,那直接設置 outRect 即可。

如果需要像上述例子那樣,要求四周的 Item 的間隙要區別於內部 item 的間隙,那麽就需要判斷出這個 View 的位置,因此可以通過其他參數輔助配合實現。

onDraw() 這個方法就是用於繪制,註意這個方法參數只給了 RecyclerView,而繪制 item 的 Decoration 是針對於每個 item 而言的,所以內部需要通過遍歷子 View 來對每個 item 進行繪制操作。

當然,我這裏寫得很粗糙,考慮到性能優化方面,繪制過度方面等等因素,通常是需要使用到 canvas.clipRect()。這部分代碼建議可以參考 DividerItemDecoration 內部的實現。

9. OnFlingListener

RecyclerView 是可滑動控件,在平常使用過程中,我們可能就是上滑,下滑,左邊滑滑,右邊滑滑,能夠刷新更多列表即可,通常都沒太過去註意到滑動的細節。

但其實,滑動策略也是支持多樣化的。

比如,如果想要實現不管以多大的加速度滑動,滑多長距離,最終停下來時都系統有個 ItemView 是居中顯示的效果。

再比如,只希望翻頁滑動,當手指滑動距離小於翻頁時,自動滑回原位進行翻頁等等。

這些滑動策略其實就可以直接借助內置的兩個類來實現:LinearSnapHelper 和 PagerSnapHelper

ps:本來以為這種滑動策略也是支持由焦點觸發的滑動行為的,最後測試時才發現,原來只支持手指觸摸式的滑動行為。由於我是搞 Tv 應用開發的,Tv 應用沒有觸摸事件,只有遙控器事件,滑動是由於焦點的變化觸發的滑動行為。而在 Tv 上,Item 居中的需求也非常常見,但利用這個是無法實現的。所以,我就先不打算深入了解這塊了,後續有時間再來慢慢研究。附上鴻神公眾號中的一篇文章,大夥看這篇就行了。

Android中使用RecyclerView + SnapHelper實現類似ViewPager效果

技術分享圖片

9.1 SnapHelper

9.2 LinearSnapHelper

9.3 PagerSnapHelper


大家好,我是 dasu,歡迎關註我的公眾號(dasuAndroidTv),如果你覺得本篇內容有幫助到你,可以轉載但記得要關註,要標明原文哦,謝謝支持~
技術分享圖片

關於RecyclerView你知道的不知道的都在這了(下)