九.核心动画 - 显式动画

news2025/5/26 11:45:01

引言

本篇博客紧接着上一篇的隐式动画开始介绍显式动画。隐式动画是创建动态页面的一种简单的直接的方式,也是UIKit的动画机制基础。但是它并不能涵盖所有的动画类型。

显式动画

接下来我们就来研究另外一种动画显式动画,它能够对一些属性做指定的动画,或者非线性动画。

显式动画 - 属性动画

给一个图层的属性添加动画时,我们需要借助CAAnimationDelegate代理,这个代理我们可以在CAAnimation的文件中找到,它有两个方法:

动画已经开始

    optional func animationDidStart(_ anim: CAAnimation)

动画已经结束

optional func animationDidStop(_ anim: CAAnimation, finished flag: Bool)

下面的例子中我们使用属性动画来修改图层的颜色,并在动画结束之后来设置图层最终颜色。

这里面有一个需要注意的地方,如果我们给单独的图层添加动画,需要在一个新的事物中来进行,并且禁用图层的默认行为,否则动画会发生两次,一次是我们设置的显式动画,一个是图层默认的隐式动画。

代码如下:

class ViewController: UIViewController, CAAnimationDelegate{

    let colorLayer = CALayer()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        colorLayer.frame = CGRect(x: 50, y: 100, width: 100, height: 100)
        colorLayer.backgroundColor = UIColor.red.cgColor
        self.view.layer.addSublayer(colorLayer)
    }
    
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        let colors = [UIColor.red.cgColor, UIColor.green.cgColor, UIColor.blue.cgColor]
        let animation = CABasicAnimation()
        animation.keyPath = "backgroundColor"
        animation.toValue = colors.randomElement()
        animation.duration = 1.0
        animation.delegate = self
        colorLayer.add(animation, forKey: nil)
    }

    func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
        if let animation = anim as? CABasicAnimation {
            CATransaction.begin()
            CATransaction.setDisableActions(true)
            let cgColor = animation.toValue
            colorLayer.backgroundColor =  (cgColor as! CGColor)
            CATransaction.commit()
        }
    }

}

动画的结束事件,是使用代理的方法来实现的,这样会有一个问题。就是假如一个控制器中有多个图层进行动画,那么所有的动画都会使用这一个回调方法,这样的话我们就需要判断是哪个图层动画的回调。

在添加动画时我们发现open func add(_ anim: CAAnimation, forKey key: String?)有一个key属性,目前设置的为nil。事实上它就是东湖的唯一标识符,我们给它设置一个非空的唯一字符串,在回调的时候就可以对所有图层进行循环,调用-animationForKey:来比对结果。

我们来修改一下代码:

        colorLayer.add(animation, forKey: "colorChange")
    func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
        if colorLayer.animation(forKey: "colorChange") == anim {
            if let animation = anim as? CABasicAnimation {
                CATransaction.begin()
                CATransaction.setDisableActions(true)
                let cgColor = animation.toValue
                colorLayer.backgroundColor =  (cgColor as! CGColor)
                CATransaction.commit()
            }
        }
    }

但是当有很多很多图层有很多很多动画的话,这个方法就显得不太优雅了。

不过我们还有更简单的版本。CAAnimation也显示了KVC协议,所以我们可以使用open func setValue(_ value: Any?, forKey key: String)和open func value(forKey key: String) -> Any?两个方法来存取属性。但是CAAnimation还有一个不同的地方,它更新时一个NSDictionary,我们可以随意设置键值对。

这就意味着我们可以对动画打任何类型的标签,下面我们就来写个例子感受一下这个特性:

class ViewController: UIViewController, CAAnimationDelegate{

    
    let firstView = UIView()
    
    let secondView = UIView()
    
    let thirdView = UIView()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        self.view.addSubview(firstView)
        firstView.backgroundColor = .red
        firstView.frame = CGRect(x: 40.0, y: self.view.bounds.size.height - 300.0, width: 40.0, height: 10.0)
        
        self.view.addSubview(secondView)
        secondView.backgroundColor = .green
        secondView.frame = CGRect(x: 80.0, y: self.view.bounds.size.height - 300.0, width: 40.0, height: 10.0)
        
