一个自定义中间放大CollectionViewLayout

效果图如下

在这里插入图片描述

思路:

根据cell距离屏幕中间的距离,设置cell的缩小系数,并通过设置 attributes.transform 缩小cell
attributes.transform = CGAffineTransformMakeScale(1.0, scale);

核心代码

//
//  LBMiddleExpandLayout.m
//  LiuboMiddleExpandLayout
//
//  Created by liubo on 2023/7/8.
//

#import "LBMiddleExpandLayout.h"

@implementation LBMiddleExpandLayout


//设置放大动画
-(NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect
{
    NSArray *arr = [self getCopyOfAttributes:[super layoutAttributesForElementsInRect:rect]];
    //屏幕中线
    CGFloat centerX = self.collectionView.contentOffset.x + self.collectionView.bounds.size.width/2.0f;
    //刷新cell缩放
    for (UICollectionViewLayoutAttributes *attributes in arr) {
        CGFloat distance = fabs(attributes.center.x - centerX);
        //移动的距离和屏幕宽度的的比例
        CGFloat apartScale = distance/self.collectionView.bounds.size.width;
        //把卡片移动范围固定到 -π/6到 +π/6这一个范围内

        CGFloat scale = fabs(cos(apartScale * M_PI/6));
        
        //设置cell的缩放 按照余弦函数曲线 越居中越趋近于1
        attributes.transform = CGAffineTransformMakeScale(1.0, scale);
    }
    return arr;
}

//防止报错 先复制attributes
- (NSArray *)getCopyOfAttributes:(NSArray *)attributes
{
    NSMutableArray *copyArr = [NSMutableArray new];
    for (UICollectionViewLayoutAttributes *attribute in attributes) {
        [copyArr addObject:[attribute copy]];
    }
    return copyArr;
}

//是否需要重新计算布局
-(BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
{
    return true;
}


@end


//手指拖动开始
-(void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
    self.m_dragStartX = scrollView.contentOffset.x;
}

- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
    if (self.endDraggingOffset < 0 && scrollView.contentOffset.x >= self.endDraggingOffset) {
        //这里是为了上刷新的时候,停留在固定的偏移量,展示loading
        scrollView.contentOffset = CGPointMake(self.endDraggingOffset, 0);
    }
    
    if (!self.hasNextPage) {
        return;
    }
    
    /*
     遇到一次精度问题,正在刷新的时候 应该展示loading,但是由于精度问题,这时候
      scrollView.contentOffset.x = -52.333333 , refreshThreshold = -52.399998,
     如果 是 (scrollView.contentOffset.x <= refreshThreshold ),就会造成loading隐藏,
     所以这里+0.5
     
     */
    if (scrollView.contentOffset.x <= (refreshThreshold + 0.5)) {
        self.refreshView.hidden = NO;
    } else {
        self.refreshView.hidden = YES;
    }
}

//手指拖动停止
-(void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
    self.m_dragEndX = scrollView.contentOffset.x;
    dispatch_async(dispatch_get_main_queue(), ^{
        [self fixCellToCenter];
    });
    if (scrollView.contentOffset.x < refreshThreshold) {
        if (!self.hasNextPage) {
            return;
        }
        if (self.isLoadingMore) {
            return;
        }
        self.endDraggingOffset = refreshThreshold;
        NSMutableArray *array = [NSMutableArray array];
        for (int i = (self.signsArray.count + 10); i > self.signsArray.count; i --) {
            [array addObject:[NSString stringWithFormat:@"%d", i]];
        }
        [self refreshHandleWithArray:array];
        self.isLoadingMore = YES;
    }
}

#pragma mark - private

- (void)fixCellToCenter {
    //最小滚动距离
    float dragMiniDistance = self.view.bounds.size.width/20.0f;
    if (self.m_dragStartX -  self.m_dragEndX >= dragMiniDistance) {
        self.m_currentIndex -= 1;//向右
    } else if(self.m_dragEndX -  self.m_dragStartX >= dragMiniDistance){
        self.m_currentIndex += 1;//向左
    }
    NSInteger maxIndex = [_collectionView numberOfItemsInSection:0] - 1;
    self.m_currentIndex = self.m_currentIndex <= 0 ? 0 : self.m_currentIndex;
    self.m_currentIndex = self.m_currentIndex >= maxIndex ? maxIndex : self.m_currentIndex;
    [self.collectionView scrollToItemAtIndexPath:[NSIndexPath indexPathForRow:self.m_currentIndex inSection:0] atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally animated:YES];
}

- (void)refreshHandleWithArray:(NSArray <NSString *> *)array
{
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSArray *originArray = [self.signsArray copy];
        [self.signsArray removeAllObjects];
        [self.signsArray addObjectsFromArray:array];
        [self.signsArray addObjectsFromArray:originArray];
        self.refreshView.hidden = YES;
        [self.collectionView reloadData];
        self.m_currentIndex = array.count - 1;
        
        CGFloat temp = self.endDraggingOffset;
        self.endDraggingOffset = 0;
        /*
         刷新过之后首先要隐性偏移到之前滚动到的日签卡片处,(index.item已经不一样了,之前是0,现在是请求到的
         日签数量),
         */
        [self.collectionView scrollToItemAtIndexPath:[NSIndexPath indexPathForItem:self.m_currentIndex + 1 inSection:0] atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally animated:NO];
        CGFloat currentOffset = self.collectionView.contentOffset.x;
        currentOffset = currentOffset + temp;
        [self.collectionView setContentOffset:CGPointMake(currentOffset, 0)];
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.01 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            /*
             然后显性的滚动到请求到的日签处的最后一个,从而达到动画切换到最新的一个选项的效果
             */
            [self.collectionView scrollToItemAtIndexPath:[NSIndexPath indexPathForRow:self.m_currentIndex inSection:0] atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally animated:YES];
            self.isLoadingMore = NO;
        });
    });
}

