iOS开发-下拉刷新动画CAShapeLayer的strokeStart与strokeEnd指示器动画效果

iOS开发-下拉刷新动画CAShapeLayer的strokeStart与strokeEnd刷新指示器效果

之前开发中实现下拉刷新动画CAShapeLayer的strokeStart与strokeEnd指示器动画效果

一、效果图

在这里插入图片描述

二、基础动画

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

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

可以查看

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

三、实现代码

CAShapeLayer的strokeStart和strokeEnd属性
苹果官方给出这两个属性的解释为:

/* These values define the subregion of the path used to draw the
* stroked outline. The values must be in the range [0,1] with zero
* representing the start of the path and one the end. Values in
* between zero and one are interpolated linearly along the path
* length. strokeStart defaults to zero and strokeEnd to one. Both are
* animatable. */

我们可以对绘制的Path进行分区。这两个属性的值在0~1之间,0代表Path的开始位置,1代表Path的结束位置。是一种线性递增关系。strokeStart默认值为0,strokeEnd默认值为1。这两个属性都支持动画。

keyPath = strokeStart 动画的fromValue = 0,toValue = 1
表示从路径的0位置画到1 怎么画是按照清除开始的位置也就是清除0 一直清除到1 效果就是一条路径慢慢的消失

keyPath = strokeStart 动画的fromValue = 1,toValue = 0
表示从路径的1位置画到0 怎么画是按照清除开始的位置也就是1 这样开始的路径是空的(即都被清除掉了)一直清除到0 效果就是一条路径被反方向画出来

keyPath = strokeEnd 动画的fromValue = 0,toValue = 1
表示 这里我们分3个点说明动画的顺序 strokeEnd从结尾开始清除 首先整条路径先清除后2/3,接着清除1/3 效果就是正方向画出路径

keyPath = strokeEnd 动画的fromValue = 1,toValue = 0
效果就是反方向路径慢慢消失
注释: 动画的0-1(fromValue = 0,toValue = 1) 或1-0 (fromValue = 1,toValue = 0) 表示执行的方向 和路径的范围。

  • strokeStart 把一个圆先画完,然后 再慢慢减少
  • strokeEnd 从原点开始画,然后把圆画完整

这部分介绍参考https://www.jianshu.com/p/2f5d1b2f1261

我们主要实现动画效果。

3.1 代码实现动画

主要实现CABasicAnimation动画,KeyPath是strokeStart、strokeEnd

- (void)strokeAnimation {
    CABasicAnimation *strokeStartAnimation = [CABasicAnimation animationWithKeyPath:@"strokeStart"];
    strokeStartAnimation.fromValue         = @0.0;
    strokeStartAnimation.toValue           = @1.0;
    strokeStartAnimation.timingFunction    = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
    
    CABasicAnimation *strokeEndAnimation   = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
    strokeEndAnimation.fromValue           = @0.0;
    strokeEndAnimation.toValue             = @1.0;
    strokeEndAnimation.timingFunction      = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
    strokeEndAnimation.duration            = self.duration * 0.5;
    
    CAAnimationGroup *strokeAniamtionGroup = [CAAnimationGroup animation];
    strokeAniamtionGroup.duration          = self.duration;
    strokeAniamtionGroup.fillMode          = kCAFillModeForwards;
    strokeAniamtionGroup.removedOnCompletion = NO;
    
    strokeAniamtionGroup.delegate          = self;
    strokeAniamtionGroup.animations        = @[strokeEndAnimation,strokeStartAnimation];
    [self.loadingLayer addAnimation:strokeAniamtionGroup forKey:@"strokeAniamtion"];
}

完整代码如下

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

static CGFloat kCircleSize = 30.0;

@interface INRefreshCircleLoading ()<CAAnimationDelegate>

@property (nonatomic, strong) CAShapeLayer *loadingLayer;

@property (nonatomic, assign) CGFloat duration;

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

@end

@implementation INRefreshCircleLoading