        self.view.addSubview(thirdView)
        thirdView.backgroundColor = .blue
        thirdView.frame = CGRect(x: 120.0, y: self.view.bounds.size.height - 300.0, width: 40.0, height: 10.0)
        
    }
    
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        
        addAnimation(view: firstView, height: 100.0)
        addAnimation(view: secondView, height: 200.0)
        addAnimation(view: thirdView, height: 300.0)
    }
    
    func addAnimation(view:UIView,height:CGFloat) {
        let animation = CABasicAnimation()
        animation.keyPath = "bounds.size.height"
        animation.toValue = height
        animation.duration = 1.0
        animation.delegate = self
        animation.setValue(view, forKey: "view")
        view.layer.add(animation, forKey: nil)
    }

    func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
        guard let anim = anim as? CABasicAnimation else {
            return
        }
        if let view = anim.value(forKey: "view") as? UIView {
            let height = anim.toValue as? CGFloat
            view.layer.bounds.size.height = height!
        }
    }

}

点击屏幕后发现,每个动画的结果都对应到了自己的图层,效果如下:

可以做动画的属性和虚拟属性非常的多,我们就来列举一些常见的几何属性吧

  • tranform.rotation.x        按x轴旋转的弧度
  • tranform.rotation.y        按y轴旋转的弧度
  • tranform.rotation.z        按z轴旋转的弧度
  • tranform.rotation        按z轴旋转的弧度,和tranform.rotation.z效果一样
  • tranform.scale.x        在x轴按比例放大缩小
  • tranform.scale.y        在y轴按比例放大缩小
  • tranform.scale.z        在z轴按比例放大缩小
  • tranform.scale        整体按比例放大缩小
  • transform.translation.x        沿x轴平移
  • transform.translation.y        沿y轴平移
  • transform.translation.z        沿z轴平移
  • transform.translation        x,y坐标均发生改变
  • transform        CATransform3D 4xbounds4矩阵
  • bounds        layer大小
  • position        layer位置
  • anchorPoint        锚点位置
  • cornerRadius        圆角大小
  • ZPosition        Z轴位置

显式动画 - 关键帧动画

CAKeyframeAnimation是另一种UIKit没有暴露出来的且功能十分强大的类。和CABasicAnimation一样它也是继承自CAPropertyAnimation,并且它也作用于单一的一个属性,不同的是它不是只设置一个起始值和一个结束值。而是可以设置一连串随意的值来做动画。

关键帧动画起源于传动动画。它的主要思想是指主导的动画在显著改变发生时重绘当前帧,也就是关键帧。而关键帧与关键帧之间的绘制则由Core Animation通过推算来完成。

我们来使用一个例子,通过关键帧动画来修改图层的颜色,代码如下:

class ViewController: UIViewController, CAAnimationDelegate{

    let colorLayer = CALayer()
    
    override func viewDidLoad() {
        super.viewDidLoad()

        colorLayer.frame = CGRect(x: 50, y: 100, width: 100, height: 100)
        colorLayer.backgroundColor = UIColor.red.cgColor
        self.view.layer.addSublayer(colorLayer)
    }
    
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        
        let animation = CAKeyframeAnimation()
        animation.keyPath = "backgroundColor"
        animation.duration = 2.0
        animation.values = [UIColor.red.cgColor, UIColor.green.cgColor, UIColor.blue.cgColor,UIColor.yellow.cgColor]
        animation.keyTimes = [0.0, 0.25, 0.5, 0.75, 1.0]
        colorLayer.add(animation, forKey: nil)
    }

效果如下:

CAKeyframeAnimation还有另外一种方式来指定动画,就是使用CGPath。path属性可以用一种更直观的方式来描述动画,我们这次采用UIKit提供的UIBezierPath来创建path,并且为了更直观我们使用CAShapeLayer将path渲染出来。然后我们在设置CAKeyframeAnimation来创建我们的动画。

代码如下:

        let bezierPath = UIBezierPath()
        bezierPath.move(to: CGPoint(x: 50, y: 150))
        bezierPath.addCurve(to: CGPoint(x: 300, y: 150), controlPoint1: CGPoint(x: 150, y: 50), controlPoint2: CGPoint(x: 200, y: 250))
        
        let shapeLayer = CAShapeLayer()
        shapeLayer.path = bezierPath.cgPath
        shapeLayer.fillColor = UIColor.clear.cgColor
        shapeLayer.strokeColor = UIColor.red.cgColor
        shapeLayer.lineWidth = 3.0
        self.view.layer.addSublayer(shapeLayer)
        
        let layer = CALayer()
        layer.frame = CGRect(x: 50 - 40, y: 150 - 40, width: 40, height: 40)
        layer.contents = UIImage(named: "a-meiguihuahuameigui")?.cgImage
        self.view.layer.addSublayer(layer)
        
        let animation = CAKeyframeAnimation()
        animation.keyPath = "position"
        animation.path = bezierPath.cgPath
        animation.duration = 4.0
        layer.add(animation, forKey: nil)

