使用UICollectionView自定義樣式01-水平線性布局

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)個喜歡,如有閑錢,歡迎打賞。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容