iOS动画实现方式和效果

iOS动画学习路程 :一开始学习肯定需要通过网络搜索,逐渐对动画的架构、常用API、常用效果进行全面的认识;熟悉动画的各种API和实现一些效果后,达到可以通过“搬砖+修改”来进行快速开发;

根据动画在iOS中实现的位置划分:

  • 显示层动画效果:利用UIView图层显示的效果实现各种动画,常用的效果有:位置动画、位移动画、颜色动画、淡入淡出动画、旋转动画、缩放动画、几何形状动画、另外还有关键帧动画、逐帧动画等;
  • 内容层动画效果:利用 图层实现的动画:CAEmitterCell粒子动画、CAGradient 扫描动画、CAShape 图表类动画、CAReplicator 图层快速复制动画等;
  • 3D动画效果:3D动画效果以矩阵变换为基础,利用x、y、z与变换矩阵相互作用实现各种效果;
  • 转场动画效果:常用于多视图场景下视图切换,如:水滴、翻页、波纹效果或自定义转场动画;

显示层动画

UIView中的动画都是通过修改当前UI控件的各种属性来实现的动画效果;
关键帧动画的实现与UIView的动画合集提到的动画效果有些不同,关键帧只需要设置动画的几个关键的显示帧;

显示层初级动画合集

1. 动画分析方法展示过程

动画起始阶段、动画进行阶段、动画结束阶段;
实现动画可以有两种方法:1.闭包形式;2.方法形式; 下面是简单示例:

var loginButton: UIButton?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        
        loginButton = UIButton( : CGRect(x: -400, y: 300, width: self.view. .width - 40, height: 50))
        loginButton?.backgroundColor = UIColor(red: 50/255.0, green: 185/255.0, blue: 170/255.0, alpha: 1.0)
        loginButton?.set (\"登录\", for: UIControl.State.normal)
        view.addSubview(loginButton!)
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)

        // 使用闭包形式
        UIView.animate(withDuration: 1.0) {
            self.loginButton?.  = CGRect(x: 20, y: 300, width: self.view. .width - 40, height: 50)
        }
        
        // 使用方法形式
        UIView.beginAnimations(nil, context: nil)
        UIView.setAnimationDuration(1)
        loginButton?.  = CGRect(x: 20, y: 300, width: view. .width - 40, height: 50)
        UIView.commitAnimations()
    }
2. UIView视图中常见动画的属性
  • 位置属性: bounds center :可以用于修改移动位置和拉伸收缩效果;
  • 透明度属性:alpha:实现淡入淡出效果;
  • 属性:圆形渐变、边框颜色、阴影、3D等高级动画效果;
  • transform属性:该属性继承于CGAffineTransform,是核心绘图框架与UIView之间的桥梁,可以借助实现很多高级动画效果;最常用的动画分别是缩放、旋转和位移;
3. 动画常用属性
  • setAnimationDelay
  • setAnimationDuration
  • setAnimationCurve : 设置动画加速减速效果(参数枚举:EaseInOut、EaseIn、EaseOut、Linear)
  • setAnmationsEnabled
  • setAnimationRepeatCount
  • setAnimationRepeatAutoreverses
4. 动画回调方法的使用
  • delegate回调方法
  • setAnimationDidStopSelector自定义回调方法
5. 初级动画效果合集

重点:
1.掌握UIView显示层常用动画效果合集:位置、几何形状、旋转、缩放、淡入淡出、颜色渐变等;以及CoreGraphics中的CGAffineTransform属性的了解;
2.掌握动画效果的组合使用;
3.理解抽奖转盘动画效果的实现方法;

6. 转盘效果
import UIKit

class ZhuanPanViewController: UIViewController {

    var zhuanpan: UIImageView?
    
    var rotationIndex: NSInteger = 0
    
