UIScrollView
及其子类UITableView
, UICollectionView
为iOS开发带来了极大的方便, 其分页(pagingEnabled
)功能也很常用, 但是功能却有些局限, 页只能按UIScrollview
的bounds
尺寸划分, 如果要实现自定义分页宽度或高度就需要一些技巧.
方法一
类似iOS 6中MobileSafari, 如图所示的分页方法:
StackOverflow上提供的实现思路如下:
- 将
UIScrollview
的bounds
限制再屏幕中间那一页的位置 - 禁用
UIScrollview
的clipsToBouds
, 从而能显示超出bounds
的内容
此时已经可以在借助UIScrollview
原有分页功能的基础上实现了”视觉上”与MobileSafari类似的效果, 但因为UIScrollview
的尺寸限制, 超出范围的触摸事件不会被UIScrollview
收到, 影响体验, 要把触摸事件传递给UIScrollview
, 方法如下:
- 用一个全屏的(或者其他你想要的尺寸)
UIView
子类, 这里我们命名为CustomUIView
来做UIScrollview
的superView
- 重写
CustomUIView
的hitTest:withEvent
方法:- (UIView *) hitTest:(CGPoint) point withEvent:(UIEvent *)event { if ([self pointInside:point withEvent:event]) { return scrollView; } return nil; }
通过重写hitTest, 就可以将
CustomUIView
接受到的触摸事件传递给UIScrollView
方法二
前一种方法保留了UIScrollView
原有的分页功能, 而下面这种更加灵活的自定义分页方法则完全自制了一个分页功能:
DMPagingScrollView就是一个很好的实现, 其自定义分页的思路大致如下(需要参考DMPagingScrollView.m代码):
- 用实例变量
pageWidth
记录自定义分页的宽度 - 通过实现
UIScrollView
的delegate方法, 来记录每次拖动UIScrollView
动作的相关参数(位移, 加速度…) - 在
scrollViewDidEndDecelerating:
等一些标志拖动结束的delegate方法中调用snapToPage
方法, 在snapToPage
方法中, 借助之前收集的动作参数计算出contentOffset
, 并通过setContentOffset:offset animated:YES
来移动.
这种方式的关键就在contentOffset
的计算中(DMPagingScrollView.m pageOffsetForComponent:
方法), 如果计算的不正确, 就会严重影响拖动UIScrollView
的手感.
DMPagingScrollView通过subclassing UIScrollview
将自定义分页相关的过程操作封装在DMPagingScrollView
里面, 同时支持横向纵向的自定义分页, 十分方便. 类似地, 我们也可以对UITableView
, UICollectionView
进行同样的处理.
此外, 通过参考pageOffsetForComponent:
中的计算方法, 我们甚至可以让每一页的宽度都不同, 从而大大提升了分页的灵活性, 比如我实现的这种:
- (CGFloat)pageOffset {
CGFloat totalWidth = self.collectionView.contentSize.width;
CGFloat visibleWidth = self.collectionView.bounds.size.width;
CGFloat currentOffset = self.collectionView.contentOffset.x;
CGFloat dragVelocity = _dragVelocity.x;
CGFloat dragDisplacement = _dragDisplacement.x;
NSInteger nearestIndex = 0;
CGFloat minDis = CGFLOAT_MAX;
CGFloat nearestOffset = 0;
CGFloat prevOffset = 0;
CGFloat nextOffset = 0;
CGFloat totalOffset = 0;
// self.pagesWidths is an NSArray that contains each page's width
for (NSInteger i = 0; i < self.pagesWidths.count; i++) {
CGFloat dis = ABS(totalOffset - currentOffset);
if (dis < minDis) {
minDis = dis;
prevOffset = nearestOffset;
nearestOffset = totalOffset;
if (i == self.pagesWidths.count - 1) {
nextOffset = totalOffset;
}
else {
nextOffset = totalOffset + [(NSNumber *)self.pagesWidths[i] floatValue];
}
}
totalOffset += [(NSNumber *)self.pagesWidths[i] floatValue];
}
NSInteger lowerIndex;
NSInteger upperIndex;
CGFloat lowerOffset;
CGFloat upperOffset;
if (currentOffset - nearestOffset < 0) {
lowerIndex = nearestIndex - 1;
upperIndex = nearestIndex;
lowerOffset = prevOffset;
upperOffset = nearestOffset;
}
else {
lowerIndex = nearestIndex;
upperIndex = nearestIndex + 1;
lowerOffset = nearestOffset;
upperOffset = nextOffset;
}
CGFloat newOffset;
if (ABS(dragDisplacement) < DRAG_DISPLACEMENT_THRESHOLD || dragDisplacement * dragVelocity < 0) {
if (currentOffset - lowerOffset > upperOffset - currentOffset) {
newOffset = upperOffset;
} else {
newOffset = lowerOffset;
}
} else {
if (dragVelocity > 0) {
newOffset = upperOffset;
} else {
newOffset = lowerOffset;
}
}
if (newOffset > totalWidth - visibleWidth)
newOffset = totalWidth - visibleWidth;
if (newOffset < 0)
newOffset = 0;
return newOffset;
}
–以上–