概述
在上一篇博客中,简单了解了一下 UICollectionView的构成和使用,在这篇文章中我们来学习一下几种经典的布局,在这之前我们需要来了解一个比较重要的类
UICollectionViewLayoutAttributes
UICollectionViewLayoutAttributes是一个非常重要的类,由他提供给 cell各种属性,看一下其有哪些重要的属性:
@property (nonatomic) CGRect frame;
// 大小@property (nonatomic) CGPoint center;
// 中心位置@property (nonatomic) CGSize size;
// 形状@property (nonatomic) CATransform3D transform3D;
// 3D 动画@property (nonatomic) CGRect bounds NS_AVAILABLE_IOS(7_0);
//尺寸@property (nonatomic) CGAffineTransform transform NS_AVAILABLE_IOS(7_0);
// 形变@property (nonatomic) CGFloat alpha;
// 透明度@property (nonatomic, getter=isHidden) BOOL hidden; // As an optimization, UICollectionView might not create a view for items whose hidden attribute is YES
从上面可以看出,其实一个 cell 对应一个UICollectionViewLayoutAttributes对象,这个对象决定了cell 的 fram 等属性
线性布局
从图片来看,图片可以左右滑动,并且停留在屏幕最中间的 cell 会自动放大,这个简单的效果,让我们来看看是如何实现的
首先,我们来考虑用系统的流水布局来实现这个效果,随后发现系统并没有提供相关 API, 接下来需要我们自定义一个线性布局,这个布局继承自系统提供的流水布局
用来做布局的初始化操作
1 2 3 4 5 6 7 8 9 |
- (void)prepareLayout { [super prepareLayout]; // 水平滚动 self.scrollDirection = UICollectionViewScrollDirectionHorizontal; // 设置内边距 CGFloat inset = (self.collectionView.frame.size.width - self.itemSize.width) * 0.5; self.sectionInset = UIEdgeInsetsMake(0, inset, 0, inset); } |
返回一个数组,数组内存放着 rect 范围内所有元素的排布
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect { // 获得super已经计算好的布局属性 NSArray *array = [super layoutAttributesForElementsInRect:rect]; // 计算collectionView最中心点的x值 CGFloat centerX = self.collectionView.contentOffset.x + self.collectionView.frame.size.width * 0.5; // 在原有布局属性的基础上,进行微调 for (UICollectionViewLayoutAttributes *attrs in array) { // cell的中心点x 和 collectionView最中心点的x值 的间距 CGFloat delta = ABS(attrs.center.x - centerX); // 根据间距值 计算 cell的缩放比例 CGFloat scale = 1 - delta / self.collectionView.frame.size.width; // 设置缩放比例 attrs.transform = CGAffineTransformMakeScale(scale, scale); } return array; } |
返回值决定了 collectionVIew 停止滚动时的偏移量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity { // 计算出最终显示的矩形框 CGRect rect; rect.origin.y = 0; rect.origin.x = proposedContentOffset.x; rect.size = self.collectionView.frame.size; // 获得super已经计算好的布局属性 NSArray *array = [super layoutAttributesForElementsInRect:rect]; // 计算collectionView最中心点的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; } |
需要注意的是,当 collectionView的显示范围发生变化时,会调用此方法来决定是否重新刷新 UI
1 2 3 4 |
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds { return YES; } |
商品展示
商品展示效果可以上下滑动,图片交错排布,这个布局需要继承自UICollectionViewLayout(其实流水布局就继承自他),因为我们不需要流水布局为我们计算一些布局
懒加载一个数组,里面存放着布局属UICollectionViewLayoutAttributes
1 2 3 4 5 6 7 |
- (NSMutableArray *)attrsArray { if (!_attrsArray) { _attrsArray = [NSMutableArray array]; } return _attrsArray; } |
准备工作,计算好返回的属性
1 2 3 4 5 6 7 8 |
- (void)prepareLayout { [super prepareLayout]; [self.attrsArray removeAllObjects]; NSInteger count = [self.collectionView numberOfItemsInSection:0]; for (int i = 0; i |
显示范围内的属性
1 2 3 4 |
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect { return self.attrsArray; } |
collectionview 的内容大小
1 2 3 4 5 6 7 |
- (CGSize)collectionViewContentSize { int count = (int)[self.collectionView numberOfItemsInSection:0]; int rows = (count + 3 - 1) / 3; CGFloat rowH = self.collectionView.frame.size.width * 0.5; return CGSizeMake(0, rows * rowH); } |
圆形布局
这是一个圆形布局,自定义UICollectionViewLayout实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
/** 布局属性 */ @property (nonatomic, strong) NSMutableArray *attrsArray; @end @implementation XMGCircleLayout - (NSMutableArray *)attrsArray { if (!_attrsArray) { _attrsArray = [NSMutableArray array]; } return _attrsArray; } - (void)prepareLayout { [super prepareLayout]; [self.attrsArray removeAllObjects]; NSInteger count = [self.collectionView numberOfItemsInSection:0]; for (int i = 0; i |