UICollectionView 是 iOS6 推出的 API, 可以實(shí)現(xiàn)復(fù)雜的定制效果, 使用和 UITableView 類似, 這篇主要通過自定義 UICollectionViewFlowLayout 實(shí)現(xiàn)一個左右滑動, 支持放大的相冊效果
效果展示

003.png
思路
- 可以滾動, 必定繼承自 UIScrollView
- 數(shù)據(jù)量未知, 為了避免內(nèi)存過大導(dǎo)致 crash, 因此需要使用具有循環(huán)利用機(jī)制的控件, 否則還需要自寫一套循環(huán)利機(jī)制
- UITableView 也可實(shí)現(xiàn), 但是比較奇葩 (需要將整個 tableView 90 °, cell 內(nèi)容旋轉(zhuǎn) 90 °)
- 自定義 UICollectionViewLayout 完全可以滿足需求, 并實(shí)現(xiàn)一套完全可以復(fù)用的布局方案
- 可繼承自 UICollectionView 提供 的 UICollectionViewFlowLayout, 并在此基礎(chǔ)上做改動
API 簡介
UICollectionViewLayout 提供了以下 API 控制自定義布局的樣式, 實(shí)現(xiàn)定制效果
01 - layoutAttributesForElementsInRect:
- (nullable NSArray<__kindof UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect; // return an array layout attributes instances for all the views in the given rect
- 這個方法的返回值是一個數(shù)組(數(shù)組里面存放著rect范圍內(nèi)所有元素的布局屬性)
- 這個方法的返回值決定了rect范圍內(nèi)所有元素的排布(frame)
- 返回?cái)?shù)組內(nèi)存放的是 UICollectionViewLayoutAttributes 對象, 一個 cell 對應(yīng)一個 UICollectionViewLayoutAttributes 對象, 該對象決定了 cell 的 frame
02 - prepareLayout
- (void)prepareLayout;
- 用來做布局的初始化操作
- 不建議在 init 方法中進(jìn)行布局的初始化操作
- 一定要調(diào)用[super prepareLayout], 官方文檔中有明確注釋, Subclasses should always call super if they override.
03 - shouldInvalidateLayoutForBoundsChange:
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds; // return YES to cause the collection view to requery the layout for geometry information
如果返回YES,那么collectionView顯示的范圍發(fā)生改變時(shí),就會重新刷新布局
一旦重新刷新布局,就會按順序調(diào)用下面的方法:
- prepareLayout
- layoutAttributesForElementsInRect:
04 - targetContentOffsetForProposedContentOffset:
- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity; // return a point at which to rest after scrolling - for layouts that want snap-to-point scrolling behavior
這個方法的返回值,就決定了collectionView停止?jié)L動時(shí)的偏移量
參數(shù):
- proposedContentOffset:collectionView停止?jié)L動時(shí)最終的偏移
- velocity:滾動速率,通過這個參數(shù)可以了解滾動的方向
具體實(shí)現(xiàn)
01 - 重寫 layoutAttributesForElementsInRect: 方法]
- 調(diào)用 [super layoutAttributesForElementsInRect], 拿到父類算好的 cell 布局屬性(位置和尺寸)數(shù)組
- 遍歷布局屬性數(shù)組, 修改每個 cell 布局屬性的縮放比例 scale (原則應(yīng)該是在一定范圍內(nèi), cell 距離 collectionView 的 contentView 中心線越近, cell 顯示比例越大)
- 在父類算好的基礎(chǔ)上進(jìn)行修改 (transform), 修改完后, 返回這個數(shù)組即可
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
// 獲得super已經(jīng)計(jì)算好的布局屬性
NSArray *array = [super layoutAttributesForElementsInRect:rect];
// 計(jì)算collectionView最中心點(diǎn)的x值
CGFloat centerX = self.collectionView.contentOffset.x + self.collectionView.frame.size.width * 0.5;
// 在原有布局屬性的基礎(chǔ)上,進(jìn)行微調(diào)
for (UICollectionViewLayoutAttributes *attrs in array) {
// cell的中心點(diǎn)x 和 collectionView最中心點(diǎn)的x值 的間距
CGFloat delta = ABS(attrs.center.x - centerX);
// 根據(jù)間距值 計(jì)算 cell的縮放比例
CGFloat scale = 1 - delta / self.collectionView.frame.size.width;
// 設(shè)置縮放比例
attrs.transform = CGAffineTransformMakeScale(scale, scale);
}
return array;
}
02 - 重寫 shouldInvalidateLayoutForBoundsChange 方法, 返回 YES
意味著當(dāng)collectionView顯示的范圍發(fā)生改變時(shí),就會重新刷新布局, 一旦刷新布局, 就會按順序調(diào)用:
- (void)prepareLayout;
- (nullable NSArray<__kindof UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect; // return an array layout attributes instances for all the views in the given rect
03 - 重寫 targetContentOffsetForProposedContentOffset: 方法決定 collectionView 停止?jié)L動時(shí)的偏移量
- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity
{
// 計(jì)算出最終顯示的矩形框
CGRect rect;
rect.origin.y = 0;
rect.origin.x = proposedContentOffset.x;
rect.size = self.collectionView.frame.size;
// 獲得super已經(jīng)計(jì)算好的布局屬性
NSArray *array = [super layoutAttributesForElementsInRect:rect];
// 計(jì)算collectionView最中心點(diǎn)的x值
CGFloat centerX = proposedContentOffset.x + self.collectionView.frame.size.width * 0.5;
// 存放最小的間距值
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 proposedContentOffset;
}
```
#### 04 - (補(bǔ)充) 關(guān)于 scale - 縮放比例
計(jì)算出 cell 的中心線和 collectionView 的 contentView 的中心線的絕對距離 delta
- 計(jì)算collectionView最中心點(diǎn)的x值
```
CGFloat centerX = self.collectionView.contentOffset.x + self.collectionView.frame.size.width * 0.5;
```
- 計(jì)算cell的中心點(diǎn) x 和 collectionView 最中心點(diǎn)的 x 值的間距
CGFloat delta = ABS(attrs.center.x - centerX);
```
- 根據(jù) delta, 加一定系數(shù), 算出 scale
CGFloat scale = 1 - delta / self.collectionView.frame.size.width;
UICollectionView 的優(yōu)點(diǎn)
- 相對與功能簡單的 TableView, 能實(shí)現(xiàn)復(fù)雜的定制效果
- 內(nèi)部緩存機(jī)制, cell 復(fù)用
- TableView 類似的 dataSource, delegate 方法, 簡單易用的 API
總結(jié)
- UICollectionView 提供的自定義 layout 功能可以方便定制很多效果, 比如電商應(yīng)用常見的瀑布流, tagView 等等
- UITableView 是以行為單位, 功能有限, 而 UICollectionView 可以方便的通過自定 layout 實(shí)現(xiàn)各種不同效果
- UICollectionView 也可以通過自定 layout 實(shí)現(xiàn) UITableView
- UICollectionView 內(nèi)部的循環(huán)利用機(jī)制可以有效的節(jié)約內(nèi)存, 防止程序 crash
程序代碼地址
https://git.oschina.net/aLonelyRoot3/AYCustomLayout.git
UICollectionView 實(shí)現(xiàn)的很多其他流行效果會在以后貼出
歡迎關(guān)注作者, 點(diǎn)個喜歡,如有閑錢,歡迎打賞。