    override func viewDidLoad() {
        super.viewDidLoad()

        view.backgroundColor = UIColor(red: 246/255.0, green: 246/255.0, blue: 246/255.0, alpha: 1.0)
        // Do any additional setup after loading the view.
        
        zhuanpan = UIImageView.init( : CGRect(origin: CGPoint(x: view.bounds.origin.x, y: view.bounds.origin.y), size: CGSize(width: 250, height: 250)))
        zhuanpan?.contentMode = UIView.ContentMode.scaleAspectFill
        zhuanpan?.center = view.center
        zhuanpan?.image = UIImage.init(named: \"zhuanpan\")
        view.addSubview(zhuanpan!)
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        
        animati ()
    }
    
    @objc func animati () {
        
        UIView.beginAnimations(nil, context: nil)
        UIView.setAnimationDelegate(self)
        UIView.setAnimationDidStop(#selector(animati ))
        UIView.setAnimationDuration(0.4)
        UIView.setAnimationCurve(UIView.AnimationCurve.linear)
        
        let angle:CGFloat = CGFloat(Double.pi/2)
        rotationIndex += 1
        
        zhuanpan?.transform = CGAffineTransform(rotationAngle: CGFloat(rotationIndex) * angle)
        
        UIView.commitAnimations()
    }
}

效果如图:
\"转盘旋转\"

显示层关键帧动画

1. 实现原理

关键帧动画主要使用在通过动画的几张关键图片描述整个动画的效果;

2. 关键帧的属性

options: 描述动画执行效果,枚举类型

  • CalculationModeLinear: 默认,动画匀速线性执行
  • CalculationModeDiscrete:
  • CalculationModeModePaced:
  • CalculationModeModeCubic:
  • CalculationModeModeCubicPaced:
3. 动画中常用关键帧的设置
import UIKit

class AnimateKeyViewController: UIViewController {

    var zhuanpan: UIImageView?
    
    override func viewDidLoad() {
        super.viewDidLoad()

        view.backgroundColor = UIColor(red: 246/255.0, green: 246/255.0, blue: 246/255.0, alpha: 1.0)
        // Do any additional setup after loading the view.
        
        zhuanpan = UIImageView.init( : CGRect(x: 100, y: 100, width: 80, height: 80))
        zhuanpan?.contentMode = UIView.ContentMode.scaleAspectFill
        zhuanpan?.image = UIImage.init(named: \"zhuanpan\")
        view.addSubview(zhuanpan!)
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        
        // 关键帧动画
        UIView.animateKey s(withDuration: 2.0, delay: 0, options: UIView.Key AnimationOptions.calculationModeLinear, animations: {
            
            // 第一帧
            UIView.addKey (withRelativeStartTime: 0, relativeDuration: 1/2, animations: {
                self.zhuanpan?.  = CGRect(x: 120, y: 120, width: 100, height:100)
            })
            
            // 第二帧
            UIView.addKey (withRelativeStartTime: 1/2, relativeDuration: 1/2, animations: {
                self.zhuanpan?.  = CGRect(x: 140, y: 140, width: 130, height: 130)
            })
            
        }) { (animation) in
            print(\"完成\")
        }
    }
}

显示层逐帧动画

1. 实现原理

逐帧动画实现的动画效果就是将图片一帧一帧的逐帧渲染;使用定时器、CADisplay 、Draw等方法实现逐帧动画;

2. 基于NSTimer和CADisplay 实现方式的区别

基于NSTimer的实现经常使用在动画帧率不高并且帧率之间的时间间隔并不是特别严格的情况;
基于CADisplay 的帧刷新频率高达每秒60帧且非常精准;
iOS设备的屏幕刷新频率默认是60Hz,而CADisplay 可以保持和屏幕刷新率相同的频率将内容渲染在屏幕上,且精度非常高。

3.CADisplay 的实现方法

CADisplay 在使用时需要注册到runloop中,每当刷帧频率达到的时候runloop回向CADisplay 指定的target发送一次指定的selector消息;

import UIKit

class NSTimerViewController: UIViewController {

    var zhuanpan: UIImageView?
    
    var index: NSInteger = 0
    
    var display : CADisplay ?
    
    override func viewDidLoad() {
        super.viewDidLoad()

        view.backgroundColor = UIColor(red: 246/255.0, green: 246/255.0, blue: 246/255.0, alpha: 1.0)
        // Do any additional setup after loading the view.
        
        zhuanpan = UIImageView.init( : CGRect(x: 100, y: 100, width: 80, height: 80))
        zhuanpan?.contentMode = UIView.ContentMode.scaleAspectFill
        zhuanpan?.image = UIImage.init(named: \"zhuanpan\")
        view.addSubview(zhuanpan!)
        
        display  = CADisplay .init(target: self, selector: #selector(refushImage))
        display ?.preferred sPerSecond = 1
        display ?.add(to: RunLoop.current, forMode: RunLoop.Mode.default)
    }
    
    @objc func refushImage() {
        zhuanpan?.image = UIImage(named: \"\\(index).png\")
        
        index += 1
        if index == 67 {
            index = 0
        }
    }
}
4.基于draw方法的逐帧动画

UIView中有一个draw方法,当创建一个新的view时,会自动生成一个draw方法,且该方法可以被重写,一旦draw方法被调用,Cocoa会创建一个图形上下文,在图形上下文中所有操作会反应在UIView界面上。按照这个思路,如果定期调用draw方法绘制新的内容,就可以实现逐帧动画效果了;
draw()方法触发的机制:1. 使用addSubview会触发layoutSubviews; 2.使用view的 属性会触发layoutSubviews; 3.直接调用setLayoutSubviews会触发layoutSubviews;
使用draw方法的时候不需要图片素材,可用性较好,这个是NSTimer或者CADisplay 所不能比的;

import UIKit

class BlackHoleViewController: UIViewController {

    var blackHole: BlackHoleView?
    
    var index: Float = 0
    var timer: Timer?
    
    override func viewDidLoad() {
        super.viewDidLoad()

        blackHole = BlackHoleView()
        blackHole?.  = UIScreen.main.bounds
        blackHole?.backgroundColor = UIColor.gray
        view.addSubview(blackHole!)
        
        timer = Timer.scheduledTimer(timeInterval: 1.0/30, target: self, selector: #selector(refreshImage), userInfo: nibName, repeats: true)
    }
    
    // 刷新图片
    @objc func refreshImage() {
        
        // 半径赋值
        blackHole?.blackHoleIncrease(index)
        
        index += 1
    }
}

// View视图
class BlackHoleView: UIView {
    
    // 黑洞半径
    var blackHoleRadius: Float = 0
    
    // 半径赋值,重绘
    func blackHoleIncrease(_ radius: Float) {
        blackHoleRadius = radius
        self.setNeedsDisplay()  // 会调用draw方法
    }
    
    override func draw(_ rect: CGRect) {
        // 获取当前绘图上下文
        let ctx: CGContext = UIGraphicsGetCurrentContext()!
        // 绘制圆形  clockwise:true:顺时针绘制 false:逆时针绘制
        ctx.addArc(center: CGPoint(x: self.center.x, y: self.center.y), radius: CGFloat(blackHoleRadius), startAngle: 0, endAngle: CGFloat(Double.pi * 2), clockwise: false)
        // 开始绘制
        ctx.fillPath()
    }
}
5.GIF动画效果

重点:掌握GIF图像的分解和合成与展示方式;
GIF图片的本质是由多帧静态图像按照动作发生的时间连续顺序组成的图片序列整体;
可以通过单帧图像合成GIF或者对GIF进行单帧分解;
GIF的合成与分解是由iOS图像处理核心框架ImageIO来对GIF文件格式进行解析,并将解析后的数据转换成一帧帧的图片输出;

分解过程
  • 本地读取GIF图,转为NSData数据类型;
  • 将NSData作为ImageIO模块的输入;
  • 获取ImageIO的输出数据: UIImage;
  • 将获取到的UIImage存储为JPG或PNG保存;
代码实现

内容层动画

内容层动画具有和显示层动画类似的初级动画效果,但除此之外,利用内容层的一些特殊属性可以实现高级效果:环形动画、圆角动画等;
重点:
掌握UIView显示层动画和CA 内容层动画的区别;
理解Core Animation核心动画架构;
掌握CA 内容层动画合集;

UIView显示层动画和CA 内容层动画的区别:

  • UIView继承UIResponder,可以处理响应事件,CA 继承NS ,只负责内容的创建和绘制;
  • UIView负责内容的管理,CA 负责内容的绘制;
  • UIView的位置属性只有: bounds center,而CA 除了这些还有anchorPoint position;
  • 通过修改CA 可以实现UIView无法实现的很多高级功能;

Core Animation为iOS核心动画,来自QuartzCore. work框架,其特点:

  • 直接作用于CA 图层,非UIView;
  • Core Animation的执行过程在后台执行,不阻塞主线程;
  • 可以利用CA 绝大多数属性制作高级动画效果;

Core Animation各种常用动画类的继承关系:
\"核心动画类结构\"
CAMediaTiming: protocol 动画公共属性类;
CAAnimation:用于实现动画的委托代理方法:动画开始和结束事件;
CAPropertyAnimation:属性动画,分为基础动画和关键帧动画;
CAAnimationGroup:组合动画;
CATransition:转场动画,用于视图控制器或者多个View之间的视图切换场景;

CABasicAnimation动画效果

位置动画:position transform.translation.x transform.translation.y transform.translation.z
缩放动画:transform.scale.x transform.scale.y
旋转动画:transform.rotation
颜色动画:backgroundColor borderColor
淡入淡出动画:opacity
高级动画: 圆角动画: cornerRadius 边框动画:borderWidth 阴影动画:shadowOffset

1.位置动画
override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        
        // 位置动画
        let animation: CABasicAnimation = CABasicAnimation()
        animation.keyPath = \"position\"
        // x坐标不变
        let positionX: CGFloat = (loginButton?. .origin.x)! + (loginButton?. .size.width)! * 0.5
        // y坐标 + 100
        let positionY: CGFloat = (loginButton?. .origin.y)! + (loginButton?. .size.height)! * 0.5 + 100
        animation.toValue = NSValue(cgPoint: CGPoint(x: positionX, y: positionY))
        animation.duration = 2.0
        animation.isRemovedOnCompletion = false
        loginButton?. .add(animation, forKey: nil)
    }
  • position属性与 换算公式:
    \"换算公式\"
  • toValue属性表明改变了控件的位置,需要给一个新的position,而byValue表明在控件原来的位置基础上,沿x y移动了多少;等同于: animation.byValue = NSValue(cgPoint: CGPoint(x: 0, y: 100)) ;
2.缩放动画
override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)

        // 缩放动画
        let animation: CABasicAnimation = CABasicAnimation()
        animation.keyPath = \"transform.scale.x\"
        animation.duration = 2.0
        animation.fromValue = 1.0
        animation.toValue = 0.8
        animation.isRemovedOnCompletion = false
        loginButton?. .add(animation, forKey: nil)
    }
3.旋转动画
override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        
        // 旋转动画
        let animation: CABasicAnimation = CABasicAnimation()
        animation.keyPath = \"transform.rotation\"
        animation.duration = 2.0
        animation.toValue = 3.14/2  // 90度
        animation.isRemovedOnCompletion = false
        loginButton?. .add(animation, forKey: nil)
    }
4.位移动画
override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        
        // 位移动画
        let animation: CABasicAnimation = CABasicAnimation()
        animation.keyPath = \"transform.translation.y\"
        animation.duration = 2.0
        animation.toValue = 100   // 向下移动100
        animation.isRemovedOnCompletion = false
        loginButton?. .add(animation, forKey: nil)
    }
5.圆角动画
override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        
        // 圆角动画
        let animation: CABasicAnimation = CABasicAnimation()
        animation.keyPath = \"cornerRadius\"
        animation.duration = 2.0
        animation.toValue = 15    // 圆角半径是15
        animation.isRemovedOnCompletion = false
        loginButton?. .add(animation, forKey: nil)
    }
6.边框动画
override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        
        loginButton?. .borderColor = UIColor.gray.cgColor
        loginButton?. .cornerRadius = 10.0
        
        // 边框动画
        let animation: CABasicAnimation = CABasicAnimation()
        animation.keyPath = \"borderWidth\"
        animation.duration = 2.0
        animation.toValue = 10
        animation.isRemovedOnCompletion = false
        loginButton?. .add(animation, forKey: nil)
    }
7.颜色渐变动画
 override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        
        loginButton?. .borderColor = UIColor.gray.cgColor
        loginButton?. .cornerRadius = 10.0
        
