CollectionView自定义布局

一.CollectionViewFlawyout介绍和相关属性

该类是继承CollectionviewFlowyout
 
 属性介绍
 UICollectionViewLayoutAttributes对象管理着一个Collection View中给定的一个Item的布局有关的属性。当被CollectionView要求时布局对象创建这个类的实例。
 
 @property (nonatomic) CGRect frame; item的位置
 
 @property (nonatomic) CGPoint center; item的中心点,这个点时给定的
 
 @property (nonatomic) CGSize size; item的大小
 
 
 @property (nonatomic) UIEdgeInsets sectionInset itme的偏移量
 
 
 @property (nonatomic) CATransform3D transform3D;item的放射变化 使用你指定的放射变换赋值给这个属性替换
 
 @property (nonatomic) CGRect bounds NS_AVAILABLE_IOS(7_0);
 
 @property (nonatomic) CGAffineTransform transform NS_AVAILABLE_IOS(7_0);item在平面上的变化
 
 @property (nonatomic) CGFloat alpha;item透明度
 
 @property (nonatomic) NSInteger zIndex; // default is 0 item指定在Z轴上的位置 这个属性被用来确定在布局时Item的前后顺序。大的
 zIndex
 值的Item会被显示在小的
 zIndex
 值的Item上面。这个属性使用相同的值的Item的顺序是不确定的。
   这个属性的值默认为
 0
 @property (nonatomic, getter=isHidden) BOOL hidden; // As an optimization, UICollectionView might not create a view for items whose hidden attribute is YES
 
 
 
 @property (nonatomic, strong) NSIndexPath *indexPath; Collection View
 中Item的索引值。
   索引包含了一个Section的索引和一个Item在这个Section中的索引。这两个值标示在
 Collection View
 唯一的对应的Item的位置。
 
 
 @property (nonatomic, readonly) UICollectionElementCategory representedElementCategory;
 Item的类型。
   你可以使用这个属性的值来区分这个布局属性是用于一个Cell还是Supplementary View还是Decoration View。
 
 
 
 @property (nonatomic, readonly, nullable) NSString *representedElementKind;
 
 
 
 
 CollectionviewFlowyout有三种需要布局的视图元素cells,supplementaryViews(组头组委),DecorationViews(装饰视图)
 需要重写的方法
 该方法将通知布局对象更新当前的布局。布局更新发生在 collection view 第一次展示它的内容的时候,以及由于view的改变导致布局 invalidated 的时候。在布局更新期间, collection view都会首先调用该方法,允许布局对象对此次的更新做一些准备操作。
 默认情况下,该方法不会做任何操作。子类可以重载该方法,在方法内部做一些和布局相关的数据创建或计算操作。
1 - (void)prepareLayout
 
 
 
 返回collectionView内容区的宽和高,返回了所有内容的宽高包括未显示在屏幕上l的cell
 -(CGSize)collectionViewContentSize
 
 
 返回UICollectionViewLayoutAttributes类型的数组UICollectionViewLayoutAttributes该对象中包含cell和view的布局信息,返回该区域内所有元素的布局信息,包括cell,头部时尾部视图还有装饰视图
 返回指定区域中cell和view的属性
