封装了一个仿照抖音评论轮播效果的iOS轮播视图

效果图

请添加图片描述

原理

就是我们在一个视图里面有两个子视图,一个是currentView,
一个是willShowView,在一次动画过程中,我们改变current View的frame,同时改变willShowView的frame,同时,需要改变currentVIew 的transform.y不然的话,currentView里面的内容就没有缩放效果了,看起来就是单纯的展示不下的感觉,动画结束之后,将currentView指向willView, willView指向currentView, 同时,将刚刚消失的视图,放到底部,等待下次动画展示

#代码

//
//  RollingCell.m
//  TEXT
//
//  Created by 刘博 on 2021/3/18.
//  Copyright © 2021 刘博. All rights reserved.
//

#import "XBNoticeViewCell.h"
#import "XBRollingNoticeView.h"

@interface XBRollingNoticeView ()

@property (nonatomic, strong) NSMutableDictionary *cellClsDict; //注册 cell 的字典,key为cell的类名,value 为identifier
@property (nonatomic, strong) NSMutableArray *reuseCells; //重用cell的实例对象数组
@property (nonatomic, strong) NSTimer *timer; //计时器
@property (nonatomic, strong) XBNoticeViewCell *currentCell; //当前展示的cell
@property (nonatomic, strong) XBNoticeViewCell *willShowCell; //即将展示的cell
@property (nonatomic, assign) BOOL isAnimating; //动画
@property (nonatomic, assign) BOOL isRefresing ; ///在刷新, 多次刷新的时候,防止上次未执行完的动画对新的一轮刷新造成干扰
///
@property (nonatomic, strong) NSMutableArray *array ;

@end

@implementation XBRollingNoticeView


- (instancetype)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        [self setupNoticeViews];
    }
    return self;
}

- (void)setupNoticeViews
{
    self.clipsToBounds = YES;
    _stayInterval = 2;
    _animationDuration = 0.66;
    _fadeTranslationY = 6;
    [self addGestureRecognizer:[self createTapGesture]];
}

- (void)registerClass:(nonnull Class)cellClass forCellReuseIdentifier:(NSString *)identifier
{
    [self.cellClsDict setObject:NSStringFromClass(cellClass) forKey:identifier];
}

- (__kindof XBNoticeViewCell *)dequeueReusableCellWithIdentifier:(NSString *)identifier
{
    for (XBNoticeViewCell *cell in self.reuseCells)
    {
        if ([cell.reuseIdentifier isEqualToString:identifier]) {
            cell.userInteractionEnabled = NO;
            return cell;
        }
    }
    
    Class cellCls = NSClassFromString(self.cellClsDict[identifier]);
    XBNoticeViewCell *cell = [[cellCls alloc] initWithReuseIdentifier:identifier];
    cell.userInteractionEnabled = NO;
    return cell;
}

#pragma mark- rolling
- (void)layoutCurrentCellAndWillShowCell
{
    int count = (int)[self.dataSource numberOfRowsForRollingNoticeView:self];
    if (_currentIndex > count - 1) {
        _currentIndex = 0;
    }
    int willShowIndex = _currentIndex + 1;
    if (willShowIndex > count - 1) {
        willShowIndex = 0;
    }
    
    float w = self.frame.size.width;
    float h = self.frame.size.height;
    if (!_currentCell) {
        // 第一次没有currentcell
        // currentcell is null at first time
        _currentCell = [self.dataSource rollingNoticeView:self cellAtIndex:_currentIndex];
        _currentCell.frame = CGRectMake(0, 0, w, h);
        if (![self.subviews containsObject:self.currentCell]) {
            [self addSubview:_currentCell];
        }
        if (self.style == RollingStyleDefault) {
            ///默认轮播滚动样式,首次展示不需要加载下一个
            return;
        }
    }
    CGFloat willY = h + self.spaceOfItem;
    if (self.style == RollingStyleFade) {
        //淡入淡出的样式
        willY = 4;
    } else if (self.style == RollingStyleScaleY) {
        willY = h + self.spaceOfItem;
    }
    _willShowCell = [self.dataSource rollingNoticeView:self cellAtIndex:willShowIndex];
    
    _willShowCell.frame = CGRectMake(0, willY, w, h);
    if (self.style == RollingStyleFade) {
        ///首次展示currentCell的时候,will 需要隐藏
        _willShowCell.alpha = 0;
    }
    if (![self.subviews containsObject:_willShowCell]) {
        [self addSubview:_willShowCell];
    }

    self.isRefresing = YES;
    [self.reuseCells removeObject:_currentCell];
    [self.reuseCells removeObject:_willShowCell];
}

