UICollectionView詳解(二)—— 自定義UICollectionViewLayout

在上一篇文章中,為大家介紹了UICollectionView的基本使用方法映九,過(guò)程和內(nèi)容都比較簡(jiǎn)單,有興趣看的同學(xué)可以點(diǎn)擊這里,本次將為大家介紹的是如何使用UICollectionLayout自定義復(fù)雜的布局绽诚。

1、UICollectionViewLayout簡(jiǎn)介

(1)基本方法

在UICollectionViewLayout時(shí)杭煎,我們主要會(huì)重寫它的以下幾個(gè)方法

- (void)prepareLayout;

prepareLayout會(huì)在三個(gè)時(shí)機(jī)調(diào)用恩够,第一次初始化layout的時(shí)候,刷新layout的時(shí)候以及方法- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds返回YES的時(shí)候

- (CGSize)collectionViewContentSize;

該方法返回collectionView的內(nèi)容的大小

- (nullable NSArray<__kindof UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect; 

該方法會(huì)返回rect范圍內(nèi)所有cell的布局屬性UICollectionViewLayoutAttributes

- (nullable UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath;

該方法返回對(duì)應(yīng)indexPath下的cell的布局屬性UICollectionViewLayoutAttributes

- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds;

該方法返回在界面發(fā)生變化是是否要重新布局羡铲,返回YES則會(huì)重新布局

- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset;

返回滑動(dòng)后的collectonView的偏移量(滑動(dòng)所停止的點(diǎn))蜂桶,默認(rèn)返回proposedContentOffset參數(shù)的值,在這里我們可以手動(dòng)設(shè)置實(shí)際需要的偏移量

(2)UICollectionViewLayout與UICollectionViewFlowLayout

在此之前也切,我們先來(lái)簡(jiǎn)單的關(guān)注一下UICollectionViewFlowLayoutUICollectionViewLayout的關(guān)系:UICollectionViewFlowLayout是系統(tǒng)為我們封裝的一個(gè)繼承于UICollectionViewLayout的子類扑媚,系統(tǒng)已經(jīng)寫好了布局,所以如果我們?cè)?code>- (nullable NSArray<__kindof UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect;方法中調(diào)用 NSArray *attributesArr = [super layoutAttributesForElementsInRect:rect];雷恃,可以得到系統(tǒng)為我們寫好的布局疆股,但是如果直接繼承于UICollectionViewLayout,上述方法得不到任何布局褂萧,所以我們必須要重寫- (nullable UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath;方法押桃,在方法中寫好布局并調(diào)用,這樣才能為cell布局导犹。

(3)UICollectionViewLayoutAttributes

關(guān)于cell的布局唱凯,我們還需要著重看一個(gè)類:UICollectionViewLayoutAttributes,它就是我們上面一直所說(shuō)的cell的布局類谎痢,cell所有的布局屬性都是要寫到該類中的磕昼,那它到底都有哪些屬性呢:

@property (nonatomic) CGRect frame;
@property (nonatomic) CGPoint center;
@property (nonatomic) CGSize size;
@property (nonatomic) CATransform3D transform3D;
@property (nonatomic) CGRect bounds );
@property (nonatomic) CGAffineTransform transform ;
@property (nonatomic) CGFloat alpha;
@property (nonatomic) NSInteger zIndex; // default is 0
@property (nonatomic, getter=isHidden) BOOL hidden; 
@property (nonatomic, strong) NSIndexPath *indexPath;

改變了這些屬性,并傳遞給layout节猿,就可以改變cell的布局票从,所以歸根到底漫雕,不管多復(fù)雜的布局,都是在改變這些屬性峰鄙。

自定義UICollectionViewLayout具體實(shí)現(xiàn)

下面浸间,我們就在具體的實(shí)例中看一下,如果使用自定義layout:

先為大家貼出代碼:

創(chuàng)建一個(gè)繼承于UICollectionViewLayout的子類

.h

#import <UIKit/UIKit.h>
@interface My_1Layout : UICollectionViewLayout
@end

.m

#import "My_1Layout.h"

@interface My_1Layout()
{
    UIEdgeInsets _edgeInset;//內(nèi)邊距
    CGFloat _lineSpacing;//行間距
    CGFloat _columnsSpacing;//列間距
    NSInteger _columnsNum;//列數(shù)
    NSMutableArray *_columnsHeightArray;//用來(lái)存放所有列的高度
    CGFloat _maxHeight;//collectionContent最大高度
}
@property (nonatomic,strong) NSMutableArray *attributesArray;//用來(lái)存放所有的cell的布局


@end

@implementation My_1Layout

- (instancetype)init{
    if ([super init]) {
        _edgeInset = UIEdgeInsetsMake(5, 10, 5, 10);
        _lineSpacing = 10;
        _columnsSpacing = 10;
        _columnsNum = 3;
        _maxHeight = _edgeInset.top;
        _columnsHeightArray = [NSMutableArray new];
        _columnsHeightArray = [NSMutableArray arrayWithCapacity:_columnsNum];
        }
    return self;
}

- (void)prepareLayout{
    /**
     切記吟榴,一定要先調(diào)用父類的prepareLayout
     */
    [super prepareLayout];
    
    [_columnsHeightArray removeAllObjects];
    for (int i = 0; i < _columnsNum ; i ++) {
        [_columnsHeightArray addObject:[NSNumber numberWithInteger:_edgeInset.top]];
    }
    
    [self.attributesArray removeAllObjects];
    /**
     調(diào)用layoutAttributesForItemAtIndexPath:方法魁蒜,根據(jù)collectionView中cell的個(gè)數(shù),使用for循環(huán)吩翻,創(chuàng)建對(duì)應(yīng)個(gè)數(shù)的cell的attributes兜看,并存放到_columnsHeightArray數(shù)組中(也可以將該過(guò)程放到layoutAttributesForElementsInRect:中去執(zhí)行)
     */
    NSInteger cellNum = [self.collectionView numberOfItemsInSection:0];
    for (int i = 0; i < cellNum; i ++) {
        NSIndexPath*indexPath=[NSIndexPath indexPathForItem:i inSection:0];
        UICollectionViewLayoutAttributes *attri = [self layoutAttributesForItemAtIndexPath:indexPath];
        [self.attributesArray addObject:attri];
    }
}
-(BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds{
    
    return YES;
}
-(NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect{
    /**
     直接返回之前存放好的所有cell的attributes(也可以將prepareLayout方法中for循環(huán)創(chuàng)建attributes的過(guò)程放到這里執(zhí)行)
     */
    return self.attributesArray;
}
-(UICollectionViewLayoutAttributes*)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath{
    UICollectionViewLayoutAttributes*attributes=[UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
    
    CGFloat cellW = (kScreenWidth-_edgeInset.left-_edgeInset.right-(_columnsNum-1)*_columnsSpacing)/_columnsNum;
    CGFloat cellH = indexPath.item%2==0?160:125;
    // 應(yīng)該添加cell的列號(hào)
    NSInteger minHeightColumn = 0;
    // 應(yīng)該添加cell的列的高度
    CGFloat minColumnHeight = [_columnsHeightArray[minHeightColumn] doubleValue];
    //循環(huán)  獲取最小的列的高度和該列的列號(hào)
    for (int i = 1; i < _columnsHeightArray.count; i ++ ) {
        CGFloat tempH = [_columnsHeightArray[i] floatValue];
        if (minColumnHeight > tempH) {
            minColumnHeight = tempH;
            minHeightColumn = i;
        }
    }
    //為高度最小的列添加cell
    CGFloat cellY = [_columnsHeightArray[minHeightColumn] floatValue]+_lineSpacing;
    CGFloat cellX = _edgeInset.left + minHeightColumn * (cellW + _columnsSpacing);
    attributes.frame = CGRectMake(cellX, cellY, cellW, cellH);
    //保存最新的高度
    CGFloat newHeight = cellY+cellH;
    [_columnsHeightArray replaceObjectAtIndex:minHeightColumn withObject:[NSNumber numberWithInteger:newHeight]];
    //返回布局信息
    return attributes;
}
- (CGSize)collectionViewContentSize{
    //根據(jù)最高的列 設(shè)置collectionContentSize
    [_columnsHeightArray enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        CGFloat maxHeight = [_columnsHeightArray[idx]floatValue];
        if (maxHeight > _maxHeight) {
            _maxHeight = maxHeight;
        }
    }];
    return CGSizeMake(kScreenWidth, _maxHeight);
}
- (NSMutableArray *)attributesArray{
    if (!_attributesArray) {
        _attributesArray = [NSMutableArray new];
    }
    return _attributesArray;
}

大概思路就是:首先初始化layout的各種屬性和變量,在- (void)prepareLayout中循環(huán)調(diào)用-(UICollectionViewLayoutAttributes*)layoutAttributesForItemAtIndexPath:方法狭瞎,為所有的cell添加布局细移,最后從-(NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:中將其返回即可。本例中熊锭,

調(diào)用layout的方法也很簡(jiǎn)單弧轧,就正常創(chuàng)建layout,并賦給collectionView就可以了:

My_1Layout *layout = [[My_1Layout alloc]init];
    UICollectionView* collectionView = [[UICollectionView alloc]initWithFrame:CGRectMake(0, 64, kScreenWidth, kScreenHeight-64) collectionViewLayout:layout];

下面是效果圖:


效果圖-1.gif
(注:在開發(fā)中球涛,對(duì)于需要在外部的控制器中設(shè)置layout屬性的劣针,包括內(nèi)邊距、行間距亿扁、列間距捺典、列數(shù)以及cell的初始大小等,可以為layout添加代理从祝,使用代理方法返回)襟己。

下面我們?cè)倏匆粋€(gè)例子:
首先,我們先創(chuàng)建一個(gè)繼承于UICollectionViewFlowLayout的layout子類牍陌,layout類中不做任何實(shí)現(xiàn)擎浴,然后在控制器中賦值給collectionView,控制器中關(guān)于collectionView和數(shù)據(jù)源的設(shè)置和上例一樣毒涧,然后運(yùn)行程序贮预,查看效果:


效果圖-2.PNG

我們發(fā)現(xiàn),盡管layout沒(méi)有做任何布局契讲,但是collectionView任然可以顯示仿吞,這就說(shuō)明,UICollectionViewFlowLayout已經(jīng)為我們做好了一個(gè)布局捡偏,就是我們現(xiàn)在看到的流水布局唤冈,所以,對(duì)于繼承于UICollectionViewFlowLayout的 類银伟,如果要改變cell的布局你虹,只需要獲取系統(tǒng)默認(rèn)為cell寫好的布局绘搞,然后再此基礎(chǔ)上進(jìn)行修改就可以了。那么怎樣獲取UICollectionViewFlowLayout為我們寫好的布局呢傅物,使用父類調(diào)用-(NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:夯辖。

廢話不多說(shuō),直接上代碼:

/**
 *prepareLayout會(huì)頻繁調(diào)用挟伙,所以只做一些簡(jiǎn)單的初始化操作
 */
- (void)prepareLayout{
    [super prepareLayout];
    // 水平滾動(dòng)
    self.scrollDirection = UICollectionViewScrollDirectionHorizontal;
    //設(shè)置cell大新ケⅰ(可放到外面的控制器中)
    self.itemSize = CGSizeMake(200, 200);
    //設(shè)置內(nèi)邊距
    CGFloat inset = (self.collectionView.frame.size.width - self.itemSize.width) * 0.5;
    self.sectionInset = UIEdgeInsetsMake(0, inset, 0, inset);
    
}
/**
 *設(shè)置為YES,collectionView的顯示范圍發(fā)生變化時(shí)模孩,就要刷新布局
 */
-(BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds{
    
    return YES;
}
/**
 *返回所有cell的布局屬性
 */
-(NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect{
    //獲取UICollectionViweFlowLayout已經(jīng)做好的布局
    NSArray *attrbutesArray = [super layoutAttributesForElementsInRect:rect];
    //計(jì)算collectionView可視范圍的中心點(diǎn)所對(duì)應(yīng)的collectionView的x值
    CGFloat centerX = self.collectionView.contentOffset.x + self.collectionView.frame.size.width * 0.5;
    //以每個(gè)cell中心點(diǎn)到centerX的距離為參考尖阔,對(duì)cell進(jìn)行縮放
    for (UICollectionViewLayoutAttributes *attributes in attrbutesArray) {
        //cell的中心點(diǎn)到centerX的距離
        CGFloat distance = ABS(attributes.center.x - centerX);
        //根據(jù)distance計(jì)算cell的縮放比例
        CGFloat scale = 1 - (distance / self.collectionView.frame.size.width);
        //設(shè)置縮放比例
        attributes.transform3D = CATransform3DMakeScale(scale, scale, scale);
    }
    // 返回調(diào)整之后的布局屬性數(shù)組
    return attrbutesArray;
}
/**
 * 在重新刷新布局時(shí)調(diào)用
 */
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {
    UICollectionViewLayoutAttributes *attr = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
    return attr;
}
/**
 * collectionView停止滑動(dòng)時(shí)調(diào)用,可以手動(dòng)設(shè)置collectionView的偏移量
 * proposedContentOffset collectionView原本的偏移量
 */
- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity {
    //計(jì)算出最終顯示的矩形框
    CGRect endRect;
    endRect.origin.x = proposedContentOffset.x;
    endRect.origin.y = 0;
    endRect.size = CGSizeMake(self.collectionView.frame.size.width, self.collectionView.frame.size.height);
    
    //獲得所有cell的布局屬性
    NSArray *attributesArr = [super layoutAttributesForElementsInRect:endRect];
    
    //計(jì)算collectionView最中心點(diǎn)的x值
    CGFloat centerX = proposedContentOffset.x + self.collectionView.frame.size.width * 0.5;
    //循環(huán)所有的布局屬性榨咐,得到距離中心點(diǎn)最近的cell到中心點(diǎn)的距離
    CGFloat minDelta = MAXFLOAT;
    for (UICollectionViewLayoutAttributes *attr in attributesArr) {
        if(ABS(minDelta) > ABS(attr.center.x - centerX)) {
            minDelta = attr.center.x - centerX;
        }
    }
    //原來(lái)偏移量的x+距離中心點(diǎn)最近的cell到中心點(diǎn)的距離 將其設(shè)置為偏移量,該cell就會(huì)到中心點(diǎn)
    proposedContentOffset.x += minDelta;
    return proposedContentOffset;
}

效果圖:

效果圖-3.gif

總結(jié):

基本上到這里介却,UICollectionVew的使用就結(jié)束了,如何能夠?qū)ICollectionVew使用的更好块茁,關(guān)鍵就在于怎樣更好的運(yùn)用UICollectionViewLayout和UICollectionViewFlowLayout齿坷,這才是UICollectionVew的精髓所在。

有興趣的同學(xué)数焊,可以移步到GitHub中下載Demo永淌,里面我為大家做了非常詳細(xì)的注釋。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末佩耳,一起剝皮案震驚了整個(gè)濱河市遂蛀,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌干厚,老刑警劉巖李滴,帶你破解...
    沈念sama閱讀 222,946評(píng)論 6 518
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異蛮瞄,居然都是意外死亡所坯,警方通過(guò)查閱死者的電腦和手機(jī)景描,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,336評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門掐暮,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人挟纱,你說(shuō)我怎么就攤上這事闲先∽赐粒” “怎么了?”我有些...
    開封第一講書人閱讀 169,716評(píng)論 0 364
  • 文/不壞的土叔 我叫張陵饵蒂,是天一觀的道長(zhǎng)声诸。 經(jīng)常有香客問(wèn)我,道長(zhǎng)退盯,這世上最難降的妖魔是什么彼乌? 我笑而不...
    開封第一講書人閱讀 60,222評(píng)論 1 300
  • 正文 為了忘掉前任泻肯,我火速辦了婚禮,結(jié)果婚禮上慰照,老公的妹妹穿的比我還像新娘灶挟。我一直安慰自己,他們只是感情好毒租,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,223評(píng)論 6 398
  • 文/花漫 我一把揭開白布稚铣。 她就那樣靜靜地躺著,像睡著了一般墅垮。 火紅的嫁衣襯著肌膚如雪惕医。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,807評(píng)論 1 314
  • 那天算色,我揣著相機(jī)與錄音抬伺,去河邊找鬼。 笑死灾梦,一個(gè)胖子當(dāng)著我的面吹牛峡钓,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播若河,決...
    沈念sama閱讀 41,235評(píng)論 3 424
  • 文/蒼蘭香墨 我猛地睜開眼能岩,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了萧福?” 一聲冷哼從身側(cè)響起拉鹃,我...
    開封第一講書人閱讀 40,189評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎统锤,沒(méi)想到半個(gè)月后毛俏,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,712評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡饲窿,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,775評(píng)論 3 343
  • 正文 我和宋清朗相戀三年煌寇,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片逾雄。...
    茶點(diǎn)故事閱讀 40,926評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡阀溶,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出鸦泳,到底是詐尸還是另有隱情银锻,我是刑警寧澤,帶...
    沈念sama閱讀 36,580評(píng)論 5 351
  • 正文 年R本政府宣布做鹰,位于F島的核電站击纬,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏钾麸。R本人自食惡果不足惜更振,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,259評(píng)論 3 336
  • 文/蒙蒙 一炕桨、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧肯腕,春花似錦献宫、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,750評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至知态,卻和暖如春捷兰,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背肴甸。 一陣腳步聲響...
    開封第一講書人閱讀 33,867評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工寂殉, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人原在。 一個(gè)月前我還...
    沈念sama閱讀 49,368評(píng)論 3 379
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像彤叉,于是被迫代替她去往敵國(guó)和親庶柿。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,930評(píng)論 2 361

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