基礎知識參考
http://tech.glowing.com/cn/practice-in-uiscrollview/
方案一 PageEnable
http://www.cnblogs.com/JimmyBright/p/4324042.html
http://www.jianshu.com/p/9c1be359fd1b
關鍵點是:ScrollView PageEnable 翻頁大小是ScrollView的bounds來的大小,在這個基礎上需要一些 hacking 實現 bleeding 和 padding(即頁與頁之間有 padding,在當前頁可以看到前后頁的部分內容)
以下代碼基於swift3,新建項目,替換ViewController內容即可運行,預覽效果
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) {
}
}
方案二 snap
這種方法就是在 didEndDragging 且無減速動畫,或在減速動畫完成時,snap 到一個整數頁。核心算法是通過當前 contentOffset 計算最近的整數頁及其對應的 contentOffset,通過動畫 snap 到該頁。這個方法實現的效果都有個通病,就是最后的 snap 會在 decelerate 結束以后才發生,總感覺很突兀。
方案三 修改 targetContentOffset
oc:
- (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;
}
swift3
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 = UIScrollViewDecelerationRateFast