- (void)reloadDataAndStartRoll
{
    [self stopTimer];
    
    [self layoutCurrentCellAndWillShowCell];
    NSInteger count = [self.dataSource numberOfRowsForRollingNoticeView:self];
    if (count && count < 2) {
        return;
    }
    __weak typeof(self) weakSelf = self;
    self.timer = [NSTimer timerWithTimeInterval:self.stayInterval + self.animationDuration repeats:YES block:^(NSTimer * _Nonnull timer) {
        [weakSelf timerHandle];
    }];
    NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
    [runLoop addTimer:self.timer forMode:NSRunLoopCommonModes];
}

- (void)stopTimer
{
    if (_timer) {
        [_timer invalidate];
        _timer = nil;
    }
    _isAnimating = NO;
    _currentIndex = 0;
    [_currentCell removeFromSuperview];
    [_willShowCell removeFromSuperview];
    _currentCell = nil;
    _willShowCell = nil;
    [self.reuseCells removeAllObjects];
}

- (void)pause
{
    if (_timer) {
        [_timer setFireDate:[NSDate distantFuture]];
    }
}

- (void)proceed
{
    if (_timer) {
        [_timer setFireDate:[NSDate date]];
    }
}

- (void)timerHandle
{
    if (self.isAnimating) {
        return;
    }
    if (self.style == RollingStyleDefault) {
        [self defaultTimeHandler];
    } else if (self.style == RollingStyleFade) {
        [self fadeTimeHandler];
    } else if (self.style == RollingStyleScaleY) {
        [self scaleYTimeHandler];
    }
}
    
- (void)defaultTimeHandler
{
    [self layoutCurrentCellAndWillShowCell];
    _currentIndex++;
    int count = (int)[self.dataSource numberOfRowsForRollingNoticeView:self];
    if (_currentIndex > count - 1) {
        _currentIndex = 0;
    }
    float w = self.frame.size.width;
    float h = self.frame.size.height;
    
    self.isAnimating = YES;
    
    [UIView animateWithDuration:_animationDuration animations:^{
        self.currentCell.frame = CGRectMake(0, - h - self.spaceOfItem, w, h);
        self.willShowCell.frame = CGRectMake(0, 0, w, h);
    } completion:^(BOOL finished) {
        // fixed bug: reload data when animate running
        if (self.currentCell && self.willShowCell) {
            [self.reuseCells addObject:self.currentCell];
            [self.currentCell removeFromSuperview];
            self.currentCell = self.willShowCell;
        }
        self.isAnimating = NO;
    }];
}
    
- (void)fadeTimeHandler
{
    self.isRefresing = NO;
    self.isAnimating = YES;
    float w = self.frame.size.width;
    float h = self.frame.size.height;
    
    int count = (int)[self.dataSource numberOfRowsForRollingNoticeView:self];
    int willShowIndex = self->_currentIndex + 1;
    if (willShowIndex > count - 1) {
        willShowIndex = 0;
    }
    self->_willShowCell = [self.dataSource rollingNoticeView:self cellAtIndex:willShowIndex];
    self->_willShowCell.frame = CGRectMake(0, self.fadeTranslationY, w, h);
    self->_willShowCell.alpha = 0;
    [self addSubview:self.willShowCell];
    [self.reuseCells removeObject:self.willShowCell];
    [self.reuseCells removeObject:self.currentCell];
    ///动画隐藏当前的cell
    [UIView animateWithDuration:self.animationDuration delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{
        if (self.isRefresing) {
            self.currentCell.alpha = 1;
        } else {
            self.currentCell.alpha = 0;
        }
    } completion:^(BOOL finished) {
    }];
    [UIView animateWithDuration:self.animationDuration - 0.1 delay:0.1 options:UIViewAnimationOptionCurveLinear animations:^{
        if (self.isRefresing) {
            self.currentCell.frame = CGRectMake(0, 0, w, h);
        } else {
            self.currentCell.frame = CGRectMake(0, - self.fadeTranslationY, w, h);
            self.currentCell.alpha = 0;
        }
    } completion:^(BOOL finished) {
    }];
    ///动画展示下一个cell ,
    /*
     这里减0.07是需要在上面文案的动画还没有结束的时候,
     下面文案的动画就要开始了
     */
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)((self.animationDuration - 0.07) * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [self showNext];
    });
}

