UICollectionView自定义布局(二)

这是UICollectionView自定义布局的第二篇,实现类似UltravisualApp的视差效果,同样这篇文章的教程来自Ray家的Swift Expanding Cells in iOS Collection Views这篇文章。

UICollectionView自定义布局(二)

自定义布局

将该动画分解,首先实现如下图所示的效果。

UICollectionView自定义布局(二)

随着CollectionView的滑动,itermCellframe的变化如下图所示:

UICollectionView自定义布局(二)itermCell分为三种类型:
  1. FeaturedCell : 突出显示的cell,高度为featuredHeight
  2. StandardCell : 标准状态下的cell,高度为standardHeight
  3. ChangedCell : 高度随着contentOffSet改变而改变的cell,高度的变化范围在standardHeight和featuredHeight之间。(FeaturedCell下面的那个cell)
1.获取FeaturedCell的索引
- (int)featuredItemIndex{
   int index = (int)(self.collectionView.contentOffset.y / self.dragOffset);
   return MAX(0, index);
}

self.dragOffset是拖拽距离(当偏移量大于这个值时,featuredItemIndex的索引会变为下一个)。由当前FeaturedCell的索引index可以获得ChangedCell的索引为index+1,进而得到其他的索引位置就是StandardCell

2.重写prepareLayout方法

随着collectionView的滑动,standardCell 变化为 featuredCell,变化范围为[0 ,1]。

- (CGFloat)nextItemPercentageOffset{
    CGFloat percent = (self.collectionView.contentOffset.y / self.dragOffset) - [self featuredItemIndex];
    return percent;
}
  1. attribute.zIndex的值随着iterm的增加逐渐增大,形成上图所示的覆盖效果。
  2. ChangedCell的高度随着偏移距离,由standardHeight变化为featuredHeight
  3. 从视觉上看由StandardCell变为FeaturedCell只移动了standardHeight的距离,但是实际上contentOffSet.y移动的距离大于这个值,实际上移动了self.dragOffset才能完成这个变换。
  4. 详细的代码如下所示。
- (void)prepareLayout{
    [super prepareLayout];
    [self.attributesArray removeAllObjects];
    
    CGRect frame = CGRectZero;
    CGFloat y = 0;
    
    NSInteger numberOfIterm = [self.collectionView numberOfItemsInSection:0];
    for (int i = 0; i < numberOfIterm; i++) {
        NSIndexPath *path = [NSIndexPath indexPathForItem:i inSection:0];
        UICollectionViewLayoutAttributes *attribute = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:path];
        /*下一个cell都在之前的cell之上*/
        attribute.zIndex = path.item;
        /*初始化时设置cell的高度都为标准高度*/
        CGFloat height = standardHeight;
        
        if (path.item == [self featuredItemIndex]) {
            /*featured Cell*/
            CGFloat yOffSet = standardHeight * [self nextItemPercentageOffset];
            y = self.collectionView.contentOffset.y - yOffSet;
            height = featuredHeight;
            
        }else if (path.item == [self featuredItemIndex] + 1 && path.item != numberOfIterm){
            /*在featuredCell之下,随着用户滚动逐渐变大*/
            CGFloat maxY = y + standardHeight;
            height = standardHeight + MAX((featuredHeight - standardHeight) * [self nextItemPercentageOffset], 0);
            y = maxY - height;
        }
        frame = CGRectMake(0, y, CGRectGetWidth(self.collectionView.bounds), height);
        attribute.frame = frame;
        [self.attributesArray addObject:attribute];
        
        /*获取下一个cell的初始的Y值*/
        y = CGRectGetMaxY(frame);
    }
    
    //重新刷新collectionView,不然数据会错乱
    [self.collectionView reloadData];
}
3.改变滚动停止时的位置

itermCell滚动的时候,将其停在固定的点。使其滚动停止时,屏幕显示的第一个cell永远是FeaturedCell

- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity{
    NSInteger currentFeaturedIndex = round(proposedContentOffset.y / self.dragOffset);
    CGFloat yOffSet = currentFeaturedIndex * self.dragOffset;
    return CGPointMake(0, yOffSet);
}

添加图片背景和详情内容

图片和文本的创建代码比较简单就不列出了,需要注意的是:

  1. UIImageViewcontentMode设置为UIViewContentModeScaleAspectFill。并且设置layer.masksToBounds = YES
  2. 一定要使用自动布局否则会显示不正常。

重写applyLayoutAttributes

  1. 根据偏移量改变黑色CoverView的背景色,突出显示FeaturedCell
  2. 根据偏移量对titleLabel进行仿射变换。
  3. 根据偏移量对detailLabel进行透明度变化,只有当前cell是FeaturedCell的时候才显示。
- (void)applyLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes{
    [super applyLayoutAttributes:layoutAttributes];

    CGFloat standardHeight = 100.0;
    CGFloat featuredHeight = 280.0;

    /*根据移动距离改变CoverView透明度*/
    CGFloat factor = 1 - (featuredHeight - CGRectGetHeight(layoutAttributes.frame))/(featuredHeight - standardHeight);
    CGFloat minAlpha = 0.2;
    CGFloat maxAlpha = 0.75;
    CGFloat currentAlpha = maxAlpha - (maxAlpha - minAlpha) * factor;
    self.coverView.alpha = currentAlpha;
    
    /*改变字体大小*/
    CGFloat titleScale = MAX(0.5, factor);
    self.titleLabel.transform = CGAffineTransformMakeScale(titleScale, titleScale);
    
    /*设置detailLabel的透明度*/
    self.timeAndRoomLabel.alpha = factor;
    self.speakerLabel.alpha = factor;
}

至此,自定义布局就全部完成了,Demo链接可以到GitHub下载。

相关推荐