UICollectionViewLayout简介
(1)基本方法
在UICollectionViewLayout时,我们主要会重写它的以下几个方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| - (void)prepareLayout;
//该方法返回collectionView的内容的大小 - (CGSize)collectionViewContentSize;
//该方法会返回rect范围内所有cell的布局属性UICollectionViewLayoutAttributes - (nullable NSArray<__kindof UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect;
//该方法返回对应indexPath下的cell的布局属性UICollectionViewLayoutAttributes - (nullable UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath;
//该方法返回在界面发生变化是是否要重新布局,返回YES则会重新布局 - (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds;
//返回滑动后的collectonView的偏移量(滑动所停止的点),默认返回proposedContentOffset参数的值,在这里我们可以手动设置实际需要的偏移量 - (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset;
|
prepareLayout会在三个时机调用:
1、初始化layout的时候
2、刷新layout的时候
3、方法- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
返回YES的时候
(2)UICollectionViewLayout与UICollectionViewFlowLayout
在此之前,我们先来简单的关注一下UICollectionViewFlowLayout
和UICollectionViewLayout
的关系:UICollectionViewFlowLayout
是系统为我们封装的一个继承于UICollectionViewLayout
的子类,系统已经写好了布局,所以如果我们在- (nullable NSArray<__kindof UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect;
方法中调用 NSArray *attributesArr = [super layoutAttributesForElementsInRect:rect];
,可以得到系统为我们写好的布局,但是如果直接继承于UICollectionViewLayout,上述方法得不到任何布局,所以我们必须要重写- (nullable UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath;
方法,在方法中写好布局并调用,这样才能为cell布局。
(3)UICollectionViewLayoutAttributes
关于cell的布局,我们还需要着重看一个类:UICollectionViewLayoutAttributes,它就是我们上面一直所说的cell的布局类,cell所有的布局属性都是要写到该类中的,那它到底都有哪些属性呢:
1 2 3 4 5 6 7 8 9 10
| @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; @property (nonatomic, getter=isHidden) BOOL hidden; @property (nonatomic, strong) NSIndexPath *indexPath;
|
改变了这些属性,并传递给layout,就可以改变cell的布局,所以归根到底,不管多复杂的布局,都是在改变这些属性。
自定义UICollectionViewLayout具体实现
下面,我们就在具体的实例中看一下,如果使用自定义layout:
创建一个继承于UICollectionViewLayout的子类
1 2 3
| #import <UIKit/UIKit.h> @interface My_1Layout : UICollectionViewLayout @end
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109
| #import "My_1Layout.h"
@interface My_1Layout() { UIEdgeInsets _edgeInset; CGFloat _lineSpacing; CGFloat _columnsSpacing; NSInteger _columnsNum; NSMutableArray *_columnsHeightArray; CGFloat _maxHeight; } @property (nonatomic,strong) NSMutableArray *attributesArray;
@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{
[super prepareLayout]; [_columnsHeightArray removeAllObjects]; for (int i = 0; i < _columnsNum ; i ++) { [_columnsHeightArray addObject:[NSNumber numberWithInteger:_edgeInset.top]]; } [self.attributesArray removeAllObjects];
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{
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; NSInteger minHeightColumn = 0; CGFloat minColumnHeight = [_columnsHeightArray[minHeightColumn] doubleValue]; for (int i = 1; i < _columnsHeightArray.count; i ++ ) { CGFloat tempH = [_columnsHeightArray[i] floatValue]; if (minColumnHeight > tempH) { minColumnHeight = tempH; minHeightColumn = i; } } 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{ [_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
中循环调用 -(UICollectionViewLayoutAttributes*)layoutAttributesForItemAtIndexPath:
方法,为所有的cell添加布局,最后从 - (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:
中将其返回即可。
本例中,调用layout的方法也很简单,就正常创建layout,并赋给collectionView就可以了:
1 2
| My_1Layout *layout = [[My_1Layout alloc]init]; UICollectionView* collectionView = [[UICollectionView alloc]initWithFrame:CGRectMake(0, 64, kScreenWidth, kScreenHeight-64) collectionViewLayout:layout];
|
下面是效果图:
(注:在开发中,对于需要在外部的控制器中设置layout属性的,包括内边距、行间距、列间距、列数以及cell的初始大小等,可以为layout添加代理,使用代理方法返回)。
下面我们再看一个例子:
首先,我们先创建一个继承于UICollectionViewFlowLayout的layout子类,layout类中不做任何实现,然后在控制器中赋值给collectionView,控制器中关于collectionView和数据源的设置和上例一样,然后运行程序,查看效果:
我们发现,尽管layout没有做任何布局,但是collectionView任然可以显示,这就说明,UICollectionViewFlowLayout已经为我们做好了一个布局,就是我们现在看到的流水布局,所以,对于继承于UICollectionViewFlowLayout的 类,如果要改变cell的布局,只需要获取系统默认为cell写好的布局,然后再此基础上进行修改就可以了。那么怎样获取UICollectionViewFlowLayout为我们写好的布局呢,使用父类调用 -(NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:
废话不多说,直接上代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
|
- (void)prepareLayout{ [super prepareLayout]; self.scrollDirection = UICollectionViewScrollDirectionHorizontal; self.itemSize = CGSizeMake(200, 200); CGFloat inset = (self.collectionView.frame.size.width - self.itemSize.width) * 0.5; self.sectionInset = UIEdgeInsetsMake(0, inset, 0, inset); }
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds{ return YES; }
- (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect{ NSArray *attrbutesArray = [super layoutAttributesForElementsInRect:rect]; CGFloat centerX = self.collectionView.contentOffset.x + self.collectionView.frame.size.width * 0.5; for (UICollectionViewLayoutAttributes *attributes in attrbutesArray) { CGFloat distance = ABS(attributes.center.x - centerX); CGFloat scale = 1 - (distance / self.collectionView.frame.size.width); attributes.transform3D = CATransform3DMakeScale(scale, scale, scale); } return attrbutesArray; }
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath { UICollectionViewLayoutAttributes *attr = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath]; return attr; }
- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity { CGRect endRect; endRect.origin.x = proposedContentOffset.x; endRect.origin.y = 0; endRect.size = CGSizeMake(self.collectionView.frame.size.width, self.collectionView.frame.size.height); NSArray *attributesArr = [super layoutAttributesForElementsInRect:endRect]; CGFloat centerX = proposedContentOffset.x + self.collectionView.frame.size.width * 0.5; CGFloat minDelta = MAXFLOAT; for (UICollectionViewLayoutAttributes *attr in attributesArr) { if(ABS(minDelta) > ABS(attr.center.x - centerX)) { minDelta = attr.center.x - centerX; } } proposedContentOffset.x += minDelta; return proposedContentOffset; }
|
效果图:
总结:
基本上到这里,UICollectionVew的使用就结束了,如何能够将UICollectionVew使用的更好,关键就在于怎样更好的运用UICollectionViewLayout和UICollectionViewFlowLayout,这才是UICollectionVew的精髓所在。