自定义 UICollectionViewFlowLayout
UICollectionView 实现各式复杂布局核心在于 UICollectionViewLayout,需要我们去自定义实现。
通过各种layout 的自定义实现,以及它们之间的切换。可以实现一些酷炫的布局,例如
(图片选自:http://www.cnblogs.com/markstray/p/5822262.html)
Cover Flow 布局
image.png
堆叠布局
image.png圆形布局
image.png关于需要重写方法的描述
自定义布局需要重写以下四个方法
(文字描述取自:https://www.cnblogs.com/wangliang2015/p/5388658.html 和 https://www.cnblogs.com/hissia/p/5723629.html):
-
作用:在这个方法中做一些初始化操作
-
注意:子类重写prepareLayout,一定要调用[super prepareLayout]
-
(void)prepareLayout;
-
作用:
-
这个方法的返回值是个数组
-
这个数组中存放的都是UICollectionViewLayoutAttributes对象
-
UICollectionViewLayoutAttributes对象决定了cell的排布方式(frame等)
-
(nullable NSArray<__kindof UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect;
-
作用:如果返回YES,那么collectionView显示的范围发生改变时,就会重新刷新布局
-
一旦重新刷新布局,就会按顺序调用下面的方法:
-
prepareLayout
-
layoutAttributesForElementsInRect:
-
(BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds;
-
作用:返回值决定了collectionView停止滚动时最终的偏移量(contentOffset)
-
参数:
-
proposedContentOffset:原本情况下,collectionView停止滚动时最终的偏移量
-
velocity:滚动速率,通过这个参数可以了解滚动的方向
-(CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity;
具体实现逻辑
以 Cover Flow 布局的实现为例子进行阐述
image.png进行初始化
- (instancetype)init {
if (self = [super init]) {
}
return self;
}
进行布局,并且 collectionView 显示 rect 改变是进行布局刷新
- (void)prepareLayout {
[super prepareLayout];
// 水平滚动
self.scrollDirection = UICollectionViewScrollDirectionHorizontal;
// 决定第一张图片所在的位置
CGFloat margin = (self.collectionView.frame.size.width - self.itemSize.width) / 2;
self.collectionView.contentInset = UIEdgeInsetsMake(0, margin, 0, margin);
}
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds {
return YES;
} // return YES to cause the collection view to requery the layout for geometry information
该方法是进行自定义的核心,在这里写各种算法以达成想要的效果
该例子中,我们要实现的效果是在滑动的过程中,item 逐渐接近中心,会逐步增大,直到显示最大值,之后不断远离中心点,item 逐步缩小。这一效果类似于三角函数中的正、余弦函数。初步锁定这两个函数,而要选择哪一个?
在滑动的过程中,我们可以得到
self.collectionView.contentOffset.x :这是内容最左侧相对于 collectionView 最左侧的偏移值
attributes.center.x :这是当前 item 的水平中心点,该 x 值是从内容的最左侧算起,直到当前 item 的水平中心点的,全部加起来就是该 item 的水平中心 x。
再者,无论是否滑动,都有一个固定值:
self.collectionView.center.x :位于屏幕水平方向的中心不变,大小不变。
image.png那么,以第一个item 进行分析,self.collectionView.contentOffset.x 与 self.collectionView.contentOffset.x 的差值与屏幕中心 x 相减的绝对值为item 中心与屏幕中心之间的距离
![image.png](https://img.haomeiwen.com/i5745019/b1a39adbc0592197.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
屏幕中心与item 中心相距为0
![image.png](https://img.haomeiwen.com/i5745019/65990e9b7b5fb352.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
屏幕中心与item 中心相距40
由此,我们选择余弦函数进行计算。
- (nullable NSArray<__kindof UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect {
// 闪屏现象解决参考 :https://blog.csdn.net/u013282507/article/details/53103816
//扩大控制范围,防止出现闪屏现象
rect.size.width = rect.size.width + KScreenWidth;
rect.origin.x = rect.origin.x - KScreenWidth/2;
// 让父类布局好样式
NSArray *arr = [[NSArray alloc] initWithArray:[super layoutAttributesForElementsInRect:rect] copyItems:YES];
for (UICollectionViewLayoutAttributes *attributes in arr) {
CGFloat scale;
// scale = 1.0;
// collectionView 的 centerX
CGFloat centerX = self.collectionView.center.x;
CGFloat step = ABS(centerX - (attributes.center.x - self.collectionView.contentOffset.x));
NSLog(@"step %@ : attX %@ - offset %@", @(step), @(attributes.center.x), @(self.collectionView.contentOffset.x));
scale = fabsf(cosf(step/centerX * M_PI/5));
attributes.transform = CGAffineTransformMakeScale(scale, scale);
}
return arr;
} // return an array layout attributes instances for all the views in the given rect
确保在滚动结束的时候的显示效果,此处确保某一个item 滚动结束时是居中显示的。
- (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 *arr = [super layoutAttributesForElementsInRect:rect];
// 计算 collectionView 最中心点的 x 值
CGFloat centerX = proposedContentOffset.x + self.collectionView.frame.size.width * 0.5;
CGFloat minDelta = MAXFLOAT;
for (UICollectionViewLayoutAttributes *attrs in arr) {
if (ABS(minDelta) > ABS(attrs.center.x - centerX)) {
minDelta = attrs.center.x - centerX;
}
}
proposedContentOffset.x += minDelta;
return proposedContentOffset;
} // return a point at which to rest after scrolling - for layouts that want snap-to-point scrolling behavior
代码地址:https://download.csdn.net/download/u013410274/10356732
所有的参考链接:
WWDC 2012 Session 笔记 -- 205 Introducing Collection Views
http://www.cnblogs.com/markstray/p/5822262.html
UICollectionViewLayout 继承 UICollectionViewFlowLayout 自定义布局
https://www.cnblogs.com/wangliang2015/p/5388658.html
自定义流水布局(UICollectionViewFlowLayout 的基本使用)
https://www.cnblogs.com/hissia/p/5723629.html
iOS 利用余弦函数实现卡片浏览工具
https://blog.csdn.net/u013282507/article/details/53103816
————————————————
版权声明:本文为CSDN博主「若歆」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/u013410274/article/details/79925531