- (void)showNext
{
    [UIView animateWithDuration:self.animationDuration animations:^{
        if (self.isRefresing) {
            self.willShowCell.alpha = 0;
        } else {
            self.willShowCell.alpha = 1;
        }
    } completion:^(BOOL finished) {
    }] ;
    float w = self.frame.size.width;
    float h = self.frame.size.height;
    
    [UIView animateWithDuration:self.animationDuration - 0.1 delay:0.1 options:UIViewAnimationOptionCurveLinear animations:^{
        if (self.isRefresing) {
            self.willShowCell.frame = CGRectMake(0, self.fadeTranslationY, w, h);
        } else {
            self.willShowCell.frame = CGRectMake(0, 0, w, h);
        }
    } completion:^(BOOL finished) {
        if (self.isRefresing) {
            return;
        }
        self->_currentIndex++;
        int count = (int)[self.dataSource numberOfRowsForRollingNoticeView:self];
        if (self->_currentIndex > count - 1) {
            self->_currentIndex = 0;
        }
        if (self.currentCell && self.willShowCell) {
            [self.reuseCells addObject:self.currentCell];
        }
        self.currentCell = self.willShowCell;
        self.isAnimating = NO;
    }];
}

- (void)scaleYTimeHandler
{
    NSInteger count = [self.dataSource numberOfRowsForRollingNoticeView:self];
    float w = self.frame.size.width;
    float h = self.frame.size.height;
    [UIView animateWithDuration:self.animationDuration animations:^{
        self.currentCell.frame = CGRectMake(0, 0, w, 0);
        self.currentCell.transform = CGAffineTransformMakeScale(1, 0.01);
        self.willShowCell.frame = CGRectMake(0, 0, w, h);
    } completion:^(BOOL finished) {
        self.currentCell.frame = CGRectMake(0, w + self.spaceOfItem, w, h);
        self.currentCell.transform = CGAffineTransformMakeScale(1, 1);
        if (self.willShowCell && self.currentCell) {
            [self.reuseCells addObject:self.currentCell];
        }
        self.currentCell = self.willShowCell;
        self->_currentIndex += 1;
        if (self.currentIndex >= count) {
            self->_currentIndex = 0;
        }
        NSInteger willIndex = self.currentIndex + 1;
        if (willIndex >= count) {
            willIndex = 0;
        }
        self.willShowCell = [self.dataSource rollingNoticeView:self cellAtIndex:willIndex];
        self.willShowCell.frame = CGRectMake(0, w + self.spaceOfItem, w, h);
        [self addSubview:self.willShowCell];
        [self.reuseCells removeObject:self.willShowCell];
    }];
}

#pragma mark - gesture

- (void)handleCellTapAction
{
    int count = (int)[self.dataSource numberOfRowsForRollingNoticeView:self];
    if (_currentIndex > count - 1) {
        _currentIndex = 0;
    }
    if ([self.delegate respondsToSelector:@selector(didClickRollingNoticeView:forIndex:)]) {
        [self.delegate didClickRollingNoticeView:self forIndex:_currentIndex];
    }
}

- (UITapGestureRecognizer *)createTapGesture
{
    return [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(handleCellTapAction)];
}

#pragma mark- lazy
- (NSMutableDictionary *)cellClsDict
{
    if (!_cellClsDict) {
        _cellClsDict = [[NSMutableDictionary alloc]init];
    }
    return _cellClsDict;
}

- (NSMutableArray *)reuseCells
{
    if (!_reuseCells) {
        _reuseCells = [[NSMutableArray alloc]init];
    }
    return _reuseCells;
}

- (void)dealloc
{
    if (self.timer) {
        [self.timer invalidate];
        self.timer  = nil;
    }
}

