iOS CollectionView FlowLayout與風火輪佈局 (二)

weixin_33912246發表於2016-03-15

UICollectionView自定義佈局實現風火輪(Ray wenderlich上的教程

具體過程請看 Ray Wenderlich上的教程,網上也能找到其他開發者翻譯。這裡只記錄幾個我覺得比較重要的

對於自定義佈局,最主要的有三個函式:

223244-764ad08e470eecf5.png
自定義佈局關鍵過程.png
- (void)prepareLayout

用於排列cell前,計算各個cell位置資訊,避免將cell加到 collectionview 上時臨時計算:

  • 在collectionview第一次展現內容時呼叫
  • 佈局更新時,呼叫此函式來準備好下一次的佈局
- (CGSize)collectionViewContentSize

collectionView可滑動的區間

- (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect

返回一個可視rect範圍內被顯示cell的UICollectionViewLayoutAttributes

UICollectionViewLayoutAttributes類管理了佈局相關的屬性,如cell的frametransformboundszIndexcenter等。它們的定義與用法與UIView中各自屬性一致。

環形佈局

環形佈局最重要的一點就是,讓所有cell圍繞一個點轉起來。

關於view的旋轉,可以設定transform,讓view旋轉一定的角度。但是這樣,所有的view都是繞其center旋轉,不能達到我們的效果。

我們也可以設定anchorPoint,因為所有關於view的幾何運算都與其有關。關於anchorPoint,看這篇文章

關於anchorPoint DEMO

223244-bacff58092c1eef7.png
anchorPoint Demo.png
import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var anchorPointView: UIView!
    var viewArray : [UIView] = []
    var isMoved = true
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        
        let tapGesture = UITapGestureRecognizer(target: self, action: "tap")
        self.view.addGestureRecognizer(tapGesture)
    }
    
    override func viewDidAppear(animated: Bool) {
        super.viewDidAppear(animated)
        for i in (1...5) {
            let eView = UIView(frame: self.anchorPointView.frame)
            eView.backgroundColor = UIColor.blueColor()
            viewArray.append(eView)
            self.view.addSubview(eView)
        }
 
    }
    
    func tap() {
            //修改anchorPoint
            self.anchorPointView.layer.anchorPoint = CGPointMake(0.5, 0.5 + 3.0)
            //旋轉
            anchorPointView.transform = CGAffineTransformMakeRotation(CGFloat(M_PI / 10) * 0)
            //修正view的位置
            //因為修改了anchorPoint,而position不變,從而導致view上移300個點(anchorPointView 寬高都為100)
            //300根據 (高100)*(anchorPoint在y上增加值3)
            var center = self.anchorPointView.center
            center.y += 300
            self.anchorPointView.center = center
            var i = 1.0
            for view in viewArray {
                view.layer.anchorPoint = CGPointMake(0.5, 0.5 + 3.0)
                view.transform = CGAffineTransformMakeRotation(CGFloat(M_PI / 10 * i))
                i += 1.0 * 1
                var center = view.center
                center.y += 300
                view.center = center
            }
            
    }
}

自定義UICollectionViewLayoutAttributes

根據上面環形佈局,想要讓cell環形排列,必須知道cell的anchorPoint與 旋轉角度angle。

旋轉角度angle對應 UICollectionViewLayoutAttributes 的 transform,而anchorPoint作為一個CALayer的屬性,在UICollectionViewLayoutAttributes沒有對應的屬性,但這裡需要讓cell知道自己的anchorPoint,才能讓其正確旋轉。這裡就需要自定義UICollectionViewLayoutAttributes。

使用自定義子類,與使用UICollectionViewLayoutAttributes相比,你需要增加一些程式碼。

  1. 在自定義佈局的相關實現中:
  • (Class)layoutAttributesClass {
    //返回自定義UICollectionViewLayoutAttributes子類
    return [CircularCollectionViewLayoutAttributes class];
    }
這樣每當需要生成一個新的LayoutAttributes object時就會呼叫此函式

2.  在需要顯示的 UICollectionViewCell的實現中
  • (void)applyLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes {

    [super applyLayoutAttributes:layoutAttributes];
    if ([layoutAttributes isKindOfClass:[CircularCollectionViewLayoutAttributes class]]) {
    CircularCollectionViewLayoutAttributes *attributes = (CircularCollectionViewLayoutAttributes *)layoutAttributes;

    CGPoint centerPoint = self.center;
    
    self.layer.anchorPoint = attributes.anchorPoint;
    
    centerPoint.y += self.selected? (attributes.anchorPoint.y - 0.5) * CGRectGetHeight(self.bounds) - 50: (attributes.anchorPoint.y - 0.5) * CGRectGetHeight(self.bounds);
    self.center = centerPoint;
    

    }
    }

這樣新增加的UICollectionViewLayoutAttributes屬性,才有機會作用在cell上

###佈局核心過程程式碼

  • (void)prepareLayout {

    [super prepareLayout];

    CGFloat centerX = self.collectionView.contentOffset.x + CGRectGetWidth(self.collectionView.bounds) / 2.0;
    CGFloat anchorPointY = ((_itemSize.height / 2) + _radius ) / _itemSize.height;

    int startIndex = 0;
    int endIndex = (int)[self itemCount] - 1;
    // CGFloat theta = atan2(CGRectGetWidth(self.collectionView.bounds) / 2.0,
    // _radius + (_itemSize.height / 2.0) - CGRectGetHeight(self.collectionView.bounds) / 2.0);
    // if ([self angle] < -theta) {
    //
    // startIndex = (int)(floor( (-[self angle] - theta) / [self anglePerItem]));
    //
    // }
    //
    // endIndex = MIN(endIndex, (int)ceil((-[self angle] + theta) / [self anglePerItem]) );
    //
    // if (endIndex < startIndex) {
    //
    // startIndex = 0;
    // endIndex = 0;
    //
    // }

    for (int i = startIndex; i <= endIndex; i++) {

      CircularCollectionViewLayoutAttributes *attribute = [CircularCollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:[NSIndexPath indexPathForItem:i inSection:0]];
      attribute.size = _itemSize;
      attribute.center = CGPointMake(centerX, CGRectGetMidY(self.collectionView.bounds));
      attribute.anchorPoint = CGPointMake(0.5, anchorPointY);
      attribute.angle = [self angle] + [self anglePerItem]*i;
      [_layoutAttributes addObject:attribute];
    

    }

}

  • (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect {
    //返回prepareLayout計算好的佈局
    return _layoutAttributes;

}

  • (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {

//
return _layoutAttributes[indexPath.row];

}

  • (CGSize)collectionViewContentSize {

    return CGSizeMake([self.collectionView numberOfItemsInSection:0] * _itemSize.width, CGRectGetHeight(self.collectionView.bounds));
    }

  • (Class)layoutAttributesClass {

    return [CircularCollectionViewLayoutAttributes class];
    }

  • (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds {
    //每次滑動,都觸發新的佈局
    return true;
    }

相關文章