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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章