iOS开发-下拉刷新动画loading旋转指示器动画效果

iOS开发-下拉刷新动画loading旋转指示器动画效果

之前开发中实现下拉刷新动画loading旋转指示器动画效果

一、效果图

在这里插入图片描述

二、基础动画

CABasicAnimation类的使用方式就是基本的关键帧动画。

所谓关键帧动画,就是将Layer的属性作为KeyPath来注册,指定动画的起始帧和结束帧,然后自动计算和实现中间的过渡动画的一种动画方式。

fillMode
大意:fillMode的作用就是决定当前对象过了非active时间段的行为. 比如动画开始之前,动画结束之后

  • kCAFillModeRemoved 这个是默认值,也就是说当动画开始前和动画结束后,动画对layer都没有影响,动画结束后,layer会恢复到之前的状态
  • kCAFillModeForwards 当动画结束后,layer会一直保持着动画最后的状态
  • kCAFillModeBackwards 这个和kCAFillModeForwards是相对的,就是在动画开始前,你只要将动画加入了一个layer,layer便立即进入动画的初始状态并等待动画开始.你可以这样设定测试代码,将一个动画加入一个layer的时候延迟5秒执行.然后就会发现在动画没有开始的时候,只要动画被加入了layer,layer便处于动画初始状态
  • kCAFillModeBoth 理解了上面两个,这个就很好理解了,这个其实就是上面两个的合成.动画

可以查看

https://blog.csdn.net/gloryFlow/article/details/131991202

三、实现代码

3.1 代码实现动

主要实现CABasicAnimation动画,KeyPath是transform.rotation.z

- (CAAnimation *)rotateAnimation {
    //初始化一个动画
    CABasicAnimation *animation = [CABasicAnimation animation];
    //动画运动的方式,现在指定的是围绕Z轴旋转
    animation.keyPath = @"transform.rotation.z";
    //动画持续时间
    animation.duration = 0.5;
    
    //结束的角度
    animation.toValue = [NSNumber numberWithFloat:M_PI*2];
    //动画的运动方式
    animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
    //是否反向移动动画
    
    //重复次数
    animation.repeatCount = MAXFLOAT;

    //动画结束后的状态
    animation.fillMode = kCAFillModeBackwards;
    
    //如果为true,动画将在其活动持续时间过后从渲染树中移除。默认为“是”
    animation.removedOnCompletion = NO;

    return animation;
}

完整代码如下

#import "INRefreshRoundLoading.h"
#import "UIColor+Addition.h"

static CGFloat kRoundSize = 26.0;

@interface INRefreshRoundLoading ()

@property (nonatomic, strong) CAShapeLayer *roundLayer;
@property (nonatomic, strong) CAShapeLayer *loadingLayer;

@property (nonatomic, assign) CGFloat startAngle;
@property (nonatomic, assign) BOOL isLoading;

@end

@implementation INRefreshRoundLoading

- (instancetype)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        self.isLoading = NO;
        
        [self.layer addSublayer:self.roundLayer];
        [self.layer addSublayer:self.loadingLayer];

        [self drawContentShapeLayer];
        [self layoutSubLayersFrame];
        
        self.loadingLayer.strokeStart = 0.0;
    }
    return self;
}

- (void)layoutSubLayersFrame {
    self.roundLayer.frame = CGRectMake((CGRectGetWidth(self.bounds) - kRoundSize)/2,  (CGRectGetHeight(self.bounds) - kRoundSize)/2, kRoundSize, kRoundSize);
    self.loadingLayer.frame = CGRectMake((CGRectGetWidth(self.bounds) - kRoundSize)/2,  (CGRectGetHeight(self.bounds) - kRoundSize)/2, kRoundSize, kRoundSize);
}

- (void)displayPrecent:(CGFloat)precent {
    if (precent < 0) {
        precent = 0;
    }
    
    if (precent > 1.0) {
        precent = 1.0;
    }
    
    if (!self.isLoading) {
        self.loadingLayer.strokeStart = precent;
    }
}

- (void)startAnimation {
    self.isLoading = YES;
    
    self.loadingLayer.strokeStart = 0.7;
    [self.loadingLayer addAnimation:[self rotateAnimation] forKey:@"rotate"];
}

- (void)stopAnimation {
    self.isLoading = NO;
    [self.loadingLayer removeAnimationForKey:@"rotate"];
}