注意上面,加载更多的时候,有一个衔接动画,由于是从左边加载更多的,但是我们正常的添加新cell是添加到右边的,
这里我们 做了一个假动作,即将新数据添加到前面之后,刷新整个collectionView,然后立即隐性(无动画)滚动到刷新前那个cell的位置
,停留0.01秒时候,在有动画的滚动到新添加的最后一个cell (即和之前的cell紧邻的),这样就实现了刷新的一个动画效果,
总的思路就是刷新过之后,无动效滚动到原来的cell,然后又动效滚动到目标cell

    ///这里是为了隐性滚动到原位置
        [self.collectionView setContentOffset:CGPointMake(currentOffset, 0)];
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.01 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            /*
             然后显性的滚动到请求到的日签处的最后一个,从而达到动画切换到最新的一个选项的效果
             */
            [self.collectionView scrollToItemAtIndexPath:[NSIndexPath indexPathForRow:self.m_currentIndex inSection:0] atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally animated:YES];
            self.isLoadingMore = NO;
        });

链接: link

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在后端代码中,你可以通过以下方式返回一个自定义响应头: ``` response.addHeader("你自定义的响应头字段名", "你自定义的响应头字段值"); response.setHeader("Access-Control-Expose-Headers", "你自定义的响应头字段名"); ``` 其中,你需要将"你自定义的响应头字段名"替换为你想要设置的自定义响应头字段的名称,将"你自定义的响应头字段值"替换为你想要设置的自定义响应头字段的值。这样,在服务器响应中就会包含你设置的自定义响应头。\[1\] 请注意,在前端获取自定义响应头数据时,你可以使用以下代码: ``` var testdata = JSON.parse(response.headers\["你自定义的响应头字段名"\]); console.log(testdata); ``` 其中,你需要将"你自定义的响应头字段名"替换为你在后端设置的自定义响应头字段的名称。这样,你就可以在前端获取到自定义响应头的数据了。\[3\] #### 引用[.reference_title] - *1* [【已解决】前端无法获取自定义响应头字段](https://blog.csdn.net/weixin_42258128/article/details/121834311)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* *3* [前端获取自定义响应头数据](https://blog.csdn.net/Renners/article/details/125386420)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值