        // 颜色渐变动画
        let animation: CABasicAnimation = CABasicAnimation()
        animation.keyPath = \"backgroundColor\"
        animation.duration = 2.0
        animation.fromValue = UIColor.green.cgColor
        animation.toValue = UIColor.red.cgColor
        animation.isRemovedOnCompletion = false
        loginButton?. .add(animation, forKey: nil)
    }
  • animation.keyPath = “borderColor” 可设置边框渐变颜色
8.淡入淡出动画
override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)

        // 淡入淡出动画
        let animation: CABasicAnimation = CABasicAnimation()
        animation.keyPath = \"opacity\"  // opacity属性默认为0,即隐藏
        animation.duration = 2.0
        animation.fromValue = UIColor.green.cgColor
        animation.toValue = 1.0  // 显示完全
        animation.isRemovedOnCompletion = false
        loginButton?. .add(animation, forKey: nil)
    }
9.阴影渐变动画
override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)

        loginButton?. .shadowColor = UIColor.red.cgColor
        loginButton?. .shadowOpacity = 0.5  // 半透明

        // 阴影渐变动画
        let animation: CABasicAnimation = CABasicAnimation()
        animation.keyPath = \"shadowOffset\"
        animation.duration = 2.0
        animation.fromValue = UIColor.green.cgColor
        // 阴影投影角度
        animation.toValue = NSValue(cgSize: CGSize(width: 10, height: 10))
        animation.isRemovedOnCompletion = false
        loginButton?. .add(animation, forKey: nil)
    }

