Demo地址:https://github.com/Rogue24/JPCustomScrollPageSize
虽说 scrollView 有 pagingEnabled 这个属性可以实现翻页的效果,如果是整个翻页的区域为 scrollView 的 size 那就好,但想要只翻半个 scrollView 的宽度那怎么办呢?
实际上,scrollView 的 pagingEnabled 的区域是根据 scrollView 的 size 来决定,也就是scrollView有多宽就翻多少啦。
如果是单纯的 scrollView 那就好办,直接设置 scrollView 的区域为想要翻页的尺寸,如果比屏幕小就设置 scrollView 的 clipsToBounds 为NO就好了,然后修改 hittest 让多余的空位的点击事件回传给 scrollView 就好了!
可是如果是 collectionView 并且设置其为屏幕一半的大小的话,由于 collectionView 的重用机制的原因,collectionView 区域(不是 contentSize,是 bounds)以外的 cell 并不会提前显示,然后一直滚的话,超出区域的 cell 也会被立即重用直接移位到后面了。
所以。。。
一个简单粗暴的方法:使用一个占位的 scrollView 来实现!
- 创建 collectionView 并实现的基本数据源、代理的方法,这里我的 collectionView 的数据源和代理都为 collectionView 自身(self),方便管理。
self.delegate = self;
self.dataSource = self;
self.scrollEnabled = NO; // 不需要自身来进行滚动
- 在 collectionView 上添加一个占位的 scrollView(专门用来翻页用的)。
// 先从collectionViewLayout中获取翻一页的宽度
// 我这里的一页宽度就是一个cell的宽度加上间距
JPFlowLayout *flowLayout = (JPFlowLayout *)layout;
CGFloat width = flowLayout.itemSize.width + flowLayout.minimumLineSpacing;
// 创建占位的scrollView,设置成一页的宽度
// 这里只有水平滚动,所以只设置宽度即可,位置则随意
UIScrollView *placeholderSV = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, width, 0)];
placeholderSV.pagingEnabled = YES;
placeholderSV.delegate = self;
placeholderSV.showsHorizontalScrollIndicator = NO;
// 添加到collectionView上
[self addSubview:placeholderSV];
self.placeholderSV = placeholderSV;
- 修改 hitTest 方法,让占位 scrollView 以外的点击事件都能接收到。
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
return self.placeholderSV;
}
- 实现 scrollViewDidScroll: 方法。
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
if (scrollView == self.placeholderSV) {
// 让占位scrollView来控制collectionView的偏移量
self.contentOffset = scrollView.contentOffset;
}
}
- 设置占位 scrollView 的 contentSize(总页数的宽度)。
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
// 我选择在collectionView的数据源方法中设置contentSize,保证跟collectionView的contentSize保持一致
self.placeholderSV.contentSize = CGSizeMake(self.placeholderSV.jp_width * MaxItemCount, 0); // 翻页宽度 * 数据数量
return MaxItemCount;
}
现在能实现基本的翻页效果了,但是,此时所有的点击事件都被占位 scrollView 拦截了,怎么让 cell 的点击传给 collectionView 呢?
在占位 scrollView 添加手势传递事件
- 例如添加一个 tap 手势
// 添加一个tap手势
[placeholderSV addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tap:)]];
// 监听tap手势
- (void)tap:(UITapGestureRecognizer *)tapGR {
CGPoint point = [tapGR locationInView:self];
NSIndexPath *indexPath = [self indexPathForItemAtPoint:point];
if (indexPath) {
[self collectionView:self didSelectItemAtIndexPath:indexPath];
}
}
#pragma mark - UICollectionViewDataSource
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
// 使用占位scrollView滚动目标位置(这里我是滚到屏幕中心)
[self.placeholderSV setContentOffset:CGPointMake(indexPath.item * self.placeholderSV.jp_width, 0) animated:YES];
}
防止拦截 collectionViewCell 上的需要响应的子控件(例如 button)
只要在 hitTest 方法上判断触碰的 view 是否为需要响应的子控件,是则返回该控件,否则还是返回占位 scrollView,判断条件可以使用 tag 值,也可以判断 view 的类型等等。
- 在 cell 上添加一个 button
// 这里我使用设定好的可以响应的tag值
UIButton *button = ({
UIButton *btn = [UIButton buttonWithType:UIButtonTypeSystem];
// 设置可以响应的tag值
btn.tag = JPInteractionEnabledTag;
btn.backgroundColor = [UIColor greenColor];
btn.titleLabel.font = [UIFont systemFontOfSize:30];
[btn setTitleColor:[UIColor blueColor] forState:UIControlStateNormal];
[btn addTarget:self action:@selector(btnDidClick) forControlEvents:UIControlEventTouchUpInside];
btn;
});
- 在 hitTest 方法里面进行判断
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
UIView *view = [super hitTest:point withEvent:event];
// 如果该view的tag为可以响应的tag值就返回该控件
if (view.tag == JPInteractionEnabledTag) {
return view;
}
return self.placeholderSV;
}
结语
此时基本效果能实现了,然而弊端还是有的,因为占位 scrollView 拦截了 collectionView 的所有点击事件,而我这里只处理了单击事件,例如 collectionView 的 cell 拖动,那么就要把占位 scrollView 的拦截关掉。
如果有更好的实现方案请告诉我!
最后献上Demo一份:https://github.com/Rogue24/JPCustomScrollPageSize