http://tech.glowing.com/cn/practice-in-uiscrollview/html
http://www.cnblogs.com/JimmyBright/p/4324042.html
http://www.jianshu.com/p/9c1be359fd1bswift
關鍵點是:ScrollView PageEnable 翻頁大小是ScrollView的bounds來的大小,在這個基礎上須要一些 hacking 實現 bleeding 和 padding(即頁與頁之間有 padding,在當前頁能夠看到先後頁的部份內容)app
import UIKit class ViewController: UIViewController, UIScrollViewDelegate { private var contentScroll: UIScrollView! private let scrollHeight: CGFloat = 400 private let appWidth: CGFloat = UIScreen.main.bounds.width private let appHeight: CGFloat = UIScreen.main.bounds.height private let padding: CGFloat = 10 private let totalCount: Int = 9 private var selectedIndex: Int = 0 // 設置ScrollView frame 起始點和終點左邊位於超出屏幕的左右兩側, // 從屏幕邊緣到超出屏幕的終點之間的空隙,即是每兩個view之間的間隔 // PageEnable 按ScrollView 的frame寬度進行翻頁,每次滾動frame的寬度, // add view 時,展現內容區域要設置在屏幕範圍以內,空隙則有屏幕外的間隔造成 // 既有一下關係: // 1. ScrollView.frame.width = 左側間隙 + 屏幕的寬度 + 右側間隙 // 2. 首頁的frame.origin.x 在左側屏幕外面,是負值,保證首個元素和屏幕對齊 // 3. 除第一個元素外,其他元素的左側frame.origin.x 和前一個元素右側終點x座標重合,保證只有一個間隙, // 因爲間隙是重合的,因此元素view寬度打滿屏幕還好,若是不是打滿屏幕的話點擊事件處理是個麻煩事情 override func viewDidLoad() { super.viewDidLoad() self.contentScroll = UIScrollView() self.contentScroll.delegate = self // 間隙的顏色 self.contentScroll.backgroundColor = UIColor.lightGray self.contentScroll.isPagingEnabled = true // 計算總的view的寬度 let totalFrameSize = (appWidth + (2*padding)) * CGFloat(totalCount) self.contentScroll.contentSize = CGSize(width: totalFrameSize, height: 0) self.contentScroll.frame = CGRect(x: -padding, y: (appHeight-scrollHeight)*0.5, width: appWidth + 2*padding, height: scrollHeight) self.view.addSubview(self.contentScroll) for index in 0...(totalCount - 1) { let btn = UIButton() btn.setTitle(String(index), for: .normal) btn.backgroundColor = UIColor.brown // 獲取scroll view 的大小 let bounds = self.contentScroll.bounds // 設定展現內容的view的frame寬度爲:屏幕寬度 var pageFrame: CGRect = bounds pageFrame.size.width = pageFrame.size.width - (2*padding) // 設定展現內容的view的frame的origin位置在屏幕x座標系的左側起始點 pageFrame.origin.x = (bounds.size.width*CGFloat(index)) + padding btn.frame = pageFrame self.contentScroll.addSubview(btn) } } func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { } }
這種方法就是在 didEndDragging 且無減速動畫,或在減速動畫完成時,snap 到一個整數頁。核心算法是經過當前 contentOffset 計算最近的整數頁及其對應的 contentOffset,經過動畫 snap 到該頁。這個方法實現的效果都有個通病,就是最後的 snap 會在 decelerate 結束之後才發生,總感受很突兀。ide
- (CGPoint)nearestTargetOffsetForOffset:(CGPoint)offset { CGFloat pageSize = BUBBLE_DIAMETER + BUBBLE_PADDING; NSInteger page = roundf(offset.x / pageSize); CGFloat targetX = pageSize * page; return CGPointMake(targetX, offset.y); } - (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset { CGPoint targetOffset = [self nearestTargetOffsetForOffset:*targetContentOffset]; targetContentOffset->x = targetOffset.x; targetContentOffset->y = targetOffset.y; }
public func scrollViewWillEndDragging(_ scrollView: UIScrollView ,withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>){ let pageWidth = Float(appWidth + itemSpacing) let targetXContentOffset = Float(targetContentOffset.pointee.x) var newPage = Float(currentPageIndex) // I use this way calculate newPage: newPage = roundf(targetXContentOffset / pageWidth); // 如下方式在最後一頁,左滑到最後一頁時,左滑出現邊角,而後在往右側滑動,頁面會直接跳動到倒數第二頁, // 是因爲在這裏使用velocity.x判斷向左右翻頁並不科學 //if velocity.x == 0 { //newPage = floor( (targetXContentOffset - Float(pageWidth) / 2) / Float(pageWidth)) // + 1.0 //} else { // newPage = Float(velocity.x > 0 ? newPage + 1 : newPage - 1) // if newPage < 0 { // newPage = 0 // } // if (newPage > contentWidth / pageWidth) { // newPage = ceil(contentWidth / pageWidth) - 1.0 // } //} // 滑動距離過短時,沒有動畫效果,解決方法: targetContentOffset.pointee = CGPoint(x:scrollView.contentOffset.x, y: scrollView.contentOffset.y) let pageWidth = Float(appWidth + itemSpacing) let targetXContentOffset = Float(targetContentOffset.pointee.x) // let contentWidth = Float(collectionView!.contentSize.width) var newPage = Float(currentPageIndex) newPage = roundf(targetXContentOffset / pageWidth); let targetOffsetX = CGFloat(newPage * pageWidth) let newPosition = CGPoint (x: targetOffsetX, y: targetContentOffset.pointee.y) // 動畫間隔一下避免衝突 DispatchUtils.dispatchAfterGap { scrollView.setContentOffset(newPosition, animated: true) } }
collectionView.decelerationRate = UIScrollViewDecelerationRateFastspa