UICollectionView 的研究之二 :自定义 UICollectionViewFlowLayout

UICollectionView 实现各式复杂布局核心在于 UICollectionViewLayout,需要我们去自定义实现。

通过各种layout 的自定义实现,以及它们之间的切换。可以实现一些酷炫的布局,例如

(图片选自:http://www.cnblogs.com/markstray/p/5822262.html)

Cover Flow 布局

UICollectionView 的研究之二 :自定义 UICollectionViewFlowLayout_第1张图片

堆叠布局

UICollectionView 的研究之二 :自定义 UICollectionViewFlowLayout_第2张图片

圆形布局

UICollectionView 的研究之二 :自定义 UICollectionViewFlowLayout_第3张图片

关于需要重写方法的描述

自定义布局需要重写以下四个方法

(文字描述取自: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 布局的实现为例子进行阐述

UICollectionView 的研究之二 :自定义 UICollectionViewFlowLayout_第4张图片

进行初始化

- (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 :位于屏幕水平方向的中心不变,大小不变。


UICollectionView 的研究之二 :自定义 UICollectionViewFlowLayout_第5张图片

那么,以第一个item 进行分析,self.collectionView.contentOffset.x 与 self.collectionView.contentOffset.的差值与屏幕中心 x 相减的绝对值为item 中心与屏幕中心之间的距离

UICollectionView 的研究之二 :自定义 UICollectionViewFlowLayout_第6张图片    UICollectionView 的研究之二 :自定义 UICollectionViewFlowLayout_第7张图片

              屏幕中心与item 中心相距为0                                 屏幕中心与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


你可能感兴趣的:(Object-C学习笔记)