- (void)drawContentShapeLayer {
    // Drawing code
    [self drawLoadingLayer];
    [self drawRoundLayer];
}

- (CAAnimation *)rotateAnimation {
    //初始化一个动画
    CABasicAnimation *animation = [CABasicAnimation animation];
    //动画运动的方式,现在指定的是围绕Z轴旋转
    animation.keyPath = @"transform.rotation.z";
    //动画持续时间
    animation.duration = 0.5;
    
    //结束的角度
    animation.toValue = [NSNumber numberWithFloat:M_PI*2];
    //动画的运动方式
    animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
    //是否反向移动动画
    
    //重复次数
    animation.repeatCount = MAXFLOAT;

    //动画结束后的状态
    animation.fillMode = kCAFillModeBackwards;
    
    /*
 大意:fillMode的作用就是决定当前对象过了非active时间段的行为. 比如动画开始之前,动画结束之后
 kCAFillModeRemoved 这个是默认值,也就是说当动画开始前和动画结束后,动画对layer都没有影响,动画结束后,layer会恢复到之前的状态
 kCAFillModeForwards 当动画结束后,layer会一直保持着动画最后的状态
 kCAFillModeBackwards 这个和kCAFillModeForwards是相对的,就是在动画开始前,你只要将动画加入了一个layer,layer便立即进入动画的初始状态并等待动画开始.你可以这样设定测试代码,将一个动画加入一个layer的时候延迟5秒执行.然后就会发现在动画没有开始的时候,只要动画被加入了layer,layer便处于动画初始状态
 kCAFillModeBoth 理解了上面两个,这个就很好理解了,这个其实就是上面两个的合成.动画
     */
    
    //如果为true,动画将在其活动持续时间过后从渲染树中移除。默认为“是”
    animation.removedOnCompletion = NO;

    return animation;
}


#pragma mark - DrawShapeLayer
- (void)drawLoadingLayer {
    
    UIBezierPath* path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(kRoundSize/2, kRoundSize/2) radius:kRoundSize/2 startAngle:0 endAngle:M_PI*2 clockwise:YES];
    
    UIGraphicsBeginImageContext(self.loadingLayer.frame.size);
    [path stroke];
    UIGraphicsEndImageContext();
    
    self.loadingLayer.path = path.CGPath;
}

- (void)drawRoundLayer {
    
    UIBezierPath* path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(kRoundSize/2, kRoundSize/2) radius:kRoundSize/2 startAngle:0 endAngle:M_PI*2 clockwise:YES];
    
    UIGraphicsBeginImageContext(self.roundLayer.frame.size);
    [path stroke];
    UIGraphicsEndImageContext();
    
    self.roundLayer.path = path.CGPath;
}

#pragma mark - SETTER/GETTER
- (CAShapeLayer *)roundLayer {
    if (!_roundLayer) {
        _roundLayer = [CAShapeLayer layer];
        _roundLayer.backgroundColor = [UIColor clearColor].CGColor;
        //设置线条的宽度和颜色
        _roundLayer.lineWidth = 3.0f;
        _roundLayer.strokeColor = [UIColor colorWithHexString:@"dcdcdc" alpha:1.0].CGColor;
        _roundLayer.fillColor = [UIColor clearColor].CGColor;
        _roundLayer.lineCap     = kCALineCapRound;
    }
    return _roundLayer;
}

- (CAShapeLayer *)loadingLayer {
    if (!_loadingLayer) {
        _loadingLayer = [CAShapeLayer layer];
        _loadingLayer.backgroundColor = [UIColor clearColor].CGColor;
        //设置线条的宽度和颜色
        _loadingLayer.lineWidth = 3.0f;
        _loadingLayer.strokeColor = [UIColor colorWithHexString:@"ff7e48" alpha:1.0].CGColor;
        _loadingLayer.fillColor = [UIColor clearColor].CGColor;
        _loadingLayer.lineCap     = kCALineCapRound;
    }
    return _loadingLayer;
}

@end

3.2 MJRefresh使用该动画

我这里继承MJRefreshStateHeader

需要根据刷新控件的状态来执行开启动画与结束动画操作

刷新控件的状态如下

