【Flutter】自定義ListView開發記錄(三)—— 處理HitTest手勢事件

語言: CN / TW / HK

theme: condensed-night-purple

「這是我參與11月更文挑戰的第10天,活動詳情檢視:2021最後一次更文挑戰」。

前言

在前面一篇中,已經實現了LayoutManager的基礎思路結構,以及覆蓋翻頁的效果,但是還存在這麼一個小小的問題,如下圖gif所示:

有問題的.gif

當Item本身比ListView小,給Item加個點選事件;

這時候即使點選的區域不是Item所在區域,點選事件還是被響應了;

那麼這篇中就來處理一下手勢事件的響應問題;

分析:

回想一下ListView的結構,如果只看重點部分,按前面文章所述,差不多是這樣的結構:

mermaid erDiagram PrimaryScrollController ||--|{ Viewport : contains Viewport ||--|{ SliverList : contains PrimaryScrollController }

在這個體系中,下層僅僅根據上層提供的資料做出響應,上層並不知道也不關心下層具體內容,所做了什麼;

所以要再這麼多HitTest事件中定位要修改的地方,只能是知道具體內容、繪製位置的SliverList層;

而SliverList對HitTest所做的處理也不復雜:

image.png

去掉邊界條件後,其實所做的事就是通過hitTestChildren和hitTestSelf兩個方法確認是否點選自己所管理的區域;

在sliver中,hitTestSelf不重寫的話,永遠都是false,所以可以無視掉:

image.png

在SliverLit中,hitTestChidren方法是這麼規定的:

image.png

所做的事也很簡單,遍歷一遍當前持有的child們,如果有人響應了hitTestBoxChilld方法,那麼直接返回true;

而這個hitTestChildren方法就是真正來判斷child是否被點選了的地方:

image.png

看上去是有點長,往外呼叫工具方法的地方也稍微有點多,細看一下也不復雜,大體流程這樣的:

  • 首先判斷一下方向
  • 計算出當前 item 在listView中主軸、交叉軸的位置;
  • 計算當前點選點相對位置:
  • 這步可能不太好理解,這塊要結合方法資料來源,也就是ViewPort的hitTestChildren方法一起看 image.png 最後會將具體位置,通過computeChildMainAxisPosition方法轉換一下。而這個方法所做的事是這樣的: image.png 說白了,就是點選位置 - child的在ListView中的繪製偏移量,但是對於ViewPort而言,child是SliverList本體,所以在上面的例子中,偏移量為0,可以無視掉;結果就是點選位置;

而上面的 item 在 listView 中的childMainAxisPosition方法,獲得的是相對當前ListView位置;

這倆一減的結果就是點選位置 + 當前ListView的滑動偏移量,也就是點選相對位置;

  • 剩下的事就是將各種資料組裝,傳入 addWithOutOfBandPosition 方法;而這個addWithOutOfBandPosition方法,則會根據傳入的paintOffset,更新自己的size,進而能計算是否被點選到

所以問題就在這:

按照覆蓋翻頁效果的規定,只有第一頁正式使用了ListView的規則,其他頁面則一直保持在起始位置;但這個效果僅僅是在paint方法中這麼規定的,在其他地方還在使用paintOffset、layoutOffset這種,對於他們來說,還是按的是未經修改的ListView的效果來計算;

所以在這裡,也要將點選位置效果處理下;

方案

要想自定義hitTest,主要是對這麼四個方法進行修改,所以將其抽離到LayoutManager中:

image.png

將其預設實現改為ListView的普通實現方式;而自定義的部分,通過重寫的方式進行修改,比如說在這裡,覆蓋翻頁的LayoutManager 主要修改了這麼兩個地方:

hitTestChilderen 、hitTestBoxChild

首先,因為只需要響應最頂部點選到的地方,所以hitTestChildren方法,這回遍歷方式先從firstChild開始;

在hitTestBoxChild方法中,所做的事也很簡單: - 如果 childMainAxisPosition 方法拿到的相對主軸方向為負數,那麼直接返回super.hitTestBoxChild,按原先邏輯處理; - 如果不是,那麼將上面提到的計算方式逆轉計算,獲取點選的全域性位置,直接返回child.hitTest;

結語

實現方法雖然簡單,但是可以通過這個瞭解到在ListView中,點選事件是如何經過層層處理轉換、各個parentData中的變數的含義;

最後看下修改後的效果:

沒問題了.gif

可以看到,點選白色區域是徹底沒有反應,點選Item區域也是正常響應,即使是重疊的部分;

「其他文章」