CAKey Animation、CAAnimation Group动画

CAKey Animation: 是CA 层下的关键帧动画类,可以实现类似于UIView的关键帧动画效果。CAKey Animation是CAPropertyAnimation的一个子类,与CABasicAnimation原理类似,都是通过修改CA 图层的value属性来实现的动画效果。不同的是CABasicAnimation一般只能用fromValue toValue byValue,也就是只能修改一个value值,但是CAKey Animation可以修改一组value值来实现对动画的精准控制。

1.CAKey Animation动画属性要点
  • values:数组类型,数组中每个元素都描述一个关键帧动画属性;
  • keyTimers:表示周期,范围0-1;
  • path:设置CGPathRef/CGMutablePathRef绘制路径;
2.CAKey Animation淡入淡出动画效果
import UIKit

class Key ViewController: UIViewController {

    var loginButton: UIButton?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        
        loginButton = UIButton( : CGRect(x: 20, y: 300, width: self.view. .width - 40, height: 50))
        loginButton?.backgroundColor = UIColor(red: 50/255.0, green: 185/255.0, blue: 170/255.0, alpha: 1.0)
        loginButton?.set (\"登录\", for: UIControl.State.normal)
        view.addSubview(loginButton!)
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        
        let animation: CAKey Animation = CAKey Animation()
        animation.duration = 10.0
        animation.keyPath = \"opacity\"
        let valuesArray: [NSNumber] = [NSNumber(value: 0.95),NSNumber(value: 0.85),NSNumber(value: 0.65),NSNumber(value: 0.45),NSNumber(value: 0.00)]
        animation.values = valuesArray
        animation.isRemovedOnCompletion = false
        loginButton?. .add(animation, forKey: nil)
    }
}
3.CAKey Animation任意路径动画
override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)

        // 可变路径对象
        let pathLine: CGMutablePath = CGMutablePath()
        pathLine.move(to: CGPoint(x: 200, y: 300))
        pathLine.addArc(center: CGPoint(x: 200, y: 200), radius: 50, startAngle: 0, endAngle: CGFloat(Double.pi), clockwise: true)
        
        // 动画
        let animation: CAKey Animation = CAKey Animation()
        animation.duration = 3.0
        animation.path = pathLine
        animation.keyPath = \"position\"
        animation.isRemovedOnCompletion = false
        
        loginButton?. .add(animation, forKey: nil)
    }