- (instancetype)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        
        self.duration = 1.0;
        self.isLoading = NO;
        self.animationStoped = NO;
        
        [self.layer addSublayer:self.loadingLayer];
        
        [self layoutSubLayersFrame];
        [self drawContentShapeLayer];
    }
    return self;
}

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

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

- (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.animationStoped = NO;
    self.isLoading = YES;
    [self strokeAnimation];
}

- (void)stopAnimation {
    [self.loadingLayer removeAnimationForKey:@"strokeAniamtion"];
    self.animationStoped = YES;
    self.isLoading = NO;
    self.startAngle = 0.0;
}

- (void)strokeAnimation {
    CABasicAnimation *strokeStartAnimation = [CABasicAnimation animationWithKeyPath:@"strokeStart"];
    strokeStartAnimation.fromValue         = @0.0;
    strokeStartAnimation.toValue           = @1.0;
    strokeStartAnimation.timingFunction    = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
    
    CABasicAnimation *strokeEndAnimation   = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
    strokeEndAnimation.fromValue           = @0.0;
    strokeEndAnimation.toValue             = @1.0;
    strokeEndAnimation.timingFunction      = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
    strokeEndAnimation.duration            = self.duration * 0.5;
    
    CAAnimationGroup *strokeAniamtionGroup = [CAAnimationGroup animation];
    strokeAniamtionGroup.duration          = self.duration;
    strokeAniamtionGroup.fillMode          = kCAFillModeForwards;
    strokeAniamtionGroup.removedOnCompletion = NO;
    
    strokeAniamtionGroup.delegate          = self;
    strokeAniamtionGroup.animations        = @[strokeEndAnimation,strokeStartAnimation];
    [self.loadingLayer addAnimation:strokeAniamtionGroup forKey:@"strokeAniamtion"];
}

#pragma mark - CAAnimationDelegate
- (void)animationDidStart:(CAAnimation *)anim {
    
}

- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {
    CAAnimation *animate = [self.loadingLayer animationForKey:@"strokeAniamtion"];
    if (!(animate == anim)) {
        return;
    }
    
    if (flag) {
        if (self.animationStoped) {
            // 结束掉动画,否则出现循环
            return;
        }
        [self drawCirclelayer];
        [self strokeAnimation];
    }    
}

#pragma mark - DrawShapeLayer
- (void)drawCirclelayer {
    
    CGFloat endAngle = self.startAngle - (M_PI/180*45);
    if (endAngle < 0.0) {
        endAngle = self.startAngle + (M_PI/180*315);
    }
    
    UIBezierPath* path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(kCircleSize/2, kCircleSize/2) radius:kCircleSize/2 startAngle:self.startAngle endAngle:endAngle clockwise:YES];
    
    UIGraphicsBeginImageContext(self.loadingLayer.frame.size);
    [path stroke];
    UIGraphicsEndImageContext();
    
    self.loadingLayer.path = path.CGPath;
    
    self.startAngle = endAngle;
}

