在上一篇文章中,為大家介紹了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)注一下UICollectionViewFlowLayout
和UICollectionViewLayout
的關(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];
下面是效果圖:
(注:在開發(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)行程序贮预,查看效果:
我們發(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;
}
效果圖:
總結(jié):
基本上到這里介却,UICollectionVew的使用就結(jié)束了,如何能夠?qū)ICollectionVew使用的更好块茁,關(guān)鍵就在于怎樣更好的運(yùn)用UICollectionViewLayout和UICollectionViewFlowLayout齿坷,這才是UICollectionVew的精髓所在。
有興趣的同學(xué)数焊,可以移步到GitHub中下載Demo永淌,里面我為大家做了非常詳細(xì)的注釋。