學習自定義流水布局

在網(wǎng)上看到別人用collectionView寫的圖片瀏覽器,可以縮放和定位功能,因此深入學習了一下。

collectionView的系統(tǒng)的流水布局已經(jīng)可以滿足日常需要,但是如果你需要給滾動的時候加上動畫,還是需要自定義流水布局。

如何自定義流水布局

1.需要重寫的方法

自定義流水布局只需要新建一個類,繼承自UICollectionViewFlowLayout,然后重寫它的系統(tǒng)方法。

和定制圖片瀏覽器效果有關的5個系統(tǒng)方法:

  1. - (void)prepareLayout; 這個方法是在collectionView第一次顯示或者刷新的時候會調(diào)用,默認這個方法是不做任何事情的,子類可以重寫它來進行數(shù)據(jù)結構的設置或者計算任何需要執(zhí)行的布局后的初始計算。

  2. - (CGSize)collectionViewContentSize;這個方法是用來計算collectionView的滾動范圍,可以重寫,也可以不重寫。

  3. - (nullable NSArray<__kindof UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect;這個方法的意思是你提供一個區(qū)域,然后返回這個區(qū)域包含的cell的屬性。

  4. - (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds;這個方法意思是在滾動時候是否可以重新布局,默認是NO,如果想要滾動時候產(chǎn)生縮放效果則必須設置為yes,這里invalidate意思是刷新。

  5. - (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity;這個方法是獲得最終滾動的位置,因為在滾動時候有一個速率,蘋果內(nèi)部根據(jù)速率來計算緩沖,因此你快速滑動,會向后緩沖一段距離。

2.開始實現(xiàn)滾動時候縮放效果

2.1 實現(xiàn)滾動時候可以設置布局

- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
{
    return YES;
}

2.2 在滾動的過程中對圖片進行縮放

離屏幕中心點越近的圖片越大,離中心點越遠的圖片越小,這邊只需要拿到具體的cell改變他的transform屬性就可以。具體如何實現(xiàn)呢,需要以下的思路:

  • 先確定一個rect來獲得這個rect的cell,rect的寬度和高度不用說肯定是collectionView的寬度和高度,但是坐標點是在變換的,因為collectionView會滾動,但是通過深入學習bounds我們可以知道滾動底層實現(xiàn)就是改變bounds,因此滾動時候設置的frame直接取collectionView的bounds就可以了
  • 拿到給定的范圍的cell,這時候我們需要計算cell的縮放的大小,越靠近屏幕中間的則越大,離的越遠則越小,為了設置動態(tài)縮放,我們需要計算cell中心點離屏幕中心點的位置,然后計算比例,在中心點就是正常尺寸,有偏移的就縮小一定程度。
計算距離.png

通過上面這個圖我們可以直觀的理解,屏幕顯示的范圍在不停的變化,但是每次變化基本上都會包含2個cell,通過計算cell離中心點的距離,具體的計算就是cell的中心點橫坐標減去collectionView的偏移的x,減去collectionView一半寬度就能夠得到偏移量(要取絕對值,因為可能是負的)。

  • 然后計算百分比,這里得到的偏移量是cell的中心點離collectionView中心點距離,因此與collectionView的寬度一半進行相除,如果是0,則意味著2個中心點重合,這時候的縮放比例應該是最大的,如果為1則是最小,是負相關,因此用1來減去得到正相關,這樣當在中心點位置就是1,距離最遠就是0.但我們也不希望離中心點位置遠的縮小為0,比中心點的小一部分尺寸就可以了,所以cellOffset / (self.collectionView.frame.size.width * 0.5) * 0.3這樣離中心點距離為半個collectionView寬度的cell尺寸縮小0.7
- (nullable NSArray<__kindof UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect
{
    // ----1.先獲取當前collectionView所在區(qū)域的cell
    NSArray *attrs = [super layoutAttributesForElementsInRect:self.collectionView.bounds];
    
    for (UICollectionViewLayoutAttributes *cellAttr in attrs) {
        
        // ----獲取偏移量(取絕對值)
        CGFloat cellOffset = fabs(cellAttr.center.x - self.collectionView.contentOffset.x - self.collectionView.frame.size.width * 0.5);
        
        // ----計算百分比
        CGFloat perCent = 1 - cellOffset / (self.collectionView.frame.size.width * 0.5) * 0.3;
        
        cellAttr.transform = CGAffineTransformMakeScale(perCent, perCent);
    }

2.3 設置滾動結束的停留位置

在滾動之后,最好的用戶體驗是自動停留在距離中心點最近的圖片顯示在中間,這樣就需要用到了- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity這個方法,我們來分析下如何處理:

  • 因為我們知道有個緩沖,所以手指滑動時候,collectionView的偏移量和最終停留位置的坐標點不一致,這個方法返回的就是最終停留的坐標點,因此可以先調(diào)用super的這個方法,獲得到最終停留點。
  • 根據(jù)最終的滾動停止的坐標,就可以得出最后滾動停止之后的區(qū)域,然后拿到該區(qū)域的cell這里要調(diào)用super的方法,因為我們已經(jīng)重寫了當前類的這個方法
  • 計算cell誰離中心點最近,然后把該cell設置為屏幕中心,因此要取出最小的的距離點。這里比較值的時候可能會有負的,因此要去絕對值
  • 然后用最終的偏移量加上這個距離中心點的距離,這樣可以設置距離最近的圖片顯示在中信。通過打印發(fā)現(xiàn)可能會出現(xiàn)-0這種情況,導致卡頓,需要做個判斷
- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity
{
    CGPoint targetP = [super targetContentOffsetForProposedContentOffset:proposedContentOffset withScrollingVelocity:velocity];
    
    CGFloat collectionW = self.collectionView.frame.size.width;
    
    CGFloat collectionH = self.collectionView.frame.size.height;
    
    CGRect frame    = CGRectMake(targetP.x, 0, collectionW, collectionH);
    
    NSArray *attrs  = [super layoutAttributesForElementsInRect:frame];
   
    CGFloat minOffSetX = MAXFLOAT;
    
    for (UICollectionViewLayoutAttributes *attr in attrs) {
        
        // ----這里要減去最終偏移量的x!!!
        CGFloat cellOffset = attr.center.x - targetP.x - collectionW * 0.5;
        
        if (fabs(cellOffset) < fabs(minOffSetX)) {
            minOffSetX = cellOffset;
        }
    }
    
    targetP.x += minOffSetX;
    
    // ----拖動時候可能會卡頓,通過打印發(fā)現(xiàn)有負0,這種情況,因此做個判斷
    if (targetP.x < 0) {
        targetP.x = 0;
    }
    
    return targetP;
}
最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內(nèi)容