iOS橫滑元件實現

語言: CN / TW / HK

這是我早先實現的一個自定義橫滑元件,本文回顧一下當時實現過程遇到的問題和細節,最後有原始碼地址

文中所有圖片託管在Github上

所謂橫滑元件其實就如圖所示的效果:

列一下UI上的要求:

  • 每次滑動一頁,有pageEnable的效果
  • 每次顯示在螢幕中的item其實是三個而不是一個
  • 每個item的間距、檢視與螢幕邊緣的邊距嚴格按照UI上樣子

UICollectionView+pageEnable

使用UICollectionView並開啟pageEnable是最容易想到的方案,我們來試一下能否滿足需要

關鍵的幾個引數如下所示

container.width = 375 collectionView.isPagingEnable = true collectionView.width = 375 leftPadding = rightPadding = 16 cell.width = container.width - leftPadding - rightPadding collectionView.contentInset = UIEdgeInset(0,16,0,0)

效果如下所示:

顯然,沒有達到預期:

  • 問題1,每次滑動停止後,cell的位置不對
    • 通過列印contentOffset得知,UIScrollView開啟pagingEnable後的自動翻頁,每次修改contentOffset的值等於UIScrollView.width
    • 而且我們無法自定義每次翻頁移動的距離
  • 問題2,由於設定了collectionView.contentInset.left,所以第一cell可以移動到螢幕最左邊而不能自動還原到初始位置

不甘心,繼續調整

我畫了一張圖來表示要實現的效果:

  • 根據上圖的效果,我們希望的效果是每次移動cell時移動的距離(兩條紅豎線之間的距離)是一個cell的寬度+cell之間的距離--cell.width+interval
  • 既然pageEnable特性每次移動的距離一定是scrollView.width,所以我們可以讓scrollView.width = cell.width+interval
  • 這或許能解決上面顯示異常問題

我們更新一下配置引數,如下:

leftPadding = rightPadding = 16 container.width = 375 collectionView.isPagingEnable = true cell.width = container.width - leftPadding - rightPadding interval = 8 collectionView.width = cell.width + interval collectionView.contentInset = UIEdgeInset(0,0,0,interval) // 這一句可能會引起你的困惑,但經過測試必須設定成這樣,否則效果有問題,本文不做詳細解釋,跟scrollView自身對於contentSize和contentOffset的調整有關

來看一下效果:

哇,好像不錯!但還是有問題:

  • 我們希望同時顯示三個cell,但該效果卻只能顯示1個cell
  • 這是因為collectionView的寬度剛好能顯示下一個cell和一個interval,沒有更多空間來顯示其他cell了

這就很尷尬了,為了利用pageEnable的特性,我們不得不修改collectionView的寬度小一些,但這卻導致無法足夠的cell個數

所以,結論是:❌

UICollectionView + UIScrollView

在調研其他技術方案時,受一Paging a overflowing collection view啟發,可以使用一個UICollectionView和一個UIScrollView一同實現類似效果

核心思想如下:

  • 單獨用一個UIScrollView,利用pageEnable特性來實現符合要求的橫滑、拖拽翻頁效果
  • 單獨用一個UICollectionView來利用它的cell顯示、複用機制
  • UIScrollView是不顯示的,只用它的拖拽手勢能力。當拖拽UIScrollView時,將contentOffset的移動應用到UICollectionView中

具體實現過程中有些細節需要注意,比如:

  1. collectionView的contentInset需要設定
  2. 將scrollView的移動應用到collectionView中時如何計算準確
  3. 需要關閉collectionView的panGesture

再放一下效果

結論是:✅

原始碼地址:SlideView.swift

優缺點

優點很明顯:

  • 既複用了UIScrollView的pageEnable手勢和動畫效果,也複用了UICollectionView的cell複用機制
  • 由於複用了UICollectionView,所以相比通過UIScrollView自定義實現,在一些使用者互動體驗上可能更好,比如在快速橫滑時,自定義的實現可能就沒辦法快速的準備好每一個cell並無縫從上一頁切換過來,可能會有點卡頓
  • 所有實現細節都是通過系統官方的public API,不存在任何trick行為,穩定性好

缺點:

在使用者體驗上沒發現缺點。只是在封裝為獨立元件時需要注意更多細節,比如:

  • 該元件將CollectionView封裝了起來,所以必須給外部使用者暴露dataSource和delegate等必要的回撥和資料來源方法

使用UIScrollView完全自定義實現

我還看過另一種方案:

  • 自己建立cell檢視,新增到UIScrollView上
  • 完全由自己來控制cell的複用和顯示邏輯
  • 滑動手勢和效果方面,利用UIScrollViewDelegate方法來控制抬起手指後移動到到下一個或上一個cell的效果(該效果我曾經也實現過,可以參考設計與Swipe-Delete不衝突的UIPageViewController

這個思路看上去應該是可行的,我也看過類似的原始碼實現,是Github上的一個程式碼

但該原始碼的顯示邏輯寫的不好:

  • 每次切換cell時,會同時通過delegate要求更新所有的cell資料(顯示在螢幕中的cell和在快取池中未用到的cell)