4.CAAnimation Group组合动画效果
override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)

        // 可变路径对象
        let pathLine: CGMutablePath = CGMutablePath()
        pathLine.move(to: CGPoint(x: 200, y: 300))
        pathLine.addArc(center: CGPoint(x: 200, y: 200), radius: 50, startAngle: 0, endAngle: CGFloat(Double.pi), clockwise: true)
        
        // 位置动画
        let animation: CAKey Animation = CAKey Animation()
        animation.path = pathLine
        animation.keyPath = \"position\"
        animation.isRemovedOnCompletion = false
        
        // 伸缩动画
        let animationScale: CABasicAnimation = CABasicAnimation()
        animationScale.keyPath = \"transform.scale\"
        animationScale.toValue = 0.0
        
        // 组合动画
        let animationGroup: CAAnimationGroup = CAAnimationGroup()
        animationGroup.duration = 3.0
        animationGroup.animations = [animation,animationScale]
        animationGroup.isRemovedOnCompletion = false
        
        loginButton?. .add(animationGroup, forKey: nil)
    }

实战:水纹按钮动画效果

效果实现:水纹扩散效果:扩散性状是圆形,扩散颜色为粉红色,扩散过程中按钮不可点击;

CAEnitterCell粒子动画效果

1.粒子火焰效果
2.鬼火火焰效果
3.霓虹效果

CAGradient 光波扫描动画效果

1.原理
2.指纹扫描效果
3.音响音量跳动效果

CAShape 动态图标效果

1.原理
2.贝济埃曲线
3.绘制动态图表
1.动态折线动画
2.动态柱状图动画

CAReplicator 图层复制效果

1.原理
2.恒星旋转动画效果
3.音量跳动动画效果

3D动画

3D动画概念

1.锚点的基本概念
2.矩阵变换的基本概念
3.3D旋转效果

Cover Flow 3D效果

1.原理
2.实现

转场动画

CATransition转场动画

1.概念
2.基于CATransition的图片查看器
3.转场动画key-effect

视图过渡动画

1.视图控制器过渡动画相关协议
2.视图控制器过渡动画实现
3.侧滑栏动画实现
收藏 打印