@buoge
2017-08-09T08:27:14.000000Z
字数 4434
阅读 2771
iOS
http://tech.glowing.com/cn/practice-in-uiscrollview/
http://www.cnblogs.com/JimmyBright/p/4324042.html
http://www.jianshu.com/p/9c1be359fd1b
关键点是:ScrollView PageEnable 翻页大小是ScrollView的bounds来的大小,在这个基础上需要一些 hacking 实现 bleeding 和 padding(即页与页之间有 padding,在当前页可以看到前后页的部分内容)

import UIKitclass ViewController: UIViewController, UIScrollViewDelegate {private var contentScroll: UIScrollView!private let scrollHeight: CGFloat = 400private let appWidth: CGFloat = UIScreen.main.bounds.widthprivate let appHeight: CGFloat = UIScreen.main.bounds.heightprivate let padding: CGFloat = 10private let totalCount: Int = 9private 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.lightGrayself.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 = boundspageFrame.size.width = pageFrame.size.width - (2*padding)// 设定展示内容的view的frame的origin位置在屏幕x坐标系的左侧起始点pageFrame.origin.x = (bounds.size.width*CGFloat(index)) + paddingbtn.frame = pageFrameself.contentScroll.addSubview(btn)}}func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {}}
这种方法就是在 didEndDragging 且无减速动画,或在减速动画完成时,snap 到一个整数页。核心算法是通过当前 contentOffset 计算最近的整数页及其对应的 contentOffset,通过动画 snap 到该页。这个方法实现的效果都有个通病,就是最后的 snap 会在 decelerate 结束以后才发生,总感觉很突兀。
- (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)}}
1.collectionView.decelerationRate = UIScrollViewDecelerationRateFast2.