這個標題挺難起的:
UICollectionView設(shè)置翻頁區(qū)域朋贬?
UICollectionView依據(jù)items翻頁项玛,而不是屏幕寬度田晚?
UICollectionView每一頁開頭第一個item不被切割,并且左間距固定?
滤淳。。砌左。
直接看效果:
比較一下直接設(shè)置collectionView.pagingEnabled = YES
的效果:
Code
源碼在GitHub汇歹,item的寬度和間距使用宏定義屁擅,方便修改
關(guān)鍵步驟
一、新建UICollectionViewFlowLayout 子類产弹,自定義滑動位置
- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity;
Discussion
If you want the scrolling behavior to snap to specific boundaries, you can override this method and use it to change the point at which to stop. For example, you might use this method to always stop scrolling on a boundary between items, as opposed to stopping in the middle of an item.
首先派歌,proposedContentOffset
參數(shù)的含義是系統(tǒng)根據(jù)用戶的滑動手勢計算出來的將要滑動到的目標位置。
我們可以在UICollectionViewFlowLayout
的子類里重寫這個方法取视,根據(jù)系統(tǒng)計算出來的期望目標位置proposedContentOffset
和滑動速度velocity
硝皂,自定義滑動位置。
1. 新建UICollectionViewFlowLayout
的子類MyCollectionFlowLayout
將item有關(guān)參數(shù)設(shè)置為宏作谭,方便修改
#import "MyCollectionFlowLayout.h"
static CGFloat const kItemWidth = 70.f; // item寬高
static CGFloat const kPaddingMid = 30.f; // item間距
static CGFloat const kPaddingLeft = 20.f; // 最左邊item左邊距
@interface MyCollectionFlowLayout()<UIScrollViewDelegate, UICollectionViewDelegate> {
NSInteger _pageCapacity; // 每頁可以完整展示的item個數(shù)
NSInteger _currentIndex; // 當前頁碼(滑動前)
}
@end
2. 重寫- (void)prepareLayout
方法稽物,設(shè)置sectionInset
右縮進
在這個方法里,需要計算:
- 每頁可以完整顯示的items個數(shù)
- 完整顯示所有items的總頁數(shù)
- 最后一頁item從左邊開始折欠,那右邊的剩余空間有多少贝或?即
sectionInset
右縮進
- (void)prepareLayout
{
[super prepareLayout];
self.collectionView.delegate = self;
// 計算paddingRight
CGFloat paddingRight = 0.0;
// item個數(shù)
// collectionView調(diào)用reloadData后吼过,layout會重新prepareLayout
NSInteger itemsCount = [self.collectionView.dataSource collectionView:self.collectionView numberOfItemsInSection:0];
// item間距
self.minimumInteritemSpacing = kPaddingMid;
self.minimumLineSpacing = kPaddingMid;
self.itemSize = CGSizeMake(kItemWidth, kItemWidth);
CGFloat collectionViewWidth = CGRectGetWidth(self.collectionView.bounds);
// 每頁可以完整顯示的items個數(shù)
NSInteger pageCapacity = (NSInteger)(collectionViewWidth - kPaddingLeft + kPaddingMid) / (NSInteger)(kItemWidth + kPaddingMid);
_pageCapacity = pageCapacity;
// 完整顯示所有items的總頁數(shù)
NSInteger pages = itemsCount / pageCapacity;
NSInteger remainder = itemsCount % pageCapacity;
if (remainder == 0) {
paddingRight = collectionViewWidth - pageCapacity * (kItemWidth + kPaddingMid) + kPaddingMid - kPaddingLeft;
} else {
paddingRight = collectionViewWidth - remainder * (kItemWidth + kPaddingMid) + kPaddingMid - kPaddingLeft;
pages ++;
}
// padding top bottom
CGFloat paddingVertical = (CGRectGetHeight(self.collectionView.bounds) - kItemWidth) / 2;
self.sectionInset = UIEdgeInsetsMake(paddingVertical, kPaddingLeft, paddingVertical, paddingRight);
}
3. 重寫- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity
方法
重寫這個方法就可以指定滑動停止的位置,我的計算思路是先根據(jù)用戶的滑動手勢咪奖,判斷是向前翻頁還是向后翻頁盗忱,向后翻頁則目標頁碼index = _currentIndex + 1
。 翻頁時羊赵,實際的頁面寬度是每頁剛好可以完整展示的最多個item的寬度趟佃,即_pageCapacity * (kItemWidth + kPaddingMid)
,那么x軸目標偏移就是point.x = 目標頁碼 * 每頁實際寬度
這里需要知道滑動前當前的頁碼_currentIndex
昧捷, 我是通過UIScrollViewDelegate
的代理方法取到用戶將要滑動時的x軸偏移計算的
#pragma mark --- UIScrollViewDelegate
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
_currentIndex = (NSInteger)(scrollView.contentOffset.x ) / (NSInteger)(_pageCapacity * (kItemWidth + kPaddingMid));
}
- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity {
NSInteger index = (NSInteger)proposedContentOffset.x / (NSInteger)(_pageCapacity * (kItemWidth + kPaddingMid));
NSInteger remainder = (NSInteger)proposedContentOffset.x % (NSInteger)(_pageCapacity * (kItemWidth + kPaddingMid));
if (remainder > 10 && velocity.x > 0.3) {
index ++;
}
if (velocity.x < -0.3 && index > 0) {
index --;
}
// 保證一次只滑動一頁
index = MAX(index, _currentIndex - 1);
index = MIN(index, _currentIndex + 1);
CGPoint point = CGPointMake(0, 0);
if (index > 0) {
point.x = index * _pageCapacity * (kItemWidth + kPaddingMid);
}
return point;
}
二闲昭、 不使用系統(tǒng)pagingEnabled
文章開頭已說明,設(shè)置scrollView.pagingEnabled = YES
達不到我們的目標靡挥,本文介紹的方案里需要設(shè)置scrollView.pagingEnabled = NO
序矩,否則上面函數(shù)中自定義的滑動位置不起作用
三、 盡量還原pagingEnabled效果
設(shè)置scrollView.decelerationRate = UIScrollViewDecelerationRateFast;
,滑動效果基本接近系統(tǒng)pagingEnabled
運行起來后簡單測試跋破,需求基本滿足了
四簸淀、有兩個bug
1. 滑動有時會卡頓
2. 從后往前翻頁時毒返,有時會連續(xù)翻兩頁
3. 檢查出錯原因,在MyCollectionFlowLayout.m
文件里加上日志
#pragma mark --- UIScrollViewDelegate
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
_currentIndex = (NSInteger)(scrollView.contentOffset.x) / (NSInteger)(_pageCapacity * (kItemWidth + kPaddingMid));
NSLog(@"\n\n---------------------");
NSLog(@"1. 預(yù)期每頁內(nèi)容寬度 %ld",(NSInteger)(_pageCapacity * (kItemWidth + kPaddingMid)));
NSLog(@"2. 滑動前的x軸偏移 %ld",(NSInteger)(scrollView.contentOffset.x));
NSLog(@"3. 滑動前當前頁碼 %ld",_currentIndex);
}
從打印日志發(fā)現(xiàn)從第一頁翻到第二頁饿悬,然后(未等滑動完全停止)繼續(xù)滑動時令蛉,x軸偏移量比目標偏移量小幾個像素,即滑動還沒有完全結(jié)束狡恬。由于當前頁的index是通過x軸偏移量取整求商得到的,這幾個像素的差異會導(dǎo)致index比預(yù)期小1
4. 解決方法
在計算當前的頁碼_currentIndex
時蝎宇,用一個item的寬度補償x軸偏移量弟劲,由于kItemWidth
恒小于_pageCapacity * (kItemWidth + kPaddingMid)
,這種補償不會造成頁面index加1姥芥,是安全的
#pragma mark --- UIScrollViewDelegate
- (**void**)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
*/**
** 分子scrollView.contentOffset.x為什么要+kItemWidth 兔乞??*
** 消除scrollView在擺動的時候的誤差凉唐,此時contentOffset.x比預(yù)期減少了10左右像素庸追,導(dǎo)致_currentIndex比預(yù)期小1*
**/*
_currentIndex = (NSInteger)(scrollView.contentOffset.x + kItemWidth) / (NSInteger)(_pageCapacity * (kItemWidth + kPaddingMid));
}