一个自定义中间放大CollectionViewLayout

news2024/12/27 3:25:10

效果图如下

在这里插入图片描述

思路:

根据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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/734918.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

微服务之服务器缓存

Informal Essay By English In the difficult employment situation, we need to set a good goal and then do our own thing 参考书籍&#xff1a;“凤凰架构” 进程缓存&#xff08;Cache&#xff09; 缓存在分布式系统是可选&#xff0c;在使用缓存之前需要确认你的系统…

Elasticsearch【集群概念、搭建集群】(七)-全面详解(学习总结---从入门到深化)

目录 Elasticsearch集群_概念 Elasticsearch集群_搭建集群 Elasticsearch集群_概念 在单台ES服务器上&#xff0c;随着一个索引内数据的增多&#xff0c;会产生存储、效率、安全等问题。 1、假设项目中有一个500G大小的索引&#xff0c;但我们只有几台200G硬盘 的服务器&am…

Debezium日常分享系列之:流式传输 Cassandra

Debezium日常分享系列之&#xff1a;流式传输 Cassandra 一、批量 ETL 选项二、流媒体选项三、Kafka 作为事件源四、解析提交日志五、提交日志深入探讨1.延迟处理2.空间管理3.重复的事件4.无序事件5.带外架构更改6.行数据不完整 六、最低限度可行的基础设施1.无状态流处理2.有状…

45. 跳跃游戏 II (贪心)

题目链接&#xff1a;力扣 解题思路&#xff1a;贪心&#xff0c;尽可能地找到下一跳能够跳到的最远距离&#xff0c;这样到达终点时&#xff0c;所需跳跃次数最少 以nums [2,3,1,1,4,2]为例&#xff1a; 以当前位置begin作为起跳点&#xff0c;能够跳跃的最远距离为m&#…

影视剧配音软件哪个好?几款好用的影视剧配音软件推荐

影视剧配音软件哪个好&#xff1f;几款好用的影视剧配音软件推荐 我们日常刷短视频的时候&#xff0c;经常会刷到一些影视剧相关的作品&#xff0c;特别是一些大热剧及经典剧&#xff0c;很多创作者都喜欢融入自己的解读&#xff0c;进行一些加工&#xff0c;形成一部的独一无…

STM32 Mac开发环境Clion+STM32CubeMX+ST-Link-V2

STM32 Mac开发环境ClionSTM32CubeMXST-Link-V2 也不知道什么时候买的stm32板吃灰太久&#xff0c;不会玩&#xff0c;环境之前都没搞定&#xff0c;今天又折腾一天终于可以点灯了。 安装编译器gcc brew tap ArmMbed/homebrew-formulae brew install arm-none-eabi-gccOPEN-O…

Qt提取excel表单中数据

这是一个excel表单&#xff0c;目标是把其中的数据提取出来。 文章学习自&#xff1a;QT中将excel中的数据快速的读取出来显示在tablewidget中/将tablewidget中的数据快速的写入excel中_qt将excel表格中指定范围内容显示在界面中_Jessica_1409573408的博客-CSDN博客 程序如下&…

前端CSS

基础语法 /*CSS注释 */ CSS样式 CSS应用方式 内联式 在标签上写样式 <img src"..." style"height:100px" /><div style"color:red;">中国联通</div> 嵌入式 在head标签中写style标签 外联式 样式写到文件中&#xff0…

网页链接投票链接步骤公众号投票链接制作制作投票

大家在选择投票小程序之前&#xff0c;可以先梳理一下自己的投票评选活动是哪种类型&#xff0c;目前有匿名投票、图文投票、视频投票、赛事征集投票等。 我们现在要以“笛乐悠扬”为主题进行一次投票活动&#xff0c;我们可以在在微信小程序搜索&#xff0c;“活动星”投票小程…

语言模型BERT理解

一、BERT概述 BERT是由Google在2018年提出的一种预训练语言模型。BERT的创新之处在于采用了双向Transformer编码器来生成上下文相关的词向量表示。 传统的单向语言模型只考虑了左侧或右侧的上下文信息&#xff0c;而BERT则同时考虑了左侧和右侧的上下文信息&#xff0c;使得生…

