UICollectionView自定義pagingEnabled翻頁區(qū)域

這個標題挺難起的:

UICollectionView設(shè)置翻頁區(qū)域朋贬?
UICollectionView依據(jù)items翻頁项玛,而不是屏幕寬度田晚?
UICollectionView每一頁開頭第一個item不被切割,并且左間距固定?
滤淳。。砌左。

直接看效果:

第一頁item4未展示全脖咐,第二頁從item4開始

比較一下直接設(shè)置collectionView.pagingEnabled = YES的效果:

第一頁item4展示一部分,第二頁展示item4剩下部分

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右縮進

在這個方法里,需要計算:

  1. 每頁可以完整顯示的items個數(shù)
  2. 完整顯示所有items的總頁數(shù)
  3. 最后一頁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);
}
log.png

從打印日志發(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));
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市台囱,隨后出現(xiàn)的幾起案子淡溯,更是在濱河造成了極大的恐慌,老刑警劉巖簿训,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件咱娶,死亡現(xiàn)場離奇詭異米间,居然都是意外死亡,警方通過查閱死者的電腦和手機膘侮,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進店門屈糊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人琼了,你說我怎么就攤上這事逻锐。” “怎么了雕薪?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵昧诱,是天一觀的道長。 經(jīng)常有香客問我蹦哼,道長鳄哭,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任纲熏,我火速辦了婚禮妆丘,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘局劲。我一直安慰自己勺拣,他們只是感情好,可當我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布鱼填。 她就那樣靜靜地躺著药有,像睡著了一般。 火紅的嫁衣襯著肌膚如雪苹丸。 梳的紋絲不亂的頭發(fā)上愤惰,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天,我揣著相機與錄音赘理,去河邊找鬼宦言。 笑死,一個胖子當著我的面吹牛商模,可吹牛的內(nèi)容都是我干的奠旺。 我是一名探鬼主播,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼施流,長吁一口氣:“原來是場噩夢啊……” “哼响疚!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起瞪醋,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤忿晕,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后趟章,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體杏糙,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡慎王,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了宏侍。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片赖淤。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖谅河,靈堂內(nèi)的尸體忽然破棺而出咱旱,到底是詐尸還是另有隱情,我是刑警寧澤绷耍,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布吐限,位于F島的核電站,受9級特大地震影響褂始,放射性物質(zhì)發(fā)生泄漏诸典。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一崎苗、第九天 我趴在偏房一處隱蔽的房頂上張望狐粱。 院中可真熱鬧,春花似錦胆数、人聲如沸肌蜻。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蒋搜。三九已至,卻和暖如春判莉,著一層夾襖步出監(jiān)牢的瞬間豆挽,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工券盅, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留祷杈,地道東北人。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓渗饮,卻偏偏與公主長得像,于是被迫代替她去往敵國和親宿刮。 傳聞我的和親對象是個殘疾皇子互站,可洞房花燭夜當晚...
    茶點故事閱讀 42,786評論 2 345