自定義CollectionView布局---滑動的卡片

效果圖
效果圖

這是一種很常見的布局,可以使用CollectionViewFlowLayout戈稿,在其代理方法中通過相關設置來達到此效果笛质,但還是比較麻煩吹泡。如若直接用CollectionViewLayout來實現(xiàn),會簡單不少经瓷,且靈活性較好爆哑。

其實此布局主要考慮的問題就兩點:

1.實現(xiàn)任意尺寸的分頁大小,即每次滑動后某一張卡片都能停在屏幕中間(默認collectionView開啟pageEnable后是以其尺寸來作為分頁大杏咚薄)揭朝。
2.放大過程中實現(xiàn)每張卡片的縮放。

官方文檔得知自定義CollectionViewLayout至少需要重寫以下方法:

// 當collectionView滑動的時候,可見區(qū)域改變的時候是否使當前布局失效以重新布局
1.shouldInvalidateLayoutForBoundsChange:

// 需要在此方法中返回collectionView的內(nèi)容大小
2.collectionViewContentSize

// 為每個Cell返回一個對應的Attributes色冀,我們需要在該Attributes中設置對應的屬性潭袱,如Frame等
3.layoutAttributesForItemAtIndexPath:

// 可在此方法中對可見rect中的cell的屬性進行相應設置
4.layoutAttributesForElementsInRect:

我們就以橫向滾動的布局為例來實現(xiàn)此布局,首先锋恬,定義好所需的屬性:

@property (nonatomic, assign) CGFloat spacing; //cell間距
@property (nonatomic, assign) CGSize itemSize; //cell的尺寸
@property (nonatomic, assign) CGFloat scale; //縮放率
@property (nonatomic, assign) UIEdgeInsets edgeInset; //邊距

接下來是重寫上面提到的幾個方法
1. shouldInvalidateLayoutForBoundsChange:

// 由于是此布局是平鋪的效果屯换,所以當collectionView的bounds變化時,所展現(xiàn)的cell的個數(shù)及顯示效果可能會發(fā)生變化与学,故此方法應返回YES彤悔。

- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds {
    return YES;
}

2. collectionViewContentSize

// 由于此布局一般只有一個section,故在此例中僅考慮只有一個section的情況
// 內(nèi)容的寬度為: 左邊距 + n*cell的寬 + (n-1)*cell的間距 + 右邊距索守。

- (CGSize)collectionViewContentSize {
    NSInteger count = [self.collectionView numberOfItemsInSection:0];
    CGFloat width = count*(self.itemSize.width+self.spacing)-self.spacing+self.edgeInset.left+self.edgeInset.right;
    CGFloat height = self.collectionView.bounds.size.height;
    return CGSizeMake(width, height);
}

3.layoutAttributesForItemAtIndexPath:

// 此方法要求返回一個UICollectionViewLayoutAttributes * 類型的對象
// 該對象包含對應cell外觀所需的必要屬性晕窑,包括center、frame卵佛、transform杨赤、alpha及其他屬性
// 在此方法中只需要做一件事,那就是給cell設置好正確的frame截汪。

- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {
    
    UICollectionViewLayoutAttributes *attribute = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
    attribute.size = self.itemSize;
    
    CGFloat x = self.edgeInset.left + indexPath.item*(self.spacing+self.itemSize.width);
    CGFloat y = 0.5*(self.collectionView.bounds.size.height - self.itemSize.height);
    attribute.frame = CGRectMake(x, y, attribute.size.width, attribute.size.height);
    
    return attribute;
}

4.layoutAttributesForElementsInRect:

// 可在此方法中設置縮放效果

- (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect {
   
    NSArray *indexPaths = [self indexPathsInRect:rect];
    
    //找到屏幕中間的位置
    CGFloat centerX =  self.collectionView.contentOffset.x + 0.5*self.collectionView.bounds.size.width;
   NSMutableArray *attributes = [NSMutableArray array];
    for (NSIndexPath *indexPath in indexPaths) {
        UICollectionViewLayoutAttributes* attribute = [self layoutAttributesForItemAtIndexPath:indexPath];
        // 判斷可見區(qū)域和此cell的frame是否有重疊疾牲,因為indexPathsInRect返回的indexPath并不是十分準確。
        if (!CGRectIntersectsRect(rect, attribute.frame)) {
            //若不重疊則無需進行以下的步驟
            continue;
        }
        [attributes addObject:attribute];
        //計算每一個cell離屏幕中間的距離
        CGFloat offsetX = ABS(attribute.center.x - centerX);
        //這是設置一個縮放區(qū)域的閾值衙解,當cell在此區(qū)域之外不進行縮放阳柔,改值可視具體情況進行修改。
        CGFloat space = self.itemSize.width+self.spacing;
        if (offsetX<space) {
            CGFloat scale = 1+(1-offsetX/space)*(self.scale-1);
            attribute.transform = CGAffineTransformMakeScale(scale, scale);
           // 設置此屬性是為了當cell層疊后丢郊,使得位于中間的cell總是位于最前面盔沫,若不明白可將此行注釋一試便知医咨。 
            attribute.zIndex = 1;
        }
    }
    return attributes;
}

- (NSArray *)indexPathsInRect:(CGRect)rect {
    
    NSInteger leftIndex = (rect.origin.x-self.edgeInset.left)/(self.itemSize.width+self.spacing);
    NSInteger rightIndex = (CGRectGetMaxX(rect)-self.edgeInset.left)/(self.itemSize.width+self.spacing);

    NSInteger itemCount = [self.collectionView numberOfItemsInSection:0];
    preIndex = preIndex<0 ? 0 : preIndex;
    latIndex = latIndex>=itemCount ? itemCount-1 : latIndex;
    
    NSMutableArray *indexPaths = [NSMutableArray array];
    for (NSInteger i=leftIndex; i<=rightIndex; i++) {
        NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0];
        [indexPaths addObject:indexPath];
    }
    return indexPaths;
}