效果如下:

还可以体验一下animation.rotationMode = .rotateAuto这个属性。

        let animation = CAKeyframeAnimation()
        animation.keyPath = "position"
        animation.path = bezierPath.cgPath
        animation.duration = 4.0
        animation.rotationMode = .rotateAuto
        layer.add(animation, forKey: nil)

你会发现,花会沿着曲线运动时会发生旋转,使自己总垂直于曲线。

结语

本篇博客介绍了两个最常用的显式动画,属性动画和关键帧动画。大家在实际开发过程中应该经常会使用到。接下来的博客我们会继续讨论显式动画,进行动画的组合,过渡动画,以及取消动画。

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

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

相关文章

北斗GPS天线使用技巧与性能对比

北斗GPS天线使用中注意的问题 多系统兼容性&#xff1a;确保天线不仅能接收北斗信号&#xff0c;还能同时接收其他GNSS系统&#xff08;如GPS、GLONASS、Galileo&#xff09;的信号&#xff0c;以提高定位精度和可靠性。 信号频率选择&#xff1a;根据应用需求选择合适的信号…

【RHCE】实验(HTTP,DNS,SELinux,firewalld的运用)

一、题目 二、主服务器配置 1.下载HTTP服务&#xff0c;DNS服务 [rootlocalhost ~]# yum install -y httpd bind 2.开启防火墙&#xff0c;放行服务 # 开启防火墙 [rootlocalhost ~]# systemctl start firewalld # 放行服务 [rootlocalhost ~]# firewall-cmd --add-service…

【linux服务器】大语言模型实战教程:LLMS大模型快速部署到个人服务器

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《C干货基地》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 引言 说到大语言模型相信大家都不会陌生&#xff0c;大型语言模型(LLMs)是人工智能文本处理的主要类型,也现在最流行的人工智能…

市场趋势的智能预测:Kompas.ai如何洞察未来市场动向

在商业领域&#xff0c;市场趋势预测是企业制定战略规划和做出明智决策的关键。准确把握市场动向能够帮助企业及时调整战略&#xff0c;抓住机遇&#xff0c;规避风险。Kompas.ai&#xff0c;一款先进的人工智能市场分析工具&#xff0c;正通过其深度学习和数据分析能力&#x…

漏洞中有三分之一具有严重且高风险

首发公众号网络研究观&#xff0c;微信搜索关注每日获取更多内容。 在大多数项目中&#xff0c;发现了低复杂度&#xff08;38%&#xff09;和中等复杂度&#xff08;50%&#xff09;的攻击向量&#xff0c;即使是低技能的黑客也可以执行这些攻击向量。 Positive Technologie…

变量和常量(局部变量和全局变量)

常变的值叫变量&#xff0c;不变的值叫常量 变量分为局部变量和全局变量 在同一范围内&#xff0c;变量只能定义一次&#xff0c;否则就会报错 全部变量和局部变量是可以同时存在的&#xff0c;不过使用的时候是局部优先 变量如果你不给他初始化&#xff0c;那么他放得就是一…

ICC2:如何检查input floating

我正在「拾陆楼」和朋友们讨论有趣的话题&#xff0c;你⼀起来吧&#xff1f; 拾陆楼知识星球入口 input floating分为两种情况&#xff1a; input没接net的情况: get_flat_pins -f "direction &#xff1d;&#xff1d;in && undefined(net)" input接net…

解决fidder小黑怪倒出JMeter文件缺失域名、请求头

解决fidder小黑怪倒出JMeter文件缺失域名、请求头 1、目录结构&#xff1a; 2、代码 coding:utf-8 Software:PyCharm Time:2024/7/10 14:02 Author:Dr.zxyimport zipfile import os import xml.etree.ElementTree as ET import re#定义信息头 headers_to_extract [Host, Conn…

springboot轻松音乐-计算机毕业设计源码48092

目 录 摘要 1 绪论 1.1研究背景与意义 1.2研究现状 1.3论文结构与章节安排 2 基于微信小程序的轻松音乐系统分析 2.1 可行性分析 2.1.1 技术可行性分析 2.1.2 经济可行性分析 2.1.3 法律可行性分析 2.2 系统功能分析 2.2.1 功能性分析 2.3 系统用例分析 2.4 系统…

将charles证书放置根文件夹目录下

