封装了一个仿照抖音评论轮播效果的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 runningif (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

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

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

相关文章

软件管理、rpm安装、yum安装、源码编译安装

目录 一、Windows安装/卸载 二、软件的卸载&#xff1a; 三、Linux的软件安装和卸载 3.1rpm安装 第一步&#xff1a;挂在光盘 第二步&#xff1a;查看/mnt 第三步&#xff1a;切换到/mnt/Packages 第四步&#xff1a;安装 3.2yum安装&#xff08;使用关盘作为yum源&…

Facechain系列: constants.py文件解读

在根目录下还有个facechain目录&#xff0c;其中的constants.py文件中定义了代码控制的重要参数。 1.姿态控制 在应用代码进行推理&#xff08;见这里Facechain系列: 通过代码进行推理&#xff09;中&#xff0c;如果将以下代码 use_pose_model False 修改为 use_pose_mo…

低代码专题 | 低代码开发平台一般都有哪些功能和模块?

在上一篇文章中&#xff0c;我们已经对低代码开发平台的概念进行了初步的探讨&#xff0c;认识到了低代码开发平台提高开发效率、降低技术门槛方面的巨大潜力。 然而&#xff0c;要真正掌握并应用低代码开发平台&#xff0c;还需要深入了解其背后的功能与模块构成。这篇就对低…

【数据结构】平衡二叉树(AVL树)

目录 前言 一、AVL树概念 二、AVL树节点定义 三、AVL树插入 1. 按照二叉搜索树的方式插入新节点 2. 维护节点的平衡因子与调整树的结构 a. 新节点插入较高左子树的左侧---左左&#xff1a;右单旋 b. 新节点插入较高右子树的右侧---右右&#xff1a;左单旋 c. 新节点插入…

IIS 服务器,下载APK 文件,用于发布更新最新的APK包

IIS 默认情况下无法下载 .apk 文件&#xff0c;需要对 IIS 服务进行设置 1、打开 IIS 对应的应用 选中MIME 类型 右键 打开功能 2、右键添加 文件扩展名&#xff1a;.apk MIME 类型输入&#xff1a;application/vnd.android.package-archive 3、重启应用 4、浏览器访问 服务地…

OpenMV学习笔记4——二维码识别

一、示例程序 按照下图顺序点击&#xff0c;即可打开官方在IDE中准备好的二维码实例程序&#xff1a; # QRCode Example # # This example shows the power of the OpenMV Cam to detect QR Codes # using lens correction (see the qrcodes_with_lens_corr.py script for hig…

后端开发面经系列 -- 华为C++一面面经

HUAWEI – C一面面经 公众号&#xff1a;阿Q技术站 来源&#xff1a;https://www.nowcoder.com/feed/main/detail/b8113ff340d7444985b32a73c207c826 1、计网的协议分几层&#xff1f;分别叫什么&#xff1f; OSI七层模型 物理层 (Physical Layer): 负责物理设备之间的原始比…

苹果手机数据不见了怎么恢复?3个方法,搞定苹果手机数据恢复!

在许多错误的情况下&#xff0c;当你更新到最新的 iOS 版本或使用越狱来获得更多功能和权限、误删重要的手机文件时&#xff0c;苹果手机中的数据可能会丢失或被意外删除。一旦发现数据丢失&#xff0c;你就会查看 iTunes 备份或 iCloud 备份&#xff0c;并希望在其中恢复丢失的…

纷享销客安全体系: 组织及人员安全

组织及人员安全是纷享销客安全战略中的重要组成部分。 我们致力于确保组织内部和员工的安全&#xff0c;并采取一系列措施来预防和应对安全威胁。我们将持续改进和更新安全措施&#xff0c;以适应不断变化的威胁环境&#xff0c;并确保组织和员工的安全意识和培训得到充分关注…

inBuilder 低代码平台新特性推荐 - 第二十期

今天来给大家带来的是 inBuilder 低代码平台特性推荐系列第二十期——菜单导航模式个性化示例。 场景介绍 目前平台提供了四种菜单导航模式&#xff0c;包括分组视图、列表视图、横向视图、平铺视图&#xff0c;均为横向导航&#xff0c;这些也是主流的菜单导航模式。 在某些…

05-控制流(分支结构)

05-控制流(分支结构) 一、二路分支 程序中某一段代码需要满足一定的条件才会被执行。 if 语句&#xff1a;用于表达一种条件&#xff0c;如果条件满足则执行某个代码块。if-else 语句&#xff1a;用于表达一种条件&#xff0c;如果条件满足则执行某个代码块&#xff0c;否则…

车载以太网测试要测些什么呢?

车载以太网测试大致可以分成两块&#xff1a;TC8测试和以太网通信测试。 TC8测试全称TC8一致性测试&#xff0c;其规范由OPEN联盟制定&#xff0c;包括车载以太网ECU从物理层到应用层的各层互操作性以及常规基础功能服务。目的在于提高不同ECU之间的兼容性。 TC8测试规范可以…

差分原理+练习

差分的原理和前缀和相似&#xff0c;我们先联想一下前缀和。 前缀和计算下标从0到n的和&#xff0c;记为sum[n1];如果想要求出[l,r]区间的和&#xff0c;可以快速的通过sum[r1]-sum[l]来得到 。 前缀和适用于需要多次获取指定连续区间和的情景 而差分即计算相邻两个元素的差…

搜索与图论:树的重心

搜索与图论&#xff1a;树的重心 题目描述参考代码 题目描述 输入样例 9 1 2 1 7 1 4 2 8 2 5 4 3 3 9 4 6输出样例 4参考代码 #include <cstring> #include <iostream> #include <algorithm>using namespace std;const int N 100010, M N * 2;int n, m…

【VUE 具名插槽的应用】

具名插槽类似于提前将布局安排好&#xff0c;但内容为空&#xff0c;一旦有具体内容填充进来&#xff0c;可以很和谐的展示&#xff0c;不影响整体效果。&#x1f347; “举个&#x1f330;&#xff1a;系统里大部分页面的查询条件是相同的&#xff0c;所以需要封装一个公用的查…

如何在QGIS中加载高清卫星影像?

我们在《如何在GlobalMapper中加载高清卫星影像》一文中&#xff0c;分享了在GlobalMapper中加载卫星影像的方法。 这里再为你分享如何在QGIS中加载高清卫星影像的方法&#xff0c;并可以在文末查看领取软件安装包和图源的方法。 如何加载高清图源&#xff1f; 要在QGIS中在…

接口签名和postman预处理生成签名

nestjs后端代码 controller Get(md5hmacSHA1b64)postMd5hmacSHA1b64(Req() request: Request, Query() query) {// 获取GET请求参数const queryParamsMap new Map(Object.entries(query));return this.handleMd5hmacSHA1b64(queryParamsMap, request);}Post(md5hmacSHA1b64)U…

单点登录(SSO)前端怎么做

单点登录&#xff08;SSO&#xff09;前端怎么做 本文介绍单点登录&#xff08;SSO&#xff09;是什么&#xff0c;还有就是前端怎么做。 单点登录&#xff08;SSO&#xff09;是什么 单点登录&#xff08;SSO&#xff0c;Single Sign On&#xff09;&#xff0c;是在企业内部…

【再探】Java—Java 沙箱机制与类加载器

沙箱&#xff08;Sandbox&#xff09;机制是将Java程序限定在JVM特定的运行范围内&#xff0c;并严格限制代码对本地系统资源的访问&#xff0c;以保证代码的有效隔离&#xff0c;防止对本地系统造成破坏。 1 安全模型 类在加载过程中&#xff0c;类加载器会为类设置初始的安…