这是UICollectionView自定义布局的第三篇,实现另一种视差效果,要实现的效果如下图所示。你也可以查看这篇文章



1. 对Cell进行Transform变换

首先对ItermCell进行Transform变换。重写layoutAttributesForElementsInRect方法,遍历所有的布局属性,将cell按逆时针旋转14°,代码如下

- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect{
    /*获取当前可视区域内的布局属性*/
    NSArray *original = [super layoutAttributesForElementsInRect:rect];
    NSArray *layoutArray =  [[NSArray alloc]initWithArray:original copyItems:YES];
    
    CGFloat angle = M_PI *(-14/180.0);
    for (UICollectionViewLayoutAttributes *attributes in layoutArray) {
        /*cell按逆时针旋转14°*/
        attributes.transform = CGAffineTransformMakeRotation(angle);
    }
    return layoutArray;
}
复制代码

注意布局属性数组layoutArray一定要进行copy,否则会报下面这个警告:

This is likely occurring because the flow layout subclass ParallaxEffectlayout is modifying attributes returned by UICollectionViewFlowLayout without copying them
复制代码

运行结果如下:


从运行结果我们发现几个问题:

  1. 第一个itermCell和最后一个itermCell超出了屏幕范围。
  2. itermCell左右两侧有空白区域,不美观。
  3. 背景图片也跟随Cell旋转了14°,我们希望背景图不进行旋转。

2. 解决Transform后出现的问题

1. 第一个问题

可以通过设置CollectionViewcontentInset来调整。

_myCollection.contentInset = UIEdgeInsetsMake(50, 0, 50, 0);
复制代码
2. 第二个问题

可以在cell上添加一个宽度大于cell宽度的ContainerView,作为图片的父视图。



[self.contentView addSubview:self.containerview];
    [self.containerview mas_makeConstraints:^(MASConstraintMaker *make) {
        make.top.bottom.equalTo(self.contentView);
        make.left.equalTo(self.contentView).offset(-40);
        make.right.equalTo(self.contentView).offset(40);
    }];

    [self.containerview addSubview:self.backImageView];
    [self.backImageView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.top.left.right.bottom.equalTo(self.containerview);
    }];
复制代码

调整后的运行结果如下:



3. 第三个问题不让图片进行旋转

只需要将图片反方向旋转14°,就可以抵消ItermCell的旋转影响。图片旋转后会出现锯齿,可以设置layer.allowsEdgeAntialiasing = YES;来改变,参考图片变形的抗锯齿处理方法

- (void)applyLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes{
    [super applyLayoutAttributes:layoutAttributes];
    
    /*让imageView不跟随cell进行旋转*/
    CGFloat angle = M_PI *(14 / 180.0);
    self.backImageView.transform = CGAffineTransformMakeRotation(angle);
}
复制代码



3. 添加视差效果

我们来添加一个随列表滚动的一个视差效果,原理也很简单,就是 cell 向一个方向滑动时,相应的imageView向相反的方向去运动。

- (void)parallaxOffsetForCollectionBounds:(CGRect)collectionBounds{
    //collectionView和cell的中心点
    CGRect bounds = collectionBounds;
    CGPoint boundsCenter = CGPointMake(CGRectGetMidX(bounds), CGRectGetMidY(bounds));
    CGPoint cellCenter = self.center;

    //找出每个cell相对于 collectionView 中心点的偏移量
    CGPoint offsetFromCenter = CGPointMake(boundsCenter.x - cellCenter.x, boundsCenter.y - cellCenter.y);

    //cell 的最大偏移量
    CGSize cellSize = self.bounds.size;

  
    CGFloat maxVerticalOffsetWhereCellIsStillVisible = (bounds.size.height / 2) + (cellSize.height / 2);
    CGFloat scaleFactor = 40.0 / maxVerticalOffsetWhereCellIsStillVisible;

    CGPoint parallaxOffset = CGPointMake(0.0, offsetFromCenter.y * scaleFactor);

    [self.backImageView mas_updateConstraints:^(MASConstraintMaker *make) {
        make.centerY.equalTo(self.containerview.mas_centerY).offset(parallaxOffset.y);
    }];
}
复制代码

offsetFromCenter.y 表示偏离中心点的距离。这就意味 collectionView 滚动一段距离,离中心点越远的 cell 对应的 parallaxOffset 的值就越大,离中心点越近的 cell 对应的 parallaxOffset 值就越小,这也符合我们的视觉习惯,目光平视中心点,滚动起来两边的视觉差要大一些。 计算偏移量后更新Y轴的约束:

[self.backImageView mas_updateConstraints:^(MASConstraintMaker *make) {
        make.centerY.equalTo(self.containerview.mas_centerY).offset(parallaxOffset.y);
    }];
复制代码

在CollectionView中调用scrollViewDidScroll方法,监听滑动的时候实时更新约束。

- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
    NSArray *cellArray = [self.myCollection visibleCells];
    [cellArray enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        NormalCollectionViewCell *cell = obj;
        [cell parallaxOffsetForCollectionBounds:self.myCollection.bounds];
    }];
}
复制代码

到此,视差效果已全部完成,文中demo可以到GitHub下载。