一.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;
}