- (NSMutableArray *)array
{
    if (!_array) {
        _array = [NSMutableArray array];
    }
    return _array;
}

@end


如果对您有帮助,欢迎给个star
demo

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

酒店旅游API服务汇总

各大旅游平台常用API服务汇总&#xff1a; 实时房源服务【Airbnb】飞猪旅行开放服务途牛旅行开放平台API华为云数字差旅【差旅管理】动态信息接口【美团酒店】旅行商城商家管理API【马蜂窝】交易流程接口【美团酒店】电子导游【携程旅行】

【Linux】磁盘文件和软硬链接

上篇博客我们说了内存级文件&#xff0c;就是文件加载到内存中它的一些操作。那么不可能所有文件文件都要加载到内存中&#xff0c;大部分文件都要存在与一种可以永久性存储数据的硬件中&#xff0c;就是我们要说的磁盘。现在的笔记本电脑用的都是硬盘&#xff0c;你可以理解为…

12. MySQL 日志

文章目录 【 1. 日志的基本原理 】【 2. 错误日志 Error Log 】2.1 启动和设置错误日志2.2 查看错误日志2.3 删除错误日志 【 3. 二进制日志 Binary Log 】3.1 启动和设置二进制日志3.2 查看二进制日志3.3 删除二进制文件删除所有二进制日志删除小于指定编号的二进制日志删除创…

ICPC2024 邀请赛西安站(7/8/13)

心得 [ICPC2024 Xian I] ICPC2024 邀请赛西安站重现赛 - 比赛详情 - 洛谷 7表示赛时ac了7个&#xff0c;8表示含补题总共ac数&#xff0c;13表示题目总数 题目 M. Chained Lights 打表&#xff0c;发现只有k1是YES //#include <bits/stdc.h> #include<iostream&…

LCTF 2018 bestphp‘s revenge