一:证书导出重命名 先将charles证书导出,然后使用下列命令将charles证书文件重命名 openssl x509 -inform DER -subject_hash_old -in FiddlerRoot.cer 将文件命名为e5472ab9.0 二:文件移动 我在一台pixcel 5尝试将charles证书移动到安卓跟文件目录下,即显示文件只可读不可…

知识链:知识图谱提升大模型的推理能力,解决多个知识点结合推理时出错

知识链&#xff1a;知识图谱提升大模型的推理能力&#xff0c;解决多个知识点结合推理时出错 提出背景解法拆解逻辑链 CoK 框架hop 步骤处理肺炎疑似病例2-hop规则的应用3-hop规则的应用4-hop规则的应用CoK&#xff08;T&E&#xff09;方法的优势 CoK 算法步骤数据集构建 知…

什么是工业4.0?

在当今这个日新月异的时代&#xff0c;科技的每一次飞跃都深刻地改变着我们的生活与工作方式。而在这场全球性的变革中&#xff0c;工业4.0不仅代表了制造业的未来趋势&#xff0c;更是全球经济发展的新引擎。那么&#xff0c;究竟什么是工业4.0&#xff1f;它如何重塑我们的工…

nginx正向代理、反向代理、负载均衡

nginx.conf nginx首要处理静态页面 反向代理 动态请求 全局模块 work processes 1; 设置成服务器内核数的两倍&#xff08;一般不不超过8个超过8个反而会降低性能一般4个 1-2个也可以&#xff09; netstat -antp | grep 80 查端口号 *1、events块&#xff1a;* 配置影响ngi…

赛迪顾问ITSS服务经理发布多项2024年IT趋势报告

在深入探讨算力、工业控制系统、网络安全、数据治理、人工智能、数字化转型、5G通信等12大IT关键领域的基础上&#xff0c;赛迪顾问ITSS服务经理于1月16日以“乘势而上&#xff0c;及锋而试”为主题&#xff0c;成功举办了2024年IT趋势发布会。 会议聚焦IT行业的新技术、新模式…

p14数组(2)

数组作为函数的参数 冒泡排序 两两比较 void bubble_sort(int arr[],int sz) {int i0;for(i0;i<sz-1;i){//每一趟冒泡排序int j0;for(j0;j<sz-1-i;j){if(arr[j]>arr[j1]){int tmparr[j];arr[j]arr[j1];arr[j1]tmp;}}} } int main(){int arr[]{9,8,7,6,5,4,3,2,1,0}…

镜像加速方法

参考&#xff1a; https://github.com/DaoCloud/public-image-mirror 使用DaoCloud加速&#xff1a; 比如我想在dockerhub下载这个镜像&#xff1a; 本来的命令是&#xff1a; docker pull openjdk:11.0-jdk-slim-buster在要拉取的镜像前&#xff0c;添加前缀&#xff1a;m.…

网络安全从业人员必知的ClamAV工具(非常详细)零基础入门到精通,收藏这一篇就够了

一、引言 ClamAV&#xff08;Clam AntiVirus&#xff09;是一个开源的防病毒工具&#xff0c;广泛应用于网络安全领域。它的主要功能包括病毒扫描、恶意软件检测、以及电子邮件网关的病毒防护。ClamAV以其高效的性能和灵活的配置选项&#xff0c;成为网络安全从业人员的重要工…

护眼台灯真的护眼吗?一起看台灯的功能作用有哪些

长时间近距离用眼、长时间阅读书籍、长时间处于不良姿势下阅读或写作&#xff0c;都可能导致眼部调节过度&#xff0c;从而引起近视。近视不仅影响个人生活质量&#xff0c;还可能限制职业选择&#xff0c;甚至增加眼部疾病的风险。因此&#xff0c;从日常细节入手&#xff0c;…

IoC源码分析——singleton bean创建与循环依赖

文章目录 概要主流程bean的创建循环依赖 概要 容器初始化时&#xff0c;会创建单例bean&#xff0c;本文主要关注单例bean是如何创建的&#xff0c;并说明源码中是如何解决循环依赖的 代码入口 Testpublic void testIoC() {// ApplicationContext是容器的高级接口&#xff0c…

qcom 平台efuse机器抓取dump log的方法

引言&#xff1a; qcom 平台机器&#xff0c;一旦efuse后机器将无法抓取dump log qcom 原文&#xff1a; efuse机器抓取dump log的方法如下&#xff1a; 一、修改配置文件&#xff1a; 把kamorta_debugpolicy.xml 在配置了debugpolicy&#xff08;加入串号和打开开关&#x…