参考:
自定义布局和自定义流水布局
换句话说,UITableView的布局是UICollectionView的flow layout布局的一种特殊情况,类比于同矩形与正方形的关系
collectionView注册和重用时的identify不一致,会闪退报这个错:
reason: 'could not dequeue a view of kind: UICollectionElementKindCell with identifier firstCell - must register a nib or a class for the identifier or connect a prototype cell in a storyboard'
2.collectionview 刚开始的实例的时候一定要给尺寸,否则会闪
下面重点说说,除了系统默认的一般的流水布局之外的自定义布局:
自定义布局分两种情况: 1.继承UICollectionViewFlowLayout。 2.直接继承其上一层的UICollectionViewLayout。
1.先来说说第一种继承UICollectionViewFlowLayout的情况。
关键是两大基础方法,经过调试,发现其触发顺序依次为:
1.1,
/**
* 这个方法的特点是:当collectionView的显示范围发生改变的时候,判断是否需要重新刷新布局
* 一旦重新刷新布局,就重新调用下面的方法:
1.prepareLayout
2.layoutAttibutesForElementsInrect:
*
* @return 默认是NO
*/
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds//1
{
return YES;
}
1.2
- (void)prepareLayout//2
{
//在这个方法中做一些初始化操作
[super prepareLayout];
//collectionview貌似不重写布局改不了水平滑动
// 水平滚动
self.scrollDirection = UICollectionViewScrollDirectionHorizontal;
// 设置内边距
// CGFloat inset = (self.collectionView.frame.size.width - self.itemSize.width) * 0.5;
// self.sectionInset = UIEdgeInsetsMake(0, inset, 0, inset);
}
1.3,因为第三步有很多方法供自定义,这仅是举个例子来聊一下,(例子:根据当前item是否在collectionView中点来确定其缩放
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect//3
{
// 获得super已经计算好的布局属性
NSArray *elementsArray = [super layoutAttributesForElementsInRect:rect];
// 计算collectionView最中心点的x值
CGFloat centerX = self.collectionView.contentOffset.x + self.collectionView.frame.size.width * 0.5;
//在原有布局属性的基础上,进行微调
for (int i = 0;i
首先要吐槽的是:
为什么是处理数组(elementsArray)?
原因:UICollectionViewLayoutAttributes
- 它是描述布局属性的
- 一个item对应一个UICollectionViewLayoutAttributes对象
- UICollectionViewLayoutAttributes对象决定了cell的展示样式(frame)说白了就是决定你的item摆在哪里,怎么去摆
换言之:
因为每个item对应着一个UICollectionViewLayoutAttributes对象,所以会有很多该对象,所以遍历数组来处理,就不足为奇了。
吐槽点2:
计算collectionView的最中心点的x值为什么要加上偏移值(contentOffset)呢?(ps:调试发现,水平方向上,向左滑为正,右滑为负)
总的来说,是被迫加的!
因为要拿item的centerX,但是item的UICollectionViewLayoutAttributes对象坐标原点是以内容的contentsize的原点(contentsize最左上角的那个)为原点的,所以如果要算该cell与collectionview的中点的差值时,collectionView的中点被迫加上偏移值。
另外,值得一提的是
在算比例的过程中,0是一个很特殊位置的,一般用来处理目标位置
以下是布局成圆的例子:
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
{
return YES;
}
- (void)prepareLayout
{
[super prepareLayout];
[self.attrsArray removeAllObjects];
NSInteger count = [self.collectionView numberOfItemsInSection:0];
for (int i = 0; i < count; i++) {
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0];
UICollectionViewLayoutAttributes *attrs = [self layoutAttributesForItemAtIndexPath:indexPath];
[self.attrsArray addObject:attrs];
}
}
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
return self.attrsArray;
}
/**
* 这个方法需要返回indexPath位置对应cell的布局属性
*/
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
NSInteger count = [self.collectionView numberOfItemsInSection:0];
CGFloat radius = 70;
// 圆心的位置
CGFloat oX = self.collectionView.frame.size.width * 0.5;
CGFloat oY = self.collectionView.frame.size.height * 0.5;
UICollectionViewLayoutAttributes *attrs = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
attrs.size = CGSizeMake(50, 50);
if (count == 1) {
attrs.center = CGPointMake(oX, oY);
} else {
CGFloat angle = (2 * M_PI / count) * indexPath.item;
CGFloat centerX = oX + radius * sin(angle);
CGFloat centerY = oY + radius * cos(angle);
attrs.center = CGPointMake(centerX, centerY);
}
return attrs;
}
二。2.直接UICollectionViewLayout(非流水布局的根布局)
2.1使用的场景:
一开始,itemSize不一样大(流水布局的ItemSize貌似一开始一样大的,后面调的不算)
2.2 使用的注意:
1.一旦你重写了layoutAttributesForElementsInRect这个方法,就意味着所有东西你得自己写了,你的Attributes对象得自己创建了,因为它的父类不会帮你创建
2.一旦你继承自CollectionViewLayout,意味着你这个collectionViewContentSize都得告诉它了,这个是得你自己去算的(这个跟用原始的scrollview一样的)
3.如果你是希望一口气把所有东西算完,不希望它在滚动过程中再算,你可以在prepareLayout方法里面先算清楚,算完后尽管它传的矩形框都不一样。
2.3 使用的举例:
- (void)prepareLayout
{
[super prepareLayout];
[self.attrsArray removeAllObjects];
NSInteger count = [self.collectionView numberOfItemsInSection:0];
for (int i = 0; i < count; i ++)
{
//取出单个布局的对象
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0];
UICollectionViewLayoutAttributes *attrs = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
//设置布局属性
CGFloat width = self.collectionView.frame.size.width * 0.5;
if (i == 0) {
CGFloat height = width;
CGFloat x = 0;
CGFloat y = 0;
attrs.frame = CGRectMake(x, y, width, height);
} else if (i == 1){
CGFloat height = width * 0.5;
CGFloat x = width;
CGFloat y = 0;
attrs.frame = CGRectMake(x, y, width, height);
} else if (i == 2){
CGFloat height = width * 0.5;
CGFloat x = width;
CGFloat y = height;
attrs.frame = CGRectMake(x, y, width, height);
}else if (i == 3) {
CGFloat height = width * 0.5;
CGFloat x = 0;
CGFloat y = width;
attrs.frame = CGRectMake(x, y, width, height);
} else if (i == 4) {
CGFloat height = width * 0.5;
CGFloat x = 0;
CGFloat y = width + height;
attrs.frame = CGRectMake(x, y, width, height);
} else if (i == 5) {
CGFloat height = width;
CGFloat x = width;
CGFloat y = width;
attrs.frame = CGRectMake(x, y, width, height);
} else {
/*这个颇为“巧”了,是重用上面的布局,
*/
UICollectionViewLayoutAttributes *lastAttrs = self.attrsArray[i - 6];
CGRect lastFrame = lastAttrs.frame;
lastFrame.origin.y += 2 * width;
attrs.frame = lastFrame;
}
// 添加UICollectionViewLayoutAttributes
[self.attrsArray addObject:attrs];
}
}
/**
* 返回collectionView的内容大小
*/
- (CGSize)collectionViewContentSize
{
int count = (int)[self.collectionView numberOfItemsInSection:0];
int rows = (count + 3 - 1) / 3;
CGFloat rowH = self.collectionView.frame.size.width * 0.5;
return CGSizeMake(0, rows * rowH);
}
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
return self.attrsArray;
}
3,根据collectionView的item来更新其高度
有时候的需求只是把collectionview当做某个界面的小小一部分。所以就需要根据内容item来更新父级frame(主要就是高度),但我们知道collectionview的布局可谓是五花八门,(说好听点)有时会根据屏幕自动适配,(说难听点)就是item乱动。
那么正因为item的多种布局,导致contentSize的不确定,我们该如何根据最终的contentSize,来确定collection View的frame,甚至以此来更新父view的frame呢?
问题的突破口是我们要确定contentsize的frame,其实主要用到的是高度,
以下就以高度为例提供一个思路吧
在定义的UICollectionViewFlowLayout里面
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
NSArray * layoutAttributes_t = [super layoutAttributesForElementsInRect:rect];
NSArray * layoutAttributes = [[NSArray alloc]initWithArray:layoutAttributes_t copyItems:YES];
//用来临时存放一行的Cell数组
NSMutableArray * layoutAttributesTemp = [[NSMutableArray alloc]init];
for (NSUInteger index = 0; index < layoutAttributes.count ; index++) {
//防闪
UICollectionViewLayoutAttributes *currentAttr = layoutAttributes[index]; // 当前cell的位置信息,这个当前的意思是当前index的,别误解了咯
UICollectionViewLayoutAttributes *previousAttr = index == 0 ? nil : layoutAttributes[index-1]; // 上一个cell 的位置信
UICollectionViewLayoutAttributes *nextAttr = index + 1 == layoutAttributes.count ?
nil : layoutAttributes[index+1];//下一个cell 位置信息
//加入临时数组
[layoutAttributesTemp addObject:currentAttr];
_sumWidth += currentAttr.frame.size.width;
if (index == layoutAttributes.count - 1){
//主要是这里了,拿最后的那个,从而大致的得到其content size的高度
_sumHeight = currentAttr.frame.origin.y + currentAttr.frame.size.height;
NSDictionary *dict = @{collectionAllItemHeight:@(_sumHeight)};
[[NSNotificationCenter defaultCenter] postNotificationName:collectionAllItemHeight object:nil userInfo:dict];
}
//CGRectGetMaxY可以直接拿y的哟
CGFloat previousY = previousAttr == nil ? 0 : CGRectGetMaxY(previousAttr.frame);
CGFloat currentY = CGRectGetMaxY(currentAttr.frame);
CGFloat nextY = nextAttr == nil ? 0 : CGRectGetMaxY(nextAttr.frame);
//如果当前cell是单独一行
if (currentY != previousY && currentY != nextY){
}
//如果下一个cell在本行,这开始调整Frame位置
else if( currentY != nextY) {
}
}
return layoutAttributes;
}
就是在- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
方法中拿到最后一个item,进而根据其拿到大概content size的高度,通过通知的形式发布出去给外部使用。