封装了一个iOS中间放大的collectionView layout

效果图如下所示

请添加图片描述

原理:就是首先确定一个放大和缩小系数和原大小对应的基准位置,然后根据距离每个布局属性到视图中心的距离和基准点到中心的距离的差距/基准点到中心的距离, 计算出每个布局属性的缩放系数

下面是代码

//
//  LBHorizontalCenterLayout.m
//  LBHorizontalCenterLayout
//
//  Created by mac on 2024/5/24.
//

#import "LBHorizontalCenterLayout.h"

@interface LBHorizontalCenterLayout ()

@property (nonatomic, assign) NSInteger index;

@end

@implementation LBHorizontalCenterLayout

- (NSArray<__kindof UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect
{
    NSArray *array = [super layoutAttributesForElementsInRect:rect];
    array = [self getCopyOfAttributes:array];
    //屏幕中线
    CGFloat centerX = self.collectionView.contentOffset.x + self.collectionView.bounds.size.width/2.0f;
    //刷新cell 缩放
    for (UICollectionViewLayoutAttributes *attributes in array) {
        if (self.scrollDirection == UICollectionViewScrollDirectionHorizontal) {
            //分四种情况,以44 的item为基准,68和35 对应系数分别是1.56 和 0.8
            CGFloat baseW = self.minimumLineSpacing + self.itemSize.width;
            CGFloat apartScale;
            CGFloat distance = fabs(attributes.center.x - centerX);
            attributes.alpha = 1.0;
            if (distance <= baseW) {
                apartScale = 1 + (baseW - distance) / baseW * 0.56;
            } else {
                apartScale = 1 - (distance - baseW) / baseW * 0.2;
                //设置透明度
                if (attributes.center.x - centerX < 0) {
                    CGFloat alpha = (distance - baseW) / baseW;
                    attributes.alpha = 1 - alpha;
                }
            }
            //设置cell的缩放,按照余弦函数,越居中越趋近于1
            attributes.transform = CGAffineTransformMakeScale(apartScale, apartScale);
        }
    }
    return array;
}

- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
{
    //把collectionview 本身的中心位位置(固定的), 转换成collectionView 整个内容上的point
    // convert the center position of the collectionView itself (fixed) to a point on the
    //entire content of the collectionView
    CGPoint pInView = [self.collectionView.superview convertPoint:self.collectionView.center toView:self.collectionView];
    
    NSIndexPath *indexPath = [self.collectionView indexPathForItemAtPoint:pInView];
    if (indexPath.item == 0) {
        if (newBounds.origin.x < self.collectionView.bounds.size.width / 2) {
            if (self.index != indexPath.item) {
                self.index = 0;
                if (self.delegate && [self.delegate respondsToSelector:@selector(collectionViewScrollToIndex:)]) {
                    [self.delegate collectionViewScrollToIndex:self.index];
                }
            }
        }
    } else {
        if (self.index != indexPath.item) {
            self.index = indexPath.item;
            if (self.delegate && [self.delegate respondsToSelector:@selector(collectionViewScrollToIndex:)]) {
                [self.delegate collectionViewScrollToIndex:self.index];
            }
        }
    }
    [super shouldInvalidateLayoutForBoundsChange:newBounds];
    return YES;
}

- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity
{
    CGFloat minOffset = CGFLOAT_MAX;
    CGFloat horizontalCenter = proposedContentOffset.x + self.collectionView.bounds.size.width/2;
    CGRect visibleRec = CGRectMake(proposedContentOffset.x, 0, self.collectionView.bounds.size.width, self.collectionView.bounds.size.height);
    NSArray *visibleAttributes = [super layoutAttributesForElementsInRect:visibleRec];
    for (UICollectionViewLayoutAttributes *atts in visibleAttributes) {
        CGFloat itemCenterX = atts.center.x;
        if (fabs(itemCenterX - horizontalCenter) <= fabs(minOffset)) {
            minOffset = itemCenterX - horizontalCenter;
        }
    }
    
    CGFloat centerOffsetX = proposedContentOffset.x + minOffset;
    //快速轻扫的时候, 距离过短,时间过短, 会导致无法进行翻页,及时很快的轻扫也让他能左右翻页
    if (fabs(velocity.x) > 0.7 && fabs(velocity.x) < 1.5 && fabs(minOffset) <= (self.collectionView.bounds.size.width / 2.0)) {
        if (velocity.x > 0) {
            centerOffsetX = (self.index + 1) * (self.itemSize.width + self.minimumLineSpacing);
        } else {
            centerOffsetX = (self.index - 1) * (self.itemSize.width + self.minimumLineSpacing);
        }
    }
    
    if (centerOffsetX < 0) {
        centerOffsetX = 0;
    }
    
    if (centerOffsetX < self.collectionView.contentSize.width - (self.sectionInset.left + self.sectionInset.right + self.itemSize.width)) {
        centerOffsetX = floor(centerOffsetX);
    }
    return CGPointMake(centerOffsetX, proposedContentOffset.y);
}
//防止报错,先复制attributes

- (NSArray *)getCopyOfAttributes:(NSArray *)attributes
{
    NSMutableArray *array = [NSMutableArray array];
    for (UICollectionViewLayoutAttributes *attribute in attributes) {
        [array addObject:[attribute copy]];
    }
    return array;
}

@end

下面这一段是计算放大系数的核心逻辑

   if (self.scrollDirection == UICollectionViewScrollDirectionHorizontal) {
            //分四种情况,以44 的item为基准,68和35 对应系数分别是1.56 和 0.8
            CGFloat baseW = self.minimumLineSpacing + self.itemSize.width;
            CGFloat apartScale;
            CGFloat distance = fabs(attributes.center.x - centerX);
            attributes.alpha = 1.0;
            if (distance <= baseW) {
                apartScale = 1 + (baseW - distance) / baseW * 0.56;
            } else {
                apartScale = 1 - (distance - baseW) / baseW * 0.2;
                //设置透明度
                if (attributes.center.x - centerX < 0) {
                    CGFloat alpha = (distance - baseW) / baseW;
                    attributes.alpha = 1 - alpha;
                }
            }
            //设置cell的缩放,按照余弦函数,越居中越趋近于1
            attributes.transform = CGAffineTransformMakeScale(apartScale, apartScale);

支持pod


pod 'LBHorizontalCenterLayout'