仿优酷精选播单(中间大,两侧小,带分页效果的collectionViewLayout)

有时候为了区分和banner一样的播放效果,比如在一个界面有多处collectionView分页横向滚动,交互上为了区分,就引出了这种中间大,两侧小的的collectionView布局.如下图展示:
pagedScale.gif
因为这种布局仍然是流水布局,所以可以构建一个自定义布局,继承于collectionViewFlowLayout.再解决两个核心问题.

1.越靠近中间位置的item越大(其实是离中间越远越小,中间的就显得最大).重写系统的此方法

  • (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect

{

// 1.1拿到系统已经算好的布局.

NSArray *attributes = [super layoutAttributesForElementsInRect:rect];

// 1.2 计算当前collectionView的中间位置的x值

CGFloat currentCenterX = self.collectionView.contentOffset.x + self.collectionView.bounds.size.width / 2.0;

// 1.3遍历当前rect内所有layoutAttributes,计算它跟currentCenterX的距离,距离越远,计算出的缩放值越小,再赋值给layoutAttributes

for (UICollectionViewLayoutAttributes *attri in attributes) {

    CGFloat itemCenterX = attri.center.x;

    CGFloat delta = ABS(currentCenterX - itemCenterX);

    // 这里取最小值是因为距离大于一个collectionView宽度的计算出来也没有用,不会显示

    delta = MIN(delta, self.collectionView.bounds.size.width);

    CGFloat scale = (self.nearbyScale - self.centerScale) * delta / self.collectionView.bounds.size.width + self.centerScale;

    attri.transform = CGAffineTransformMakeScale(1, scale);

}

return attributes;

}

然计算一次是不够的,在collectionView滚动的过程中,每个item的centerX都在变化,所以一旦collectionView发生滚动,就得重新计算.

// 覆写此方法即可.

  • (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds

{

return YES;

}

第一个核心问题就解决了.

2.当手放开的时候,让离中间最近的item居中.当我们手滑动collectionView后离开,系统会立即调用下面的方法,proposedContentOffset就是系统算好的原本的目标位置,重写该方法,找出离得最近的item,根据这个item计算出新的targetContentOffset并返回给系统,即可实现.

  • (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity

{

// final rect

CGRect rect;

rect.origin.y = 0;

rect.origin.x = proposedContentOffset.x;

rect.size = self.collectionView.frame.size;

NSArray *attributes = [super layoutAttributesForElementsInRect:rect];

// find nearest item

CGFloat centerX = proposedContentOffset.x + self.collectionView.frame.size.width * 0.5;;

CGFloat targetOffSetX = MAXFLOAT;

for (UICollectionViewLayoutAttributes *attribute in attributes) {

    if (ABS(targetOffSetX) > ABS(centerX - attribute.center.x)) {

        targetOffSetX = attribute.center.x - centerX;

    }

}

return CGPointMake(proposedContentOffset.x + targetOffSetX, proposedContentOffset.y);

}

在下面两个方法中做一些初始化

  • (instancetype)init

{

if (self = [super init]) {

    _centerScale = 1.0f;

    _nearbyScale = 0.8f;

}

return self;

}

  • (void)prepareLayout

{

[super prepareLayout];

CGFloat left = (self.collectionView.bounds.size.width - self.itemSize.width) / 2.0;

self.sectionInset = UIEdgeInsetsMake(0, left, 0, left);

self.scrollDirection = UICollectionViewScrollDirectionHorizontal;

// 让collectionView滑动的慢一点,分页效果就出来了.

self.collectionView.decelerationRate = UIScrollViewDecelerationRateFast;

}

至此,效果就完全达到了.

新版Xcode在运行该代码时会报一个警告 UICollectionViewFlowLayout has cached frame mismatch for index path..这个警告来源主要是在使用layoutAttributesForElementsInRect:方法返回的数组时,没有使用该数组的拷贝对象,而是直接使用了该数组。解决办法对该数组进行拷贝,并且是深拷贝。

  • (NSArray *)deepCopyWithArray:(NSArray *)arr {

    NSMutableArray *arrM = [NSMutableArray array];

    for (UICollectionViewLayoutAttributes *attr in arr) {

      [arrM addObject:[attr copy]];
    

    }

    return arrM;

}

...

NSArray *attributes = [self deepCopyWithArray:[super layoutAttributesForElementsInRect:rect]];

...

github链接

首页点击右上角change

你可能感兴趣的:(仿优酷精选播单(中间大,两侧小,带分页效果的collectionViewLayout))