typedef NS_ENUM(NSInteger, MJRefreshState) {
    // 普通闲置状态
    MJRefreshStateIdle = 1,
    // 松开就可以进行刷新的状态
    MJRefreshStatePulling,
    // 正在刷新中的状态
    MJRefreshStateRefreshing,
    // 即将刷新的状态
    MJRefreshStateWillRefresh,
    // 所有数据加载完毕,没有更多的数据了
    MJRefreshStateNoMoreData
};

INRefreshHeader.h

#import "MJRefresh.h"
#import "INRefreshRoundLoading.h"

@interface INRefreshHeader : MJRefreshStateHeader

@property (nonatomic, assign) BOOL showInsetTop;

@property (nonatomic, strong) INRefreshRoundLoading *roundLoading;

@end

INRefreshHeader.m

@implementation INRefreshHeader

- (instancetype)initWithFrame:(CGRect)frame {
    if (self = [super initWithFrame:frame]) {
        self.lastUpdatedTimeLabel.hidden = YES;
        self.stateLabel.hidden = YES;
        
        [self addSubview:self.roundLoading];
    }
    return self;
}

- (INRefreshRoundLoading *)roundLoading {
    if (!_roundLoading) {
        _roundLoading = [[INRefreshRoundLoading alloc] initWithFrame:CGRectMake(0.0, 0.0, CGRectGetWidth([UIScreen mainScreen].bounds), self.bounds.size.height)];
    }
    return _roundLoading;



- (void)setState:(MJRefreshState)state {
    MJRefreshCheckState
    
    // 根据状态做事情
    if (state == MJRefreshStateIdle) {
        if (oldState == MJRefreshStateRefreshing) {
            self.roundLoading.alpha = 1.0;

            // 如果执行完动画发现不是idle状态,就直接返回,进入其他状态
            if (self.state != MJRefreshStateIdle) return;
            
            self.roundLoading.alpha = 1.0;

            [self.roundLoading stopAnimation];

        } else {
            [self.roundLoading stopAnimation];

        }
    } else if (state == MJRefreshStatePulling) {
        [self.roundLoading stopAnimation];

    } else if (state == MJRefreshStateRefreshing) {
        self.roundLoading.alpha = 1.0;
    }
}

- (void)prepare {
    [super prepare];
    self.mj_h = 60.0;
}

- (void)placeSubviews {
    [super placeSubviews];
    
    CGFloat centerX = self.mj_w * 0.5;
    CGFloat centerY = self.mj_h * 0.5;
    self.roundLoading.center = CGPointMake(centerX, centerY);
}

/** 当scrollView的contentOffset发生改变的时候调用 */
- (void)scrollViewContentOffsetDidChange:(NSDictionary *)change {
    [super scrollViewContentOffsetDidChange:change];
    NSLog(@"change:%@",change);
    
    CGPoint old = [change[@"old"] CGPointValue];
    CGPoint new = [change[@"new"] CGPointValue];
    
    CGFloat precent = -new.y/self.mj_h;
    [self.roundLoading displayIndicator:precent];
}

/** 当scrollView的contentSize发生改变的时候调用 */
- (void)scrollViewContentSizeDidChange:(NSDictionary *)change {
    [super scrollViewContentSizeDidChange:change];
}

/** 当scrollView的拖拽状态发生改变的时候调用 */
- (void)scrollViewPanStateDidChange:(NSDictionary *)change {
    [super scrollViewPanStateDidChange:change];
}

- (void)setShowInsetTop:(BOOL)showInsetTop {
    _showInsetTop = showInsetTop;
    
}

- (void)backInitState {
    
}

@end

3.3 具体的TableView使用

需要设置UITableView的下拉刷新操作:tableView.mj_header = header

- (void)configureRefresh {
    __weak typeof(self) weakSelf = self;
    INRefreshHeader *header = [INRefreshHeader headerWithRefreshingBlock:^{
        [weakSelf refreshData];
    }];
    
    INRefreshFooter *footer = [INRefreshFooter footerWithRefreshingBlock:^{
        [weakSelf loadMoreData];
    }];
    self.editView.tableView.mj_header = header;
    self.editView.tableView.mj_footer = footer;
}

- (void)refreshData {
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [self.editView.tableView.mj_header endRefreshing];
        [self.editView.tableView.mj_footer endRefreshing];
    });
}

