from: http://www.tuicool.com/articles/vuyIriN
當我們使用系統自帶的UICollectionViewFlowLayout無法實現我們的布局時,我們就可以考慮自定義layout。
所以,了解並學習一下自定義Layout是很有必要。
其實可以分三個步驟:
- 覆寫prepareLayout方法,並在里面事先就計算好必要的布局信息並存儲起來。
- 基於prepareLayout方法中的布局信息,使用collectionViewContentSize方法返回UICollectionView的內容尺寸。
- 使用layoutAttributesForElementsInRect:方法返回指定區域cell、Supplementary View和Decoration View的布局屬性。
了解了自定義布局的三個主要步驟,我們來通過自定義布局的方式用UICollectionView實現grideView。當然,grideView使用 UICollectionViewFlowLayout
就可以輕易實現,這里我們只是學習了解一下自定義布局的過程,所以拿grideView這個經常用的來作為例子。
我們創建一個新的工程 BGCustomLayoutCollectionViewDemo 。然后創建一個UICollectionViewLayout的子類對象 BGGrideLayout
,它就是我們自定義layout對象。
在BGGrideLayout里面,我們首先覆寫prepareLayout方法。
prepareLayout是專門用來准備布局的,在 prepareLayout
方法里面我們可以事先就計算后面要用到的布局信息並存儲起來,防止后面方法多次計算,提高性能。例如,我們可以在此方法就計算好每個cell的屬性、整個CollectionView的內容尺寸等等。此方法在布局之前會調用一次,之后只有在調用 invalidateLayout
、 shouldInvalidateLayoutForBoundsChange:
返回 YES
和 UICollectionView刷新
的時候才會調用。
而在BGGrideLayout的prepareLayout方法中,我們有兩個目的:
一是獲取對應indexPath的 UICollectionViewLayoutAttributes
對象,並存儲到二維數組 layoutInfoArr
當中;
二是計算出內容尺寸並保存到全局變量 contentSize
當中。
代碼如下:
- (void)prepareLayout{ [super prepareLayout]; NSMutableArray *layoutInfoArr = [NSMutableArray array]; NSInteger maxNumberOfItems = 0; //獲取布局信息 NSInteger numberOfSections = [self.collectionView numberOfSections]; for (NSInteger section = 0; section < numberOfSections; section++){ NSInteger numberOfItems = [self.collectionView numberOfItemsInSection:section]; NSMutableArray *subArr = [NSMutableArray arrayWithCapacity:numberOfItems]; for (NSInteger item = 0; item < numberOfItems; item++){ NSIndexPath *indexPath = [NSIndexPath indexPathForItem:item inSection:section]; UICollectionViewLayoutAttributes *attributes = [self layoutAttributesForItemAtIndexPath:indexPath]; [subArr addObject:attributes]; } if(maxNumberOfItems < numberOfItems){ maxNumberOfItems = numberOfItems; } //添加到二維數組 [layoutInfoArr addObject:[subArr copy]]; } //存儲布局信息 self.layoutInfoArr = [layoutInfoArr copy]; //保存內容尺寸 self.contentSize = CGSizeMake(maxNumberOfItems*(self.itemSize.width+self.interitemSpacing)+self.interitemSpacing, numberOfSections*(self.itemSize.height+self.lineSpacing)+self.lineSpacing); }
在上面的代碼中,我們看到了 UICollectionViewLayoutAttributes
這個類,這個類其實專門用來存儲視圖的內容,例如frame、size、apha、hiden等等,layout最后會拿着這些frame設置給對應的視圖。 而上面代碼中,獲取 UICollectionViewLayoutAttributes
是通過 layoutAttributesForItemAtIndexPath:
方法
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath{ UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath]; //每一組cell為一行 attributes.frame = CGRectMake((self.itemSize.width+self.interitemSpacing)*indexPath.row+self.interitemSpacing, (self.itemSize.height+self.lineSpacing)*indexPath.section+self.lineSpacing, self.itemSize.width, self.itemSize.height); return attributes; }
在這個方法中, itemSize
是cell的大小, interitemSpacing
是cell與cell之間的間距, lineSpacing
是行距。
隨后,覆寫collectionViewContentSize
collectionViewContentSize返回內容尺寸給UICollectionView。注意這個方法返回的尺寸是給UICollectionView這個繼承於 UIScrollView
的視圖作為 contentSize
,不是UICollectionView的視圖尺寸。正是因為這一點,我們自定義layout如果想讓它只能橫向滑動,只需要將這個 size.height
設置成 collectionView.height
就行了。 這個方法會多次調用,所以最好是在prepareLayout里就計算好。 在BGGrideLayout類中,我們只需要返回前面計算好的內容尺寸就行了。
- (CGSize)collectionViewContentSize{ return self.contentSize; }
最后,覆寫layoutAttributesForElementsInRect:方法
此方法需要返回一組UICollectionViewLayoutAttributes類型對象。它代表着在這個指定的區域中,我們需要顯示 cell
、 Supplementary View
和 Decoration View
中哪些視圖,而這些視圖的屬性則保存UICollectionViewLayoutAttributes中。 此方法會多次調用,為了更好的性能,在這個方法當中,我們使用的UICollectionViewLayoutAttributes最好是在prepareLayout已經布局好的信息。
在BGGrideLayout中,我們遍歷二維數組,找出了與指定區域有交接的UICollectionViewLayoutAttributes對象放到一個數組中,然后返回。
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect{ NSMutableArray *layoutAttributesArr = [NSMutableArray array]; [self.layoutInfoArr enumerateObjectsUsingBlock:^(NSArray *array, NSUInteger i, BOOL * _Nonnull stop) { [array enumerateObjectsUsingBlock:^(UICollectionViewLayoutAttributes *obj, NSUInteger idx, BOOL * _Nonnull stop) { if(CGRectIntersectsRect(obj.frame, rect)) { [layoutAttributesArr addObject:obj]; } }]; }]; return layoutAttributesArr; }
到這里,我們的BGGrideLayout已經寫好了,使用部分的代碼,請直接查看 BGCustomLayoutCollectionViewDemo 中ViewController里面的代碼就行了。