至此枫匾,我們已經(jīng)實現(xiàn)了在滾動過程中靠近屏幕中間的cell放大的效果,但是還沒實現(xiàn)滾動停止時某一個cell正好在屏幕中間拟淮,要想實現(xiàn)此效果干茉,需要在targetContentOffsetForProposedContentOffset:withScrollingVelocity方法中實現(xiàn)此邏輯。此方法會給一個系統(tǒng)默認計算好的collectionView應該停下來的位置很泊,返回一個collectionView最后要停下來的位置角虫。

// 需要在此方法中獲取默認情況下停止?jié)L動時離屏幕中間最近的那個cell沾谓,并計算兩者的距離,將此距離補到proposedContentOffset上即可戳鹅。

- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity {
    
    CGRect rect = CGRectMake(proposedContentOffset.x, proposedContentOffset.y, self.collectionView.bounds.size.width, self.collectionView.bounds.size.height);
    NSArray *attributes = [self layoutAttributesForElementsInRect:rect];
    
    CGFloat centerX = proposedContentOffset.x + 0.5*self.collectionView.bounds.size.width;
    CGFloat minOffsetX = MAXFLOAT;
    for (UICollectionViewLayoutAttributes* attribute in attributes) {
        CGFloat offsetX = attribute.center.x - centerX;
        if (ABS(offsetX) < ABS(minOffsetX)) {
            minOffsetX = offsetX;
        }
    }

    return CGPointMake(proposedContentOffset.x + minOffsetX, proposedContentOffset.y);
}

現(xiàn)在已經(jīng)實現(xiàn)了文章開頭動圖中展示的效果了均驶。再將之前定義的各個屬性的set方法重寫,以便在設置這些屬性的時候進行重新布局枫虏。

注意:若想實現(xiàn)多個section以及含有sectionHeader或sectionFooter妇穴,請參照此思路來實現(xiàn),并且重寫以下兩個方法:

// 含有sectionHeader或sectionFooter應重寫此方法
layoutAttributesForSupplementaryViewOfKind:atIndexPath:

// cell含有裝飾視圖時要重寫此方法隶债。
layoutAttributesForDecorationViewOfKind:atIndexPath:

想要源碼的可以點擊這里獲取腾它,GitHub上的版本支持水平和垂直布局且已加以優(yōu)化。
此外還有Swift版本的哦死讹,請點擊這里以獲取瞒滴。
若覺得對你有用的話,還請給個star哈~

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末赞警,一起剝皮案震驚了整個濱河市妓忍,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌愧旦,老刑警劉巖单默,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異忘瓦,居然都是意外死亡搁廓,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進店門耕皮,熙熙樓的掌柜王于貴愁眉苦臉地迎上來境蜕,“玉大人,你說我怎么就攤上這事凌停×荒辏” “怎么了?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵罚拟,是天一觀的道長台诗。 經(jīng)常有香客問我,道長赐俗,這世上最難降的妖魔是什么拉队? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮阻逮,結果婚禮上粱快,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好事哭,可當我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布漫雷。 她就那樣靜靜地躺著,像睡著了一般鳍咱。 火紅的嫁衣襯著肌膚如雪降盹。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天谤辜,我揣著相機與錄音澎现,去河邊找鬼。 笑死每辟,一個胖子當著我的面吹牛剑辫,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播渠欺,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼妹蔽,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了挠将?” 一聲冷哼從身側響起胳岂,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎舔稀,沒想到半個月后乳丰,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡内贮,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年产园,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片夜郁。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡什燕,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出竞端,到底是詐尸還是另有隱情屎即,我是刑警寧澤,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布事富,位于F島的核電站技俐,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏统台。R本人自食惡果不足惜雕擂,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望饺谬。 院中可真熱鬧捂刺,春花似錦谣拣、人聲如沸募寨。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽拔鹰。三九已至仪缸,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間列肢,已是汗流浹背恰画。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留瓷马,地道東北人拴还。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像欧聘,于是被迫代替她去往敵國和親片林。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內(nèi)容