YOLOv5改进系列(15)——增加小目标检测层

【YOLOv5改进系列】前期回顾: YOLOv5改进系列(0)——重要性能指标与训练结果评价及分析 YOLOv5改进系列(1)——添加SE注意力机制

手把手教您kaiber,吊炸天的AI视频生成工具

什么是Kaiber AI&#xff1f; 一种人工智能视频生成器&#xff0c;可以将图像和文字生成视频。 如何使用Kaiber AI&#xff1f; 请按照以下步骤使用 Kaiber AI&#xff1a; 前往kaiber ai注册或登录。点击右上角“创建视频”。 如果订阅的时候提示要绑定银行卡&#xff0c;则找…

Basics——指针和引用(详解)

指针和引用 1.初始化规则2.面试题 &#xff1a;引用和指针的区别是什么3.引用使用场景4.拓展 为什么C支持引用而C没有 1.初始化规则 指针和引用在初始化方面有不同的规则&#xff1a; 指针的初始化规则&#xff1a; 直接初始化&#xff1a;可以将指针初始化为指向特定变量或…

Linux系统运行时参数命令(性能监控、测试)(3)网络IO性能监控

目录 5. 网络IO性能监控5.1 性能指标5.2 网络信息5.2.1 网络配置5.2.2 套接字信息5.2.3 网络吞吐-sar命令5.2.4 连通性和延时 5.3 其他常用的网络相关命令5.3.1 telnet5.3.2 nc5.3.3 tcpdump5.3.4 lsof5.3.5 nmap 6.其他工具6.1 nmon性能监控6.2 glances系统监控 5. 网络IO性能…

Js提升:如何实现图片懒加载

知其然&#xff0c;更要知其所有然&#xff0c;在不同场景下该用什么方法&#xff0c;如何做到最优。 为什么要出现图片懒加载&#xff0c;解决了什么问题&#xff1f;除了懒加载&#xff0c;还有预加载呢&#xff1f;什么是预加载&#xff0c;怎么实现&#xff0c;相比于懒加载…

软件设计模式与体系结构-软件体系-层次软件体系结构

目录 四、层次软件体系结构简介代码两种方式的区别双向分层分层风格 VS 主程序-子过程风格&#xff1a;二者的不同层次软件体系结构的优点层次软件体系结构的缺点 课程作业 四、层次软件体系结构 层次之间存在接口&#xff0c;通过接口形成call/return的关系&#xff0c;上层是…

【内存优化】内存优化以及oom排查整体思路

linux疑难问题排查实战专栏&#xff0c;分享了作为公司专家&#xff0c;在解决内存、性能、各类死机等疑难问题的排查经验&#xff0c;认真学习可以让你在日后工作中大放光彩。 本文总结介绍了项目开发过程中oom排查和内存优化的一些方法&#xff0c;主要是从内存问题查看到堆内…

阿里云轻量服务器和ecs区别(最新更新)

阿里云服务器ECS和轻量应用服务器有什么区别&#xff1f;云服务器ECS是明星级云服务器&#xff0c;轻量应用服务器可以理解为简化版的云服务器ECS&#xff0c;轻量适用于单机应用&#xff0c;云服务器ECS适用于集群类高可用高容灾应用&#xff0c;阿里云百科来详细说下阿里云轻…

组合模式:如何设计实现支持递归遍历的文件系统目录树结构?

组合模式跟我们之前讲的面向对象设计中的“组合关系&#xff08;通过组合来组装两个类&#xff09;”&#xff0c;完全是两码事。这里讲的“组合模式”&#xff0c;主要是用来处理树形结构数据。这里的“数据”&#xff0c;你可以简单理解为一组对象集合&#xff0c;待会我们会…

使用 geopandas 和 shapely(.shp) 进行地理空间数据处理和可视化

文章目录 前言1. 安装所需库2. 读取 Shapefile 文件3. 可视化地图4. 用户输入坐标和清除指定区域内的图形5. 可视化删除指定区域内的图形之后的地图6. 保存为新的 Shapefile (.shp)文件完整代码及解析分析说明 测试文件地址特别说明完结 前言 在地理信息系统&#xff08;Geogra…