- (void)loadMoreData {
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [self.editView.tableView.mj_header endRefreshing];
        [self.editView.tableView.mj_footer endRefreshing];
    });
}

四、小结

iOS开发-下拉刷新动画loading旋转指示器动画效果,使用mjrefresh一个好用的上下拉刷新的控件。实现CABasicAnimation基础效果,根据不同的mjrefresh下拉刷新操作来执行动画效果。

学习记录,每天不停进步。

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

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

相关文章

会议OA项目之权限管理个人中心(修改个人信息,选择本地图片进行头像修改)

&#x1f973;&#x1f973;Welcome Huihuis Code World ! !&#x1f973;&#x1f973; 接下来看看由辉辉所写的关于OA项目的相关操作吧 数据表及分析 表数据 表分析 所谓的权限管理就是不同的人管理不同的事&#xff0c;拥有着管理不同事情的不同权力。那么第一张表--权限表&…

docker 搭建jenkins

1、拉取镜像 docker pull jenkins/jenkins:2.4162、创建文件夹 mkdir -p /home/jenkins_mount chmod 777 /home/jenkins_mount3、运行并构建容器 docker run --restartalways -d -p 10240:8080 -p 10241:50000 -v /home/jenkins_mount:/var/jenkins_home -v /etc/localtime:…

Java 注解

对于注解 Annotation 是从 Java 1.5 开始加入&#xff0c;对于 Java 17 来说&#xff0c;主要是来自模块 java.base 下的包java.lang.annotation。该包提供了 Java 编程语言注解的类库支持。 在没有注解之前&#xff0c; Java 中大量的使用了 XML 配置文件的方式&#xff0c; …

【C#】.Net Framework框架下的Authorize权限类

2023年&#xff0c;第31周&#xff0c;第3篇文章。给自己一个目标&#xff0c;然后坚持总会有收货&#xff0c;不信你试试&#xff01; 在C#的.NET Framework中&#xff0c;你可以使用Authorize类来处理权限认证。Authorize类位于System.Web.Mvc命名空间中&#xff0c;它提供了…

JVM | 基于类加载的一次完全实践

引言 我在上篇文章&#xff1a;JVM | 类加载是怎么工作的 中为你介绍了Java的类加载器及其工作原理。我们简单回顾下&#xff1a;我用一个易于理解的类比带你逐步理解了类加载的流程和主要角色&#xff1a;引导类加载器&#xff0c;扩展类加载器和应用类加载器。并带你深入了解…

苍穹外卖-day09

苍穹外卖-day09 本项目学自黑马程序员的《苍穹外卖》项目&#xff0c;是瑞吉外卖的Plus版本 功能更多&#xff0c;更加丰富。 结合资料&#xff0c;和自己对学习过程中的一些看法和问题解决情况上传课件笔记 视频&#xff1a;https://www.bilibili.com/video/BV1TP411v7v6/?sp…

优化企业集成架构:iPaaS集成平台助力数字化转型

前言 在数字化时代全面来临之际&#xff0c;企业正面临着前所未有的挑战与机遇。技术的迅猛发展与数字化转型正在彻底颠覆各行各业的格局&#xff0c;不断推动着企业迈向新的前程。然而&#xff0c;这一数字化时代亦衍生出一系列复杂而深奥的难题&#xff1a;各异系统之间数据…

山水TW91耳机恢复出厂设置

当出现左右耳不互联或者设备无法配对时&#xff0c;可按照以下操作进行重置&#xff1a; ①先在手机端删除“SANSUI”耳机的配对信息。 ②耳机开机未连接状态下&#xff0c;连击4次左/右耳功能键后复位&#xff0c;耳机自动关机清除配对。&#xff08;提示已关机表示操作正确&a…

如何快速用Go获取短信验证码

要用Go获取短信验证码&#xff0c;通常需要连接到一个短信服务提供商的API&#xff0c;并通过该API发送请求来获取验证码。由于不同的短信服务提供商可能具有不同的API和授权方式&#xff0c;我将以一个简单的示例介绍如何使用Go语言来获取短信验证码。 在这个示例中&#xff0…

使用 OpenCV 和 GrabCut 算法进行交互式背景去除