#pragma mark - SETTER/GETTER
- (CAShapeLayer *)loadingLayer {
    if (!_loadingLayer) {
        _loadingLayer = [CAShapeLayer layer];
        _loadingLayer.backgroundColor = [UIColor clearColor].CGColor;
        //设置线条的宽度和颜色
        _loadingLayer.lineWidth = 2.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 "INRefreshFourBallLoading.h"

@interface INRefreshHeader : MJRefreshStateHeader

@property (nonatomic, assign) BOOL showInsetTop;

@property (nonatomic, strong) INRefreshCircleLoading *circleLoading;

@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.circleLoading];
    }
    return self;
}

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


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

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

            [self.circleLoading stopAnimation];

        } else {
            [self.circleLoading stopAnimation];

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

    } else if (state == MJRefreshStateRefreshing) {
        self.circleLoading.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.circleLoading.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.circleLoading 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开发-下拉刷新动画CAShapeLayer的strokeStart与strokeEnd刷新指示器效果,使用mjrefresh一个好用的上下拉刷新的控件。实现CABasicAnimation基础效果,根据不同的mjrefresh下拉刷新操作来执行动画效果。

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

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

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

相关文章

【计算机网络】2.1——物理层(编码波形、奈氏准则和香农公式计算)

物理层 基本概念 概念 物理层考虑的是怎样才能在连接各种计算机的传输媒体上传输数据比特流 为数据链路层屏蔽了各种传输媒体的差异 数据链路层只需要考虑如何完成本层的协议和服务&#xff0c;而不必考虑网络具体的传输媒体是什么 物理层协议主要任务 机械特性 指明接口所…

C# 使用opencv从图片识别人脸示例

1.用chatgpt帮我写了一个示例 using System; using Emgu.CV; using Emgu.CV.CvEnum; using Emgu.CV.Structure;class Program {static void Main(string[] args){// 加载人脸分类器CascadeClassifier faceCascade new CascadeClassifier("haarcascade_frontalface_defau…

Flink任务优化分享

Flink任务优化分享 1.背景介绍 线上计算任务在某版本上线之后发现每日的任务时长都需要三个多小时才能完成&#xff0c;计算时间超过了预估时间&#xff0c;通过Dolphinscheduler的每日调度任务看&#xff0c;在数据层 dwd 的数据分段任务存在严重的性能问题&#xff0c;每天…

SQL基础使用

SQL的概述 SQL全称&#xff1a; Structured Query Language&#xff0c;结构化查询语言&#xff0c;用于访问和处理数据库的标准的计算机语言。 SQL语言1974年由Boyce和Chamberlin提出&#xff0c;并首先在IBM公司研制的关系数据库系统SystemR上实现。 经过多年发…

数值线性代数:奇异值分解SVD

本文记录计算矩阵奇异值分解SVD的原理与流程。 注1&#xff1a;限于研究水平&#xff0c;分析难免不当&#xff0c;欢迎批评指正。 零、预修 0.1 矩阵的奇异值 设列满秩矩阵&#xff0c;若的特征值为&#xff0c;则称为矩阵的奇异值。 0.2 SVD(分解)定理 设&#xff0c;则…

CTFshow-pwn入门-pwn67(nop sled空操作雪橇)

前言 本人由于今年考研可能更新的特别慢&#xff0c;不能把ctfshow的pwn入门题目的wp一一都写出来了&#xff0c;时间比较紧啊&#xff0c;只能做高数做累的时候做做pwn写写wp了&#xff0c;当然我之后只挑典型意义的题目写wp了&#xff0c;其余的题目就留到12月底考完之后再写…

基于OpenCV solvePnP函数估计头部姿势

人脸识别 文章目录 人脸识别一、姿势估计概述1、概述2、姿态估计3、在数学上表示相机运动4、姿势估计需要什么5、姿势估计算法6、Levenberg-Marquardt 优化 二、solvePnP函数1、函数原型2、参数详解 三、OpenCV源码1、源码路径 四、效果图像示例参考链接 一、姿势估计概述 1、…

寄存器分配:图着色算法

寄存器分配&#xff1a;图着色算法 背景活跃分析寄存器冲突图图着色算法溢出 背景 在编译器的中间表示中&#xff0c;一般会设定虚拟寄存器有无限多个&#xff08;方便优化&#xff09;&#xff0c;而真实的物理寄存器是有限的&#xff0c;因而编译器后端在将中间表示翻译成目…

centos7安装mysql数据库详细教程及常见问题解决

mysql数据库详细安装步骤 1.在root身份下输入执行命令&#xff1a; yum -y update 2.检查是否已经安装MySQL&#xff0c;输入以下命令并执行&#xff1a; mysql -v 如出现-bash: mysql: command not found 则说明没有安装mysql 也可以输入rpm -qa | grep -i mysql 查看是否已…

mac下安装vue cli脚手架并搭建一个简易项目

目录 1、确定本电脑下node和npm版本是否为项目所需版本。 2、下载vue脚手架 3、创建项目 1、下载node。 如果有node&#xff0c;打开终端&#xff0c;输入node -v和npm -v , 确保node和npm的版本&#xff0c;(这里可以根据自己的需求去选择&#xff0c;如果对最新版本的内容有…

python 源码中 PyId_stdout 如何定义的

python 源代码中遇到一个变量名 PyId_stdout&#xff0c;搜不到在哪里定义的&#xff0c;如下只能搜到引用的位置&#xff08;python3.8.10&#xff09;&#xff1a; 找了半天发现是用宏来构造的声明语句&#xff1a; // filepath: Include/cpython/object.h typedef struct …

Gradle build 失败后提示.lock文件,解决办法

在Gradle build失败之后时&#xff0c;有时候强制关闭AndroidStudio&#xff0c;再次打开build时&#xff0c;会提示各种.lock 文件问题&#xff0c;删除了一个还有下一个&#xff0c;而且路径不一样。 一般情况下是这两个文件夹下的lockfile影响继续build %GRADLE_HOME%/ca…

目标检测任务中常用的数据集格式(voc、coco、yolo)

一、Pascal VOC VOC数据集(Annotation的格式是xmI) Pascal VOC数据集是目标检测的常用的大规模数据集之一&#xff0c;从05年到12年都会举办比赛&#xff0c;比赛任务task&#xff1a; 分类Classification目标检测Object Detection语义分割Class Segmentation实例分割Object…

基于java+swing+mysql图书管理系统v8.0

基于javaswingmysql图书管理系统v8.0 一、系统介绍二、功能展示1.登陆及主页2.图书类别添加3.图书类别维护4.图书添加5.图书维护 三、系统实现1.BookManageMainFrame.java 四、其它1.其他系统实现 五、获取源码 一、系统介绍 该系统实现了用户登陆、图书类别管理(图书类别添加…

yolov5 onnx模型 转为 rknn模型

1、转换为rknn模型环境搭建 onnx模型需要转换为rknn模型才能在rv1126开发板上运行&#xff0c;所以需要先搭建转换环境 模型转换工具 模型转换相关文件下载&#xff1a; 网盘下载链接&#xff1a;百度网盘 请输入提取码 提取码&#xff1a;teuc 将其移动到虚拟机中&#xf…

基本排序算法

目录 一&#xff0c;插入排序 二&#xff0c;希尔排序 三&#xff0c;选择排序 四&#xff0c;冒泡排序 五&#xff0c;快排 5.1 Hoare法 5.2 挖坑法 5.3 指针法 5.4 非递归写法 六&#xff0c;归并排序 6.1 递归 6.2 非递归 一&#xff0c;插入排序 基本思想&…

CorelDraw怎么做立体字效果?CorelDraw制作漂亮的3d立体字教程

1、打开软件CorelDRAW 2019&#xff0c;用文本工具写上我们所需要的大标题。建议字体选用比较粗的适合做标题的字体。 2、给字填充颜色&#xff0c;此时填充的颜色就是以后立体字正面的颜色。我填充了红色&#xff0c;并加上了灰色的描边。 3、选中文本&#xff0c;单击界面左侧…

superset为何无法上传excel,csv等外部文件

superset为何无法上传excel&#xff0c;csv等外部文件 这是由于没有打开数据库的上传外部文件的权限 1.打开数据库连接设置&#xff0c;选择Allow file uploads to database 2.发现这里的上传链接都可以使用

c++ 类

类的引入 c 语言的结构体只能定义变量 但是 c的结构体除了定义变量之外&#xff0c;还可以定义函数。 感受感受&#xff1a; #define _CRT_SECURE_NO_WARNINGS 1//我们声明一个结构体 struct Stack {// c可以把函数写在结构体中//叫成员函数:// 如下&#xff1a;//c的写法&am…

股票回购不积极,遭分析师看空,汽车之家财务前景黯淡

来源&#xff1a;猛兽财经 作者&#xff1a;猛兽财经 第一季度财报后股价表现不佳 汽车之家&#xff08;ATHM&#xff09;于2023年5月11日公布了2023年第一季度业财报绩。 猛兽财经通过查询财报得知&#xff0c;汽车之家第一季度的实际营收为2.21亿美元&#xff0c;正常每股收…