美文网首页ios奇门杂招移动端开发ios牛掰控件
UICollectionView详解(二)—— 自定义UICol

UICollectionView详解(二)—— 自定义UICol

作者: 沐泽sunshine | 来源:发表于2017-07-11 16:23 被阅读356次

在上一篇文章中,为大家介绍了UICollectionView的基本使用方法,过程和内容都比较简单,有兴趣看的同学可以点击这里,本次将为大家介绍的是如何使用UICollectionLayout自定义复杂的布局。

1、UICollectionViewLayout简介

(1)基本方法

在UICollectionViewLayout时,我们主要会重写它的以下几个方法

- (void)prepareLayout;

prepareLayout会在三个时机调用,第一次初始化layout的时候,刷新layout的时候以及方法- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds返回YES的时候

- (CGSize)collectionViewContentSize;

该方法返回collectionView的内容的大小

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

该方法会返回rect范围内所有cell的布局属性UICollectionViewLayoutAttributes

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

该方法返回对应indexPath下的cell的布局属性UICollectionViewLayoutAttributes

  • (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds;
该方法返回在界面发生变化是是否要重新布局,返回YES则会重新布局
  • (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset;
返回滑动后的collectonView的偏移量(滑动所停止的点),默认返回proposedContentOffset参数的值,在这里我们可以手动设置实际需要的偏移量
#####(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所有的布局属性都是要写到该类中的,那它到底都有哪些属性呢:

@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的布局,所以归根到底,不管多复杂的布局,都是在改变这些属性。
###自定义UICollectionViewLayout具体实现
下面,我们就在具体的实例中看一下,如果使用自定义layout:
####先为大家贴出代码:
######创建一个继承于UICollectionViewLayout的子类
>.h

import <UIKit/UIKit.h>

@interface My_1Layout : UICollectionViewLayout
@end

>.m

import "My_1Layout.h"

@interface My_1Layout()
{
UIEdgeInsets _edgeInset;//内边距
CGFloat _lineSpacing;//行间距
CGFloat _columnsSpacing;//列间距
NSInteger _columnsNum;//列数
NSMutableArray *_columnsHeightArray;//用来存放所有列的高度
CGFloat _maxHeight;//collectionContent最大高度
}
@property (nonatomic,strong) NSMutableArray *attributesArray;//用来存放所有的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{
    /**
    切记,一定要先调用父类的prepareLayout
    */
    [super prepareLayout];

    [_columnsHeightArray removeAllObjects];
    for (int i = 0; i < _columnsNum ; i ++) {
    [_columnsHeightArray addObject:[NSNumber numberWithInteger:_edgeInset.top]];
    }

    [self.attributesArray removeAllObjects];
    /**
    调用layoutAttributesForItemAtIndexPath:方法,根据collectionView中cell的个数,使用for循环,创建对应个数的cell的attributes,并存放到_columnsHeightArray数组中(也可以将该过程放到layoutAttributesForElementsInRect:中去执行)
    /
    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循环创建attributes的过程放到这里执行)
    /
    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;
    // 应该添加cell的列号
    NSInteger minHeightColumn = 0;
    // 应该添加cell的列的高度
    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;
    }
    }
    //为高度最小的列添加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{
    //根据最高的列 设置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中循环调用-(UICollectionViewLayoutAttributes*)layoutAttributesForItemAtIndexPath:方法,为所有的cell添加布局,最后从-(NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:中将其返回即可。本例中,

调用layout的方法也很简单,就正常创建layout,并赋给collectionView就可以了:

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

下面是效果图:
![效果图-1.gif](http:https://img.haomeiwen.com/i1914621/6c873fb7927b595e.gif?imageMogr2/auto-orient/strip)

(注:在开发中,对于需要在外部的控制器中设置layout属性的,包括内边距、行间距、列间距、列数以及cell的初始大小等,可以为layout添加代理,使用代理方法返回)。


下面我们再看一个例子:
首先,我们先创建一个继承于UICollectionViewFlowLayout的layout子类,layout类中不做任何实现,然后在控制器中赋值给collectionView,控制器中关于collectionView和数据源的设置和上例一样,然后运行程序,查看效果:
![效果图-2.PNG](http:https://img.haomeiwen.com/i1914621/315258ac335f087d.PNG?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

我们发现,尽管layout没有做任何布局,但是collectionView任然可以显示,这就说明,UICollectionViewFlowLayout已经为我们做好了一个布局,就是我们现在看到的流水布局,所以,对于继承于UICollectionViewFlowLayout的 类,如果要改变cell的布局,只需要获取系统默认为cell写好的布局,然后再此基础上进行修改就可以了。那么怎样获取UICollectionViewFlowLayout为我们写好的布局呢,使用父类调用-(NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:。

废话不多说,直接上代码:

/**
*prepareLayout会频繁调用,所以只做一些简单的初始化操作
*/

  • (void)prepareLayout{
    [super prepareLayout];
    // 水平滚动
    self.scrollDirection = UICollectionViewScrollDirectionHorizontal;
    //设置cell大小(可放到外面的控制器中)
    self.itemSize = CGSizeMake(200, 200);
    //设置内边距
    CGFloat inset = (self.collectionView.frame.size.width - self.itemSize.width) * 0.5;
    self.sectionInset = UIEdgeInsetsMake(0, inset, 0, inset);

}
/**
*设置为YES,collectionView的显示范围发生变化时,就要刷新布局
*/
-(BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds{

return YES;

}
/**
*返回所有cell的布局属性
*/
-(NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect{
//获取UICollectionViweFlowLayout已经做好的布局
NSArray attrbutesArray = [super layoutAttributesForElementsInRect:rect];
//计算collectionView可视范围的中心点所对应的collectionView的x值
CGFloat centerX = self.collectionView.contentOffset.x + self.collectionView.frame.size.width * 0.5;
//以每个cell中心点到centerX的距离为参考,对cell进行缩放
for (UICollectionViewLayoutAttributes attributes in attrbutesArray) {
//cell的中心点到centerX的距离
CGFloat distance = ABS(attributes.center.x - centerX);
//根据distance计算cell的缩放比例
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;
    }
    /
  • collectionView停止滑动时调用,可以手动设置collectionView的偏移量
  • proposedContentOffset collectionView原本的偏移量
    */
  • (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);

    //获得所有cell的布局属性
    NSArray *attributesArr = [super layoutAttributesForElementsInRect:endRect];

    //计算collectionView最中心点的x值
    CGFloat centerX = proposedContentOffset.x + self.collectionView.frame.size.width * 0.5;
    //循环所有的布局属性,得到距离中心点最近的cell到中心点的距离
    CGFloat minDelta = MAXFLOAT;
    for (UICollectionViewLayoutAttributes *attr in attributesArr) {
    if(ABS(minDelta) > ABS(attr.center.x - centerX)) {
    minDelta = attr.center.x - centerX;
    }
    }
    //原来偏移量的x+距离中心点最近的cell到中心点的距离 将其设置为偏移量,该cell就会到中心点
    proposedContentOffset.x += minDelta;
    return proposedContentOffset;
    }

效果图:

![效果图-3.gif](http:https://img.haomeiwen.com/i1914621/bc10257d76409d4d.gif?imageMogr2/auto-orient/strip)

###总结:
基本上到这里,UICollectionVew的使用就结束了,如何能够将UICollectionVew使用的更好,关键就在于怎样更好的运用UICollectionViewLayout和UICollectionViewFlowLayout,这才是UICollectionVew的精髓所在。

有兴趣的同学,可以移步到GitHub中下载[Demo](https://github.com/muzesunshine/UICollectionViewDemo),里面我为大家做了非常详细的注释。

相关文章

网友评论

本文标题:UICollectionView详解(二)—— 自定义UICol

本文链接:https://www.haomeiwen.com/subject/uwgshxtx.html