2 - (nullable NSArray<__kindof UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect;
 
 这下面三个方法分别是cell,追加视图和装饰视图的布局方法
 
 返回指定的indePath位置的cell布局属性,该方法只能返回cell的布局信息,
 3- (nullable UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath;
 
 
 
 //组头和组委布局信息,kind是注册组头和组尾时提供
 - (nullable UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryViewOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath;
 
 - (nullable UICollectionViewLayoutAttributes *)layoutAttributesForDecorationViewOfKind:(NSString*)elementKind atIndexPath:(NSIndexPath *)indexPath;
 
 
 
 该方法是决定需要跟新布局,如果collectionview需要更新布局返回yes,否则返回no,默认返回no,
 基于是否collection view的bounds的改变会引发cell和view布局的改变,给出正确的返回值。
 如果collection view的bounds改变,该方法返回YES,collection view通过调用
 invalidateLayoutWithContext方法使原来的layout失效
 4.-(BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
 
 
 //插入
 在一个 item被插入到collection view 的时候,返回开始的布局信息。这个方法在 prepareForCollectionViewUpdates:之后和finalizeCollectionViewUpdates 之前调用。collection view将会使用该布局信息作为动画的起点(结束点是该item在collection view 的最新的位置)。如果返回为nil,布局对象将用item的最终的attributes 作为动画的起点和终点。
 - (UICollectionViewLayoutAttributes *)initialLayoutAttributesForAppearingItemAtIndexPath:(NSIndexPath *)itemIndexPath
 
 
 
返回值是item即将从collection view移除时候的布局信息,对即将删除的item来讲,该方法在 prepareForCollectionViewUpdates: 之后和finalizeCollectionViewUpdates 之前调用。在该方法中返回的布局信息描包含 item的状态信息和位置信息。 collection view将会把该信息作为动画的终点(起点是item当前的位置)。如果返回为nil的话,布局对象将会把当前的attribute,作为动画的起点和终点。
 - (UICollectionViewLayoutAttributes *)finalLayoutAttributesForDisappearingItemAtIndexPath:(NSIndexPath *)itemIndexPath
 
 除了这些方法之外,你也可以重载- (void)prepareForCollectionViewUpdates:(NSArray<UICollectionViewUpdateItem *> *)updateItems;做一些和布局相关的准备工作。也可以重载- (void)finalizeCollectionViewUpdates;通过该方法添加一些动画到block,或者做一些和最终布局相关的工作
 
 
 该方法将通知布局对象更新当前的布局。布局更新发生在 collection view 第一次展示它的内容的时候,以及由于view的改变导致布局 invalidated 的时候。在布局更新期间, collection view都会首先调用该方法,允许布局对象对此次的更新做一些准备操作。
 默认情况下,该方法不会做任何操作。子类可以重载该方法,在方法内部做一些和布局相关的数据创建或计算操作。
 - (void)prepareLayout
 
 
当item在手势交互下移动时,通过该方法返回这个item布局的attributes 。默认实现是,复制已存在的attributes,改变attributes两个值,一个是中心点center;另一个是z轴的坐标值,设置成最大值。所以该item在collection view的最上层。子类重载该方法,可以按照自己的需求更改attributes,首先需要调用super类获取attributes,然后自定义返回的数据结构。
 - (UICollectionViewLayoutAttributes *)layoutAttributesForInteractivelyMovingItemAtIndexPath:(NSIndexPath *)indexPath withTargetPosition:(CGPoint)position
 
 在进行动画式布局的时候,该方法返回内容区的偏移量。在布局更新或者布局转场的时候,collection view 调用该方法改变内容区的偏移量,该偏移量作为动画的结束点。如果动画或者转场造成item位置的改变并不是以最优的方式进行,可以重载该方法进行优化。 collection view在调用prepareLayout 和 collectionViewContentSize 之后调用该方法
 - (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset
 
 
 
 
 该方法返回值为滑动停止的点。如果你希望内容区快速滑动到指定的区域,可以重载该方法。比如,你可以通过该方法让滑动停止在两个item中间的区域,而不是某个item的中间。
 - (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity
 
 
 根据item在collection view中的位置获取该item的index path。第一个参数该item原来的index path,第二个参数是item在collection view中的位置。在item移动的过程中,该方法将collection view中的location映射成相应 index paths。该方法的默认是现实,查找指定位置的已经存在的cell,返回该cell的 index path 。如果在相同的位置有多个cell,该方法默认返回最上层的cell。
 
 你可以通过重载该方法来改变 index path的决定方式。比如,你可以返回z坐标轴最底层cell的index path.当你重载该方法的时候,没有必要去调用super类该方法。
 - (NSIndexPath *)targetIndexPathForInteractivelyMovingItem:(NSIndexPath *)previousIndexPath withPosition:(CGPoint)position
 
 使布局失效:
 
 - (void)invalidateLayout

二自定义布局必须实现的几个方法

//设置layout的结构和初始化需要的参数
-(void)prepareLayout
/允许每次重新布局
-(BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
/布局cell属性
-(UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
-(CGSize)collectionViewContentSize

三.线性布局

//设置layout的结构和初始化需要的参数
-(void)prepareLayout{
    //设置横向移动
    self.scrollDirection = UICollectionViewScrollDirectionHorizontal;
    
    
  //屏幕宽度-cell宽度乘以0.5得到屏幕一半的宽度
  CGFloat insert = (self.collectionView.frame.size.width - self.itemSize.width)*0.5;
    //item的宽高
    self.itemSize = CGSizeMake(150, 150);
    
  
    //设置cell的边距,上左下右,左和右
    self.sectionInset = UIEdgeInsetsMake(0, insert, 0, insert);
    
}

//rect初始的layout的外观将有该方法返回的collectionviewLayoutAttributesl来决定
-(nullable NSArray<__kindof UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect{
   
    // 获得super已经计算好的布局属性,返回的素组包括所有cell的布局属性
    NSArray *array = [super layoutAttributesForElementsInRect:rect];
    // 计算collectionView最中心点的x值
    CGFloat centerX = self.collectionView.contentOffset.x+self.collectionView.frame.size.width*0.5;
    NSLog(@"centerX---%f",centerX);
    // 遍历所有cell的属性,在原有布局属性的基础上,进行微调
    for (UICollectionViewLayoutAttributes *attrs in array){
        // cell的中心点x 和 collectionView最中心点的x值 的间距
        CGFloat delta = ABS(attrs.center.x - centerX);
        NSLog(@"cell和collectionview的中心点的间距delta---%f",attrs.center.x);
        // 根据间距值 计算 cell的缩放比例
        CGFloat scale = 1 - delta/self.collectionView.frame.size.width;
        // 设置缩放比例
        attrs.transform = CGAffineTransformMakeScale(scale, scale);
    }
    return array;
}


/**
 * 这个方法的返回值,就决定了collectionView停止滚动时的偏移量
该方法返回值为滑动停止的点。如果你希望内容区快速滑动到指定的区域,可以重载该方法。比如,你可以通过该方法让滑动停止在两个item中间的区域,而不是某个item的中间。
 */
- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity
{
    // 计算出最终显示的矩形框
    CGRect rect;
    rect.origin.y = 0;
    rect.origin.x = proposedContentOffset.x;
    rect.size = self.collectionView.frame.size;
    
    // 计算collectionView最中心点的x值
    CGFloat centerX = proposedContentOffset.x + self.collectionView.frame.size.width * 0.5;
    // 获得super已经计算好的布局属性
    NSArray *array = [super layoutAttributesForElementsInRect:rect];
    
    // 存放最小的间距值
    CGFloat minDelta = MAXFLOAT;
    
    for (UICollectionViewLayoutAttributes *attrs in array){
        if (ABS(minDelta) > ABS(attrs.center.x-centerX)) {
            minDelta = attrs.center.x - centerX;
        }
    }
    // 修改原有的偏移量
    //    proposedContentOffset.x += minDelta;
    return CGPointMake(proposedContentOffset.x + minDelta, proposedContentOffset.y);
}
-(BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds{
  return  YES;
}

四不规则瀑布流

.h文件
@class WaterfallLayout;

@protocol WaterFallLayoutDelegate <NSObject>

-(CGFloat)waterFallLayout:(WaterfallLayout *)waterfallLayout heightForWidth:(CGFloat)width anIndexPath:(NSIndexPath *)indexPath;

@end

@interface WaterfallLayout : UICollectionViewFlowLayout
@property(nonatomic,assign)CGFloat collumnMargin;
@property(nonatomic,assign)CGFloat rowMargin;
@property(nonatomic,assign)UIEdgeInsets LsectionInset;
@property(nonatomic,assign)NSInteger columnCount;
@property(nonatomic,weak)id<WaterFallLayoutDelegate>delegate;


.m文件
@interface WaterfallLayout()
@property(nonatomic,strong)NSMutableArray *attrsArray;
@property(nonatomic,strong)NSMutableDictionary *maxYDic;
@end


@implementation WaterfallLayout
-(NSMutableDictionary *)maxYDic{
    
    if (!_maxYDic) {
        _maxYDic = [[NSMutableDictionary alloc]init];
    }
    return _maxYDic;
}
-(NSMutableArray *)attrsArray{
    
    if (!_attrsArray) {
        _attrsArray = [[NSMutableArray array]init];
    }
    return _attrsArray;
}

-(instancetype)init{
    
    if (self = [super init]) {
        //列间距
        self.collumnMargin = 10;
        //行间距
        self.rowMargin = 10;
        
        //偏移量
        self.LsectionInset = UIEdgeInsetsMake(10, 10, 10, 10);
        //每行3个
        self.columnCount  = 3;
    }
    return self;
}
-(void)prepareLayout{
    [super prepareLayout];
    
    for (NSInteger i=0; i<self.columnCount; i++) {
       //遍历每一行cell,获取到每一行cell的上边距
        NSString *colunm = [NSString stringWithFormat:@"%ld",i];
        //存入字典每高都是s10
        self.maxYDic[colunm] = @(self.LsectionInset.top);
   
    }
   
    
    
    [self.attrsArray removeAllObjects];
    
    //获取每组cell个数
    NSInteger count = [self.collectionView numberOfItemsInSection:0];
    //遍历
    for (NSInteger i=0; i<count; i++) {
        //获取attre对象
        UICollectionViewLayoutAttributes *attrs = [self layoutAttributesForItemAtIndexPath:[NSIndexPath indexPathForRow:i inSection:0]];
        [self.attrsArray addObject:attrs];
    }

}

//允许每次重新布局
-(BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds{
    
    return YES;
}
//布局cell属性
-(UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath{
    
    //架设最短的那一列为第0列
    __block NSString *minColunm = @"0";
    
    //遍历字典,找出最短的那一列
    [self.maxYDic enumerateKeysAndObjectsUsingBlock:^(NSString *column, NSNumber *maxY, BOOL *stop) {
       
        //字典里面column的序号由于只有每行3高cell所以字典里面只有012编号,
        //maxY:字典值 10,
        if ([maxY floatValue] < [self.maxYDic[minColunm] floatValue])
        {
           minColunm = column;
           
        }
    }];
    
    //item宽,屏幕宽度-(每列间距*每行个数一行3个那么间距只有2个间距)-cell左边距-右边距/每行cell个数
    CGFloat width = (self.collectionView.frame.size.width-self.collumnMargin*(self.columnCount-1)-self.LsectionInset.left-self.LsectionInset.right)/self.columnCount;
    //高
    CGFloat height = [self.delegate waterFallLayout:self heightForWidth:width anIndexPath:indexPath];
   
    
    
    CGFloat x = self.LsectionInset.left+(width+self.collumnMargin)*[minColunm floatValue];
    
    CGFloat y = [self.maxYDic[minColunm]floatValue]+self.rowMargin;
    
    self.maxYDic[minColunm] = @(y+height);
   
    
    UICollectionViewLayoutAttributes *attrs = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
    //每一个cell的位置和宽高
    attrs.frame = CGRectMake(x, y, width, height);
    
    
    
    
    return attrs;
    
    
}
//先走上面的cell布局再走这理的cell宽高
-(CGSize)collectionViewContentSize{
   
    //假设最长的那一列为第0列
    __block NSString *maxcolumn = @"0";
    //遍历字典,找出最长的那一列
    [self.maxYDic enumerateKeysAndObjectsUsingBlock:^(NSString *column, NSNumber *maxY, BOOL *stop) {
        // NSLog(@"maxY%f,%f",[maxY floatValue],[column floatValue]);
        if ([maxY floatValue] > [self.maxYDic[maxcolumn] floatValue])
        {
            maxcolumn = column;
        }
    }];
    
    return CGSizeMake(0, [self.maxYDic[maxcolumn]floatValue]+self.LsectionInset.bottom);
    
}

- (nullable NSArray<__kindof UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect{
    
    return self.attrsArray;
}

使用
//waterfallLayout代理,返回宽和高
-(CGFloat)waterFallLayout:(WaterfallLayout *)waterfallLayout heightForWidth:(CGFloat)width anIndexPath:(NSIndexPath *)indexPath{
    
  
    
    return 100/[[self rateForWidthDividedHeight][indexPath.row]floatValue];
    
}
//瀑布流图片真实宽高比
- (NSArray *)rateForWidthDividedHeight{
    
    NSString *path = [[NSBundle mainBundle] pathForResource:@"1" ofType:@".plist"];
    NSArray *images = [NSArray arrayWithContentsOfFile:path];
    __block NSMutableArray *rates = [NSMutableArray array];
    [images enumerateObjectsUsingBlock:^(NSDictionary *imageDic, NSUInteger idx, BOOL * _Nonnull stop) {
        CGFloat w = [imageDic[@"w"] floatValue];
        CGFloat h = [imageDic[@"h"] floatValue];
        //宽高比
        [rates addObject:@(w/h)];
    }];
    return rates;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值