考点:Soap原生类Session反序列化CRLF注入 <?php highlight_file(__FILE__); $b implode; call_user_func($_GET[f], $_POST); session_start(); if (isset($_GET[name])) { $_SESSION[name] $_GET[name]; } var_dump($_SESSION); $a array(reset($_…

路由黑洞处理

今天BGP基础实验碰到了路由黑洞 BGP承载于IGP之上&#xff0c;BGP路由天生要递归&#xff0c;才能找出口 在E的BGP去A&#xff0c;下一跳只有B&#xff0c;但是流量走了两条路&#xff0c;c和d BGP路由黑洞&#xff1a; 控制层面可达&#xff0c;数据层面不可达; 路由条目在BG…

查看远程桌面端口,查看服务器的远程桌面端口的方法

如果你正在寻找一种方法来检查服务器的远程桌面端口&#xff0c;那么请务必按照以下步骤操作&#xff0c;以确保准确且安全地获取所需信息。这不仅是一个技术问题&#xff0c;更是一个关于效率和安全性的重要议题。 首先&#xff0c;你需要明确&#xff0c;远程桌面端口通常是…

C++之第十一课

课程列表 十分抱歉拖更了这么久&#xff0c;在之前的第十课里也有网友反馈了一个问题&#xff1a; 说的没错&#xff0c;怪我在审稿时没有注意图片&#xff0c;所以这里修正一下&#xff1a; 在此也十分感谢“我有一些感想……”网友的提醒&#xff01; 好了&#xff0c;回到…

【MySQL调优】如何进行MySQL调优?从参数、数据建模、索引、SQL语句等方向,三万字详细解读MySQL的性能优化方案(2024版)

导航&#xff1a; 本文一些内容需要聚簇索引、非聚簇索引、B树、覆盖索引、索引下推等前置概念&#xff0c;虽然本文有简单回顾&#xff0c;但详细可以参考下文的【MySQL高级篇】 【Java笔记踩坑汇总】Java基础进阶JavaWebSSMSpringBoot瑞吉外卖SpringCloud黑马旅游谷粒商城学成…

Linux——内存管理代码分析

虚空间管理 页框和页的关系 页框 将内存空间分为一个个大小相等的分区(比如:每个分区4KB),每个分区就是一个页框&#xff0c;也叫页帧&#xff0c;即物理页面&#xff0c;是linux划分内存空间的结果。 每个页框都有一个页框号&#xff0c;即内存块号、物理块号。 页 将用户…

app自动识别ios或安卓手机,微信浏览器,并下载相应的apk安装包

来源是安卓下载界面显示: 来源是IOS下载界面显示: 源码 <!DOCTYPE html> <html lang="en"><head

流水线建构apk、abb实战(二)

gradlew 命令生成apk、aab包 其实构建应用程序包就几个命令&#xff1a; ### 生成AAB&#xff1a; gradlew bundleRelease #输出到[project]/build/outputs/bundle/release/下 gradlew bundleDebug### 生成APK&#xff1a; gradlew assembleRelease gradlew assembleDebug###…

今日arXiv最热大模型论文:大模型都能怎么用?中南大学最新综述:大模型时代的自然语言处理

还记得2022年末ChatGPT的横空出世&#xff0c;带来了整个NLP乃至AI领域的震动&#xff0c;随后如LLaMA、ChatGLM、Qwen等类ChatGPT大模型&#xff08;LLM&#xff09;开始如雨后春笋般涌现&#xff0c;这些先进的模型不仅展示了在零样本学习中的出色表现&#xff0c;还在多种NL…

ToonCrafter——自动生成动画中间帧与动画上色

1、引言 动画制作对许多人来说都是一项专业且复杂的工作&#xff0c;需要学习专门的知识、掌握特定的工具&#xff0c;并投入大量的时间和精力才能获得成果。不过&#xff0c;最近推出的一款 AI 动画制作工具 ToonCrafter 有望改变这一现状。 它只需两张图像即可生成连贯流畅…

Turntin查重报告解读,如何根据颜色标准修改essay作业

留学生是学术生涯&#xff0c;撰写essay作业时是最常见的学习状态。在这样一个过程中&#xff0c;常常会遇到许多问题&#xff0c;当然&#xff0c;最需要注意便是抄袭问题。为了确定我们的essay符合标准&#xff0c;通常许多学生会选择使用Turnitin查重&#xff08;www.checkt…

神经网络搭建(1)----nn.Sequential

神经网络模型构建 采用CIFAR10中的数据&#xff0c;并对其进行简单的分类。以下图为例 输入&#xff1a;3通道&#xff0c;3232 ( 经过一个55的卷积) → 变成32通道&#xff0c;3232的图像 (经过22的最大池化) → 变成32通道&#xff0c;1616的图像 ( 经过一个55的卷积) → 变…

英伟达再创历史,市值超越苹果,跃居全球第二大上市公司

进入2024年&#xff0c;英伟达股价依然突飞猛进。 今天凌晨&#xff0c;英伟达凭借其在AI领域强劲的创新能力和市场势头&#xff0c;达成了历史性的里程碑——市值首次突破3万亿美元&#xff0c;成功超越苹果&#xff0c;成为全球市值第二大上市公司。 排名仅次于微软。 英伟达…

从零开始实现自己的串口调试助手(7) -隐藏面板/刷新串口号/实现多文本

隐藏/拓展面板/历史: ui界面操作: 选中隐藏面板按钮&#xff0c;勾选checkable 创建bool类型的槽函数 隐藏/拓展面板槽函数: void Widget::on_btnHideTable_clicked(bool checked) {if(checked){ui->btnHideTable->setText("拓展面板");ui->groupBoxTexts-…

Kotlin 网络请求小例子(Ktor)

文章目录 导入依赖创建 Http 客户端 其实还是借着 Ktor 学一学 Kotlin 如何导入依赖&#xff0c;这应该是我们 Kotlin 基础专栏的最后一期了。 Ktor 是 Kotlin 官方的一个网络请求库&#xff0c;它具有优秀且精炼的 API&#xff0c;并且是跨平台的。 本教程参考自 Ktor 文档 …

f1c100s 荔枝派 系统移植

一。交叉编译环境配置 1.1下载交叉工具链&#xff1a;https://releases.linaro.org/components/toolchain/binaries/7.2-2017.11/arm-linux-gnueabi/ 1.2解压安装 在home目录下新建 工程目录&#xff1a;mkdir f1c100s_project 将windows下的gcc-linaro-7.2.1-2017.11-x86…