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