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

news2024/11/23 1:01:05

效果图

请添加图片描述

原理

就是我们在一个视图里面有两个子视图,一个是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


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

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

相关文章

【数据结构】链式二叉树详解

个人主页~ 链式二叉树基本内容~ 链式二叉树详解 1、通过前序遍历的数组来构建二叉树2、二叉树的销毁3、二叉树节点个数4、二叉树叶子节点个数5、二叉树第k层节点个数6、二叉树查找7、前序遍历8、中序遍历9、后序遍历10、层序遍历与检查二叉树是否为完全二叉树Queue.hQueue.c层序…

(echarts)图上数值显示单位

&#xff08;echarts&#xff09;图上数值显示单位 series: [{name: 比例,type: bar,...label: {show: true,position: top,formatter: (params) > params.value % //图上数值显示格式},tooltip: { //鼠标移入图上数值显示格式valueFormatter: function(value) {return val…

电源设计01

嵌入式电源设计 电池容量的计算电路充电时的选择&#xff1a; 科普硬件知识&#xff0c;写写关于电路板电源的事情。各类电源模块实物但为什么硬件工程师又必须要了解电源并且在板内自己设计电源呢&#xff1f;首先是DCDC的降压芯片下面推荐几个升压芯片&#xff1a;LDO 电池容…

Unity 自定义编辑器根据枚举值显示变量

public class Test : MonoBehaviour {[HideInInspector][Header("数量")][SerializeField]public int num;[Header("分布类型")][SerializeField]public DistributionType distType;[HideInInspector][Header("位置")][SerializeField]public Li…

数据结构 | 超详细讲解七大排序(C语言实现,含动图,多方法!)

目录 ​编辑 排序的概念 常见排序算法 ​编辑 1.冒泡排序 &#x1f379;图解 &#x1f973;代码实现 &#x1f914;时间复杂度 2.插入排序 &#x1f379;图解 &#x1f334;深度剖析 &#x1f34e;代码思路 &#x1f973;代码实现 &#x1f914;时间复杂度 3.希尔…

【深度学习-第6篇】使用python快速实现CNN多变量回归预测(使用pytorch框架)

上一篇我们讲了使用CNN进行分类的python代码&#xff1a; Mr.看海&#xff1a;【深度学习-第5篇】使用Python快速实现CNN分类&#xff08;模式识别&#xff09;任务&#xff0c;含一维、二维、三维数据演示案例&#xff08;使用pytorch框架&#xff09; 这一篇我们讲CNN的多变…

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

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

C语言 io-文件拷贝

#include <stdio.h> int main(int argc, const char *argv[]) {//1文件拷贝到2文件FILE* fileAfopen(argv[1],"r");FILE* fileBfopen(argv[2],"w");if(NULLfileA){perror("fopen");return -1;}if(NULLfileB){perror("fopen");re…

【Vue】scoped解决样式冲突

默认情况下写在组件中的样式会 全局生效 → 因此很容易造成多个组件之间的样式冲突问题。 全局样式: 默认组件中的样式会作用到全局&#xff0c;任何一个组件中都会受到此样式的影响 局部样式: 可以给组件加上scoped 属性,可以让样式只作用于当前组件 一、代码示例 BaseOne…

MYSQL ORDER BY

在MySQL中&#xff0c;默认情况下&#xff0c;升序排序会将NULL值放在前面&#xff0c;因为在排序过程中&#xff0c;NULL会被视为最小值。然而&#xff0c;有时会要求在升序排序中需要将NULL值放在最后。 例如根据日期升序时就会出现这种问题 方案一&#xff1a; SELECT sor…

Docker成功启动Rabbitmq却访问不了管理页面问题解决

目录 启动步骤&#xff1a; 无法访问问题总结&#xff1a; 启动步骤&#xff1a; 拉取镜像&#xff1a; docker pull rabbitmq 运行&#xff1a; docker run -d -p 5672:5672 -p 15672:15672 --name rabbitmq rabbitmq进入容器&#xff1a; docker exec -it 容器id /bin/…

2024.6.9周报

目录 摘要 ABSTRACT 一、文献阅读 1、相关信息 2、摘要 3、文献解读 1、Introduction 2、文章主要贡献 3、模型架构 4、实验 4、结论 二、代码实现 总结 摘要 本周我阅读了一篇题目为《Unlocking the Potential of Transformers in Time Series Forecasting with …

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

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

Linux系统之fc命令的基本使用

Linux系统之fc命令的基本使用 一、fc命令介绍1.1 fc命令简介1.2 fc命令用途 二、fc命令的帮助信息2.1 fc的man帮助2.2 fc命令的使用帮助2.3 fc命令与history命令区别 三、fc命令的基本使用3.1 显示最近执行的命令3.2 指定序号查询历史命令3.3 使用vim编辑第n条历史命令3.4 替换…

openh264 自适应量化功能源码分析

openh264 OpenH264是一个开源的H.264/AVC视频编解码器&#xff0c;由Cisco公司发起并贡献了最初的代码基础。它提供了一个用于视频编码和解码的库&#xff0c;支持H.264视频压缩标准&#xff0c;广泛应用于视频会议、流媒体和视频存储等领域。OpenH264是实现H.264编解码功能的…

关于vue2 antd 碰到的问题总结下

1.关于vue2 antd 视图更新问题 1.一种强制更新 Vue2是通过用Object…defineProperty来设置数据的getter和setter实现对数据和以及视图改变的监听的。对于数组和对象这种引用类型来说&#xff0c;getter和setter无法检测到它们内部的变化。用这种 this.$set(this.form, "…

T-Rex2: Towards Generic Object Detection via Text-Visual Prompt Synergy论文解读

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、引言二、文献综述1. Text-prompted Object Detection2. Visual-prompted Object Detection3. Interactive Object Detection 三、模型方法1. Visual-Text P…

在vscode 中使用npm的问题

当我装了 npm和nodejs后 跑项目在 文件中cmd的话可以直接运行但是在 vscode 中运行的时候就会报一下错误 解决方法就是在 vscode 中吧 power shell换成cmd 来运行就行了

JVM相关:Java内存区域

Java 虚拟机&#xff08;JVM)在执行 Java 程序的过程中会把它管理的内存划分成若干个不同的数据区域。 Java运行时数据区域是指Java虚拟机&#xff08;JVM&#xff09;在执行Java程序时&#xff0c;为了管理内存而划分的几个不同作用域。这些区域各自承担特定的任务&#xff0c…

知攻善防应急

知攻善防应急靶场一 小李在值守的过程中&#xff0c;发现有 CPU 占用飙升&#xff0c;出于胆子小&#xff0c;就立刻将服务器关机&#xff0c;并找你帮他分析&#xff0c;这是他的服务器系统&#xff0c;请你找出以下内容&#xff0c;并作为通关条件&#xff1a; 1.攻击者的 …