当前位置: > > > Swift - 沿路径运动的动画实现(圆形、曲线、直线等路径轨迹)

Swift - 沿路径运动的动画实现(圆形、曲线、直线等路径轨迹)

(本文代码已升级至Swfit4)

通常要实现沿着复杂路径的动画,我们可以借助关键帧动画(CAKeyframeAnimation)来实现。由于 CAKeyframeAnimation 提供了的便利的 path 属性,我们只需要对其设置相应的路径即可。

1,准备工作
首先我们在页面上添加一个橙色的方块,后面通过对这个方块添加轨迹动画来演示CAKeyframeAnimation的使用。
import UIKit

class ViewController: UIViewController {
    
    var square:UIView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        square = UIView(frame:CGRect(x:0, y:0, width:20, height:20))
        square.backgroundColor = UIColor.orange
        self.view.addSubview(square)
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
}


2,做圆周运动
下面样例中方块会沿着圆形的轨迹做圆周运动(只播放一次)
(1)通过 CGPath 的 ellipseIn 方法,我们创建一个圆形的 CGPath 作为我们的关键帧动画的 path
(2)将 calculationMode 设置为 kCAAnimationPaced,让 Core Animation 向被驱动的对象施加一个恒定速度,不管路径的各个线段有多长。
let centerX = view.bounds.size.width/2
let boundingRect = CGRect(x:centerX-75, y:50, width:150, height:150)

let orbit = CAKeyframeAnimation(keyPath:"position")
orbit.duration = 3
orbit.path = CGPath(ellipseIn: boundingRect, transform: nil)
orbit.calculationMode = kCAAnimationPaced

square.layer.add(orbit, forKey:"Move")

3,让图层保持显示动画执行后的状态
默认情况下动画执行完毕后,图形会恢复到动画执行前的状态。比如上面样例,方块移动一周后又会跑到页面坐上角位置。如果想让方块保持显示动画执行后的状态,有如下两种方式:
(1)动画下方代码直接设置方块最终位置坐标
let centerX = view.bounds.size.width/2
let boundingRect:CGRect = CGRectMake(centerX-75, 50, 150, 150)

let orbit = CAKeyframeAnimation(keyPath:"position")
orbit.duration = 3
orbit.path = CGPathCreateWithEllipseInRect(boundingRect,nil)
orbit.calculationMode = kCAAnimationPaced
square.layer.addAnimation(orbit,forKey:"Move")

//下面代码让方块运动一周后停留在终点位置(否则会回到原来的位置)
square.layer.position = CGPoint(x:centerX+75, y:125)

(2)将动画的 isRemovedOnCompletion 属性那就设置为falsefillMode 设置为 kCAFillModeForwards
let centerX = view.bounds.size.width/2
let boundingRect = CGRect(x:centerX-75, y:50, width:150, height:150)

let orbit = CAKeyframeAnimation(keyPath:"position")
orbit.duration = 3
orbit.path = CGPath(ellipseIn: boundingRect, transform: nil)
orbit.calculationMode = kCAAnimationPaced
orbit.isRemovedOnCompletion = false
orbit.fillMode = kCAFillModeForwards

square.layer.add(orbit,forKey:"Move")

4,在轨迹上重复运动

repeatCount 参数可以设置动画的重复次数,将其设为 HUGE 可以让方块圆周运动动画一直循环播放下去。
let centerX = view.bounds.size.width/2
let boundingRect = CGRect(x:centerX-75, y:50, width:150, height:150)

let orbit = CAKeyframeAnimation(keyPath:"position")
orbit.duration = 3
orbit.path = CGPath(ellipseIn: boundingRect, transform: nil)
orbit.calculationMode = kCAAnimationPaced
orbit.repeatCount = HUGE

square.layer.add(orbit, forKey:"Move")

5,让方块沿着路径旋转
设置 rotationMode 属性为 kCAAnimationRotateAuto 可以让方块沿着路径旋转,即方块不管移动到哪个位置,它始终朝向圆心。
let centerX = view.bounds.size.width/2
let boundingRect = CGRect(x:centerX-75, y:50, width:150, height:150)

let orbit = CAKeyframeAnimation(keyPath:"position")
orbit.duration = 3
orbit.path = CGPath(ellipseIn: boundingRect, transform: nil)
orbit.calculationMode = kCAAnimationPaced
orbit.repeatCount = HUGE
orbit.rotationMode = kCAAnimationRotateAuto

square.layer.add(orbit,forKey:"Move")

6,构建更加复杂的路径
通过 CGPathCreateMutable,我们可以将多条弧线、直线等拼接成一个复杂的路径,让方块在这个路径上运动。
let centerX = view.bounds.size.width/2
//创建用于转移坐标的Transform,这样我们不用按照实际显示做坐标计算
let transform = CGAffineTransform(translationX: centerX, y: 50)
let path =  CGMutablePath()
path.move(to: CGPoint(x:0 ,y:0), transform: transform)
path.addLine(to: CGPoint(x:0 ,y:75), transform: transform)
path.addLine(to: CGPoint(x:75 ,y:75), transform: transform)
path.addArc(center: CGPoint(x:0 ,y:75), radius: 75, startAngle: 0,
            endAngle: CGFloat(1.5 * .pi), clockwise: false, transform: transform)

let orbit = CAKeyframeAnimation(keyPath:"position")
orbit.duration = 3
orbit.path = path
orbit.calculationMode = kCAAnimationPaced
orbit.repeatCount = HUGE
//orbit.rotationMode = kCAAnimationRotateAuto

square.layer.add(orbit,forKey:"Move")

7,使用动画组实现多种动画的组合
通过动画组(CAAnimationGroup),我们可以将多个动画组合在一起同时播放。
下面的样例中,小方块沿着轨迹运动的同时,自身还会快速地旋转。(方块移动一周,自身会旋转100次)
//旋转动画
let rotateAnimation =  CAKeyframeAnimation(keyPath:"transform.rotation.z")
rotateAnimation.values = [0,200 * Float.pi]

//轨迹动画路径
let centerX = view.bounds.size.width/2
//创建用于转移坐标的Transform,这样我们不用按照实际显示做坐标计算
let transform:CGAffineTransform = CGAffineTransform(translationX: centerX, y: 50)
let path =  CGMutablePath()
path.move(to: CGPoint(x:0 ,y:0), transform: transform)
path.addLine(to: CGPoint(x:0 ,y:75), transform: transform)
path.addLine(to: CGPoint(x:75 ,y:75), transform: transform)
path.addArc(center: CGPoint(x:0 ,y:75), radius: 75, startAngle: 0,
            endAngle: CGFloat(1.5 * .pi), clockwise: false, transform: transform)

//轨迹动画
let orbitAnimation = CAKeyframeAnimation(keyPath:"position")
orbitAnimation.path = path
orbitAnimation.calculationMode = kCAAnimationPaced

//组合两个动画
let animationgroup =  CAAnimationGroup()
animationgroup.animations = [rotateAnimation, orbitAnimation]
animationgroup.duration = 4
animationgroup.repeatCount = HUGE
square.layer.add(animationgroup,forKey:"Move")
评论2
  • 2楼
    2017-12-04 14:37
    GDD

    如何能让运动轨迹绘制出来呢

    站长回复

    给路径加个CABasicAnimation动画就可以了。

  • 1楼
    2016-04-11 18:17
    qqqq

    很好,mark下

    站长回复

    :)