利用 UIScrollView 有多種方法實現分頁,但是各自的效果和用途不盡相同,其中方法 2 和方法 3 的區別也正是一些同類 App 在模仿 Glow 的首頁 Bubble 翻轉效果時跟 Glow 體驗上的的差距所在(但愿他們不會看到本文并且調整他們的實現方式)。本例通過三種方法實現相似的一個場景,你可以通過安裝到手機上來感受三種實現方式的不同用戶體驗。
1.pagingEnabled
這是系統提供的分頁方式,最簡單,但是有一些局限性:
<li>只能以 frame size 為單位翻頁,減速動畫阻尼大,減速過程不超過一頁
<li>需要一些 hacking 實現 bleeding 和 padding(即頁與頁之間有 padding,在當前頁可以看到前后頁的部分內容)
有簡單實現 bleeding 和 padding 效果的代碼,主要的思路是:
<li>讓 scroll view 的寬度為 page 寬度 + padding,并且設置 clipsToBounds 為 NO
<li>這樣雖然能看到前后頁的內容,但是無法響應 touch,所以需要另一個覆蓋期望的可觸摸區域的 view 來實現類似 touch bridging 的功能
適用場景:上述局限性同時也是這種實現方式的優點,比如一般 App 的引導頁(教程),Calendar 里的月視圖,都可以用這種方法實現。
2.Snap
這種方法就是在 didEndDragging 且無減速動畫,或在減速動畫完成時,snap 到一個整數頁。核心算法是通過當前 contentOffset 計算最近的整數頁及其對應的 contentOffset,通過動畫 snap 到該頁。這個方法實現的效果都有個通病,就是最后的 snap 會在 decelerate 結束以后才發生,總感覺很突兀。
修改 targetContentOffset
通過修改 scrollViewWillEndDragging: withVelocity: targetContentOffset: 方法中的 targetContentOffset 直接修改目標 offset 為整數頁位置。其中核心代碼:
func nearestTargetOffsetForOffset(offset:CGPoint)->CGPoint{
var pageSize:CGFloat = BUBBLE_DIAMETER2+BUBBLE_PADDING2
var page:Int = Int(roundf(Float(offset.x) / Float(pageSize)))
var targetX:CGFloat = CGFloat(pageSize) * CGFloat(page)
return CGPointMake(targetX, offset.y)
}
func scrollViewWillEndDragging(scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
var targetOffset:CGPoint = self.nearestTargetOffsetForOffset(targetContentOffset.memory)
targetContentOffset.memory.x = targetOffset.x
targetContentOffset.memory.y = targetOffset.y
}
適用場景:方法 2 和 方法 3 的原理近似,效果也相近,適用場景也基本相同,但方法 3 的體驗會好很多,snap 到整數頁的過程很自然,或者說用戶完全感知不到 snap 過程的存在。這兩種方法的減速過程流暢,適用于一屏有多頁,但需要按整數頁滑動的場景;也適用于如圖表中自動 snap 到整數天的場景;還適用于每頁大小不同的情況下 snap 到整數頁的場景(不做舉例,自行發揮,其實只需要修改計算目標 offset 的方法)。