一、说明 我想&#xff0c;任何人都可以尝试从图像中删除背景。当然&#xff0c;有大量可用的软件或工具能够做到这一点&#xff0c;但其中一些可能很昂贵。但是&#xff0c;我知道有人使用窗口绘画3D魔术选择或PowerPoint背景去除来删除背景。 如果您是计算机视觉领域的初学者…

C#时间轴曲线图形编辑器开发1-基本功能

目录 一、前言 1、简介 2、开发过程 3、工程下载链接 二、基本功能实现 1、绘图面板创建 &#xff08;1&#xff09;界面布置 &#xff08;2&#xff09;显示面板代码 &#xff08;3&#xff09; 面板水平方向、竖直方向移动功能实现 &#xff08;4&#xff09;面板放…

2024届IC秋招兆易创新数字IC后端笔试面试题

数字IC后端实现PR阶段设计导入需要哪些文件&#xff1f; 设计导入需要的文件如下图所示。这个必须熟练掌握。只要做过后端训练营项目的&#xff0c;对这个肯定是比较熟悉的。大家还要知道每个input文件的作用是什么。 在吾爱IC后端训练营Cortexa7core项目中&#xff0c;你认为…

华为刷题:HJ3明明随机数

import java.util.Scanner;// 注意类名必须为 Main, 不要有任何 package xxx 信息 public class Main {public static void main(String[] args) {Scanner scan new Scanner(System.in);int N scan.nextInt();int[] arr new int[N];for (int i 0; i < N; i) {int n sca…

数据库压力测试方法小结

一、前言 在前面的压力测试过程中&#xff0c;主要关注的是对接口以及服务器硬件性能进行压力测试&#xff0c;评估请求接口和硬件性能对服务的影响。但是对于多数Web应用来说&#xff0c;整个系统的瓶颈在于数据库。 原因很简单&#xff1a;Web应用中的其他因素&#xff0c;…

基于ChatGPT聊天的零样本信息提取7.25

基于ChatGPT聊天的零样本信息提取 摘要介绍ChatIE用于零样本IE的多轮 QA 实验总结 摘要 零样本信息提取&#xff08;IE&#xff09;旨在从未注释的文本中构建IE系统。由于很少涉及人类干预&#xff0c;因此具有挑战性。 零样本IE减少了数据标记所需的时间和工作量。最近对大型…

【Git】Git的概念安装工作区-暂存区-版本库

文章目录 Git概念-版本控制器Git安装Centos平台&ubuntu Git基本操作创建Git本地仓库配置Git 认识⼯作区、暂存区、版本库添加文件查看.git文件总结添加文件场景2 Git概念-版本控制器 引入 我们在编写各种⽂档时&#xff0c;为了防⽌⽂档丢失&#xff0c;更改失误&#xff0…

sql server表值函数

一、创建测试表 Employees 二、创建表值函数 -- DROP FUNCTION TableIntSplit;CREATE FUNCTION TableIntSplit(Text NVARCHAR(4000),Sign NVARCHAR(4000)) RETURNS tempTable TABLE(Id INT ) AS BEGIN DECLARE StartIndex INT DECLARE FindIndex INT DECLARE Content VARCHAR(…

Spring之IoC源码分析及设计思想(一)——BeanFactory

关于Spring的IOC Spring 是一个开源的 Java 平台&#xff0c;它提供了一种简化应用程序开发的框架。它是一个分层的框架&#xff0c;包括两个主要的内核&#xff1a;控制反转&#xff08;IOC&#xff09;和面向切面编程&#xff08;AOP&#xff09;。IOC 允许应用程序将组件之…

STM32MP157驱动开发——按键驱动(POLL 机制)

文章目录 “POLL ”机制&#xff1a;APP执行过程驱动使用的函数应用使用的函数pollfd结构体poll函数事件类型实现原理 poll方式的按键驱动程序(stm32mp157)gpio_key_drv.cbutton_test.cMakefile修改设备树文件编译测试 “POLL ”机制&#xff1a; 使用休眠-唤醒的方式等待某个…

BOB_1.0.1靶机详解

BOB_1.0.1靶机详解 靶机下载地址&#xff1a;https://download.vulnhub.com/bob/Bob_v1.0.1.ova 这个靶机是一个相对简单的靶机&#xff0c;很快就打完了。 找到ip地址后对IP进行一个单独的扫描&#xff0c;发现ssh端口被改到25468了&#xff0c;等会儿登陆时候需要用到。 目…