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

2024-06-06 23:12

本文主要是介绍封装了一个仿照抖音评论轮播效果的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

这篇关于封装了一个仿照抖音评论轮播效果的iOS轮播视图的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1037498

相关文章

鸿蒙中Axios数据请求的封装和配置方法

《鸿蒙中Axios数据请求的封装和配置方法》:本文主要介绍鸿蒙中Axios数据请求的封装和配置方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录1.配置权限 应用级权限和系统级权限2.配置网络请求的代码3.下载在Entry中 下载AxIOS4.封装Htt

SpringBoot中封装Cors自动配置方式

《SpringBoot中封装Cors自动配置方式》:本文主要介绍SpringBoot中封装Cors自动配置方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录SpringBoot封装Cors自动配置背景实现步骤1. 创建 GlobalCorsProperties

Spring MVC使用视图解析的问题解读

《SpringMVC使用视图解析的问题解读》:本文主要介绍SpringMVC使用视图解析的问题解读,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Spring MVC使用视图解析1. 会使用视图解析的情况2. 不会使用视图解析的情况总结Spring MVC使用视图

Java导入、导出excel用法步骤保姆级教程(附封装好的工具类)

《Java导入、导出excel用法步骤保姆级教程(附封装好的工具类)》:本文主要介绍Java导入、导出excel的相关资料,讲解了使用Java和ApachePOI库将数据导出为Excel文件,包括... 目录前言一、引入Apache POI依赖二、用法&步骤2.1 创建Excel的元素2.3 样式和字体2.

JAVA封装多线程实现的方式及原理

《JAVA封装多线程实现的方式及原理》:本文主要介绍Java中封装多线程的原理和常见方式,通过封装可以简化多线程的使用,提高安全性,并增强代码的可维护性和可扩展性,需要的朋友可以参考下... 目录前言一、封装的目标二、常见的封装方式及原理总结前言在 Java 中,封装多线程的原理主要围绕着将多线程相关的操

Vue项目的甘特图组件之dhtmlx-gantt使用教程和实现效果展示(推荐)

《Vue项目的甘特图组件之dhtmlx-gantt使用教程和实现效果展示(推荐)》文章介绍了如何使用dhtmlx-gantt组件来实现公司的甘特图需求,并提供了一个简单的Vue组件示例,文章还分享了一... 目录一、首先 npm 安装插件二、创建一个vue组件三、业务页面内 引用自定义组件:四、dhtmlx

前端原生js实现拖拽排课效果实例

《前端原生js实现拖拽排课效果实例》:本文主要介绍如何实现一个简单的课程表拖拽功能,通过HTML、CSS和JavaScript的配合,我们实现了课程项的拖拽、放置和显示功能,文中通过实例代码介绍的... 目录1. 效果展示2. 效果分析2.1 关键点2.2 实现方法3. 代码实现3.1 html部分3.2

C++实现封装的顺序表的操作与实践

《C++实现封装的顺序表的操作与实践》在程序设计中,顺序表是一种常见的线性数据结构,通常用于存储具有固定顺序的元素,与链表不同,顺序表中的元素是连续存储的,因此访问速度较快,但插入和删除操作的效率可能... 目录一、顺序表的基本概念二、顺序表类的设计1. 顺序表类的成员变量2. 构造函数和析构函数三、顺序表

Go语言利用泛型封装常见的Map操作

《Go语言利用泛型封装常见的Map操作》Go语言在1.18版本中引入了泛型,这是Go语言发展的一个重要里程碑,它极大地增强了语言的表达能力和灵活性,本文将通过泛型实现封装常见的Map操作,感... 目录什么是泛型泛型解决了什么问题Go泛型基于泛型的常见Map操作代码合集总结什么是泛型泛型是一种编程范式,允

基于Python实现PDF动画翻页效果的阅读器

《基于Python实现PDF动画翻页效果的阅读器》在这篇博客中,我们将深入分析一个基于wxPython实现的PDF阅读器程序,该程序支持加载PDF文件并显示页面内容,同时支持页面切换动画效果,文中有详... 目录全部代码代码结构初始化 UI 界面加载 PDF 文件显示 PDF 页面页面切换动画运行效果总结主