如果想在底层做一些改变,想实现一些特别的动画,这时除了学习Core Animation之外,别无选择。

最近在看《iOS Core Animation:Advanced Techniques》这本书籍,尚有所收获,并将之记录下来。

  1. CALayer
    如果将UIView说成是视图,那么CALayer就是图层了。每一个 UIView 的身后对应一个 Core Animation 框架中的 CALayer;每一个 CALayer 都是 UIView 的代理。可以尝试运行下面的代码,会发现打印是一样的内存地址:

    let testView = UIView()
    print("\(testView)")
    print("\(testView.layer.delegate!)")

    在iOS开发中,处理的一个又一个UIView,实际是在操作CALayer。那么为什么不直接对CALayer进行编程呢?那是因为CALayer继承自NSObject,主要是用于图层的处理以及动画,而UIView继承自UIResponder,可以处理交互事件。

    由此,可以认为UIView就是对CALayer的一个简单封装,图像绘制、动画都是CALayer做的。有过开发经验的朋友都知道,苹果在UIView里面封装了一套动画接口,但是利用这些接口,只是可以做一些简单、不灵活的动画。如果想在底层做一些改变,想实现一些特别的动画,这是除了学习Core Animation之外,别无选择。

    A. CALayer的简单使用
        这一部分内容开发中经常用到,可以说是基础的东西。代码:

            let testView = UIView(frame: CGRectMake(100, 100, 100, 100))
    testView.backgroundColor = UIColor.blueColor()
    self.view.addSubview(testView)
    //设置圆角
    testView.layer.cornerRadius = testView.frame.width / 2.0
    // testView.layer.masksToBounds = true //设置边框
    testView.layer.borderWidth = 5
    testView.layer.borderColor = UIColor.redColor().CGColor //设置阴影,这里需要注意,如果masksToBounds为true,阴影会被裁剪掉(所以,一般如果你既想要圆形图片,又想要阴影,那么是需要将图片进行裁剪的)
    testView.layer.shadowOpacity = 0.5
    testView.layer.shadowOffset = CGSizeMake(0, 2)
    testView.layer.shadowRadius = 5 //需要注意的是这个属性: shadowPath 它是CGPathRef类型,可以用自定义阴影样式,比如说,它默认是圆形的阴影,现在将之变成方形阴影
    let path = CGPathCreateMutable()
    CGPathAddRect(path, nil, testView.bounds)
    testView.layer.shadowPath = path //如果是oc的话,需要注意释放path,因为它是core Graphics的东西,OC的ARC机制并不会对它进行内存管理,但是Swift对它自动进行了,所以在Swift中不需要写这个代码
    // CGPathRelease(path)

    B. 自定义CALayer
        CALayer有一个contents属性,它在OC中是id类型,在Swift中是AnyObject类型,这意味着它可以是任何类型对象。但在实践中,如果给这个属性赋值的不是CGImage类型,图层会是一片空白。它的这个奇怪的现象是因为MAC OS原因造成的,在MAC OS系统上,给它赋值CGImage或者NSImage都是起作用的,但是在iOS系统,如果将UIImage赋值给它,图层只会是一片空白。(注意,视图指的是UIView,图层指的是CALayer)

    在OC中,如果直接将这么做,会产生编译错误:

        UIImage *image = [UIImage imageNamed:@"1"];
    self.view.layer.contents = image.CGImage;

    正确的写法是:

        UIImage *image = [UIImage imageNamed:@"1"];
    self.view.layer.contents = (__bridge id _Nullable)(image.CGImage);

    在OC中,事实上真正要赋值的类型应该是CGImageRef,它是一个指向CGImage的指针,而CGImageRef并不是Cocoa对象,而是一个Core Foundation类型,所以需要进行桥接转换。
        但是在Swift中没必要这么麻烦,直接用CGImage赋值即可,我猜想这是苹果在底层帮忙转换了,代码:

        func drawImageOnLayer() {
    let image = UIImage(named: "1")
    self.blueView.layer.contents = image?.CGImage
    /**
    * 相当于UIImageView的contentMode属性,可以设置图片显示样式
    kCAGravityCenter
    kCAGravityTop
    kCAGravityBottom
    kCAGravityLeft
    kCAGravityRight
    kCAGravityTopLeft
    kCAGravityTopRight
    kCAGravityBottomLeft
    kCAGravityBottomRight
    kCAGravityResize
    kCAGravityResizeAspect
    kCAGravityResizeAspectFill
    */
    //相当于UIImageView的contentMode属性,可以设置图片显示样式
    self.blueView.layer.contentsGravity = kCAGravityResizeAspect //contentsScale定义了CGImage的像素尺寸和视图大小比例,默认情况下为1.0
    //如何理解这句话,我是这么理解的,屏幕有非retina屏幕(像素和尺寸是1:1)
    //还有retina屏幕,像素和尺寸比是2:1
    //现在还出现了@3x图片,像素和尺寸比理论上是3:1,但实际上在显示的时候,苹果进行了调整(具体可以看ios9的新特性)
    //那么,像我下面这么写,就是说像素点按照屏幕来调整,如果是非retaina,那么就是1:1,如果是retina,就是2:1
    self.blueView.layer.contentsScale = UIScreen.mainScreen().scale self.blueView.layer.masksToBounds = true
    }

    其实自定义CALayer也是很简单的事情,代码如下:

        func drawCircleOnLayer() {
    let layer = CALayer()
    layer.frame = CGRectMake(50.0, 50.0, 100.0, 100.0)
    layer.backgroundColor = UIColor.blueColor().CGColor layer.delegate = self
    // layer.contentsScale = UIScreen.mainScreen().scale
    self.view.layer.addSublayer(layer) layer.display()
    } //MARK:- CALayerDelegate
    override func drawLayer(layer: CALayer, inContext ctx: CGContext) {
    CGContextSetLineWidth(ctx, 10)
    CGContextSetStrokeColorWithColor(ctx, UIColor.redColor().CGColor)
    CGContextAddEllipseInRect(ctx, layer.bounds)
    CGContextStrokePath(ctx)
    }

    上面的代码通过使用Core Graphics来绘图。CALayer有一个delegate属性,实现了CALayerDelegate协议,但是这个协议是个非正式的协议,可以看到CALayer的这个属性的声明:

    weak public var delegate: AnyObject?
    

    然后在这个方法里面绘制想要的图像,从而达到自定义CALayer的效果:

    override func drawLayer(layer: CALayer, inContext ctx: CGContext) {
    CGContextSetLineWidth(ctx, 10)
    CGContextSetStrokeColorWithColor(ctx, UIColor.redColor().CGColor)
    CGContextAddEllipseInRect(ctx, layer.bounds)
    CGContextStrokePath(ctx)
    }

    UIView有一个drawRect:方法,在这个方法里面拿到当前的CGContext,进行绘画。它的底层还是通过调用CALayer的一系列方法绘制东西。

    C. CALayer的布局

    UIView有frame、bounds、center这三个重要的布局属性,对应的CALayer也有frame、bounds、position这三个布局属性。layer的position与view的center代表的是同样的值。
        如下图的关系:
        

    anchorPoint(锚点)这个属性必须要好好说说。anchorPoint决定了view和layer的显示位置,它的取值是CGPoint(0~~1, 0~~1)。默认是为(0.5, 0.5),默认意味着它就在中点。那么,如果它不在中点呢?如下图:

    注意到,当anchorPoint发生改变的时候,frame改变了,但是position并不会发生变化。也就是说,anchorPoint决定了view和layer的位置如何显示。
        那么,在生命场合需要改变anchorPoint呢?
        
        加入现在需要做一个钟表的应用,有三根针,时针、分针、秒针,他们需要转动,转动过程中需要动画。如果anchorPoint为默认值,也就是中点的话,会如下面这样旋转:

    围绕着三根针的重点旋转,这明显不是我们所想要。但如果将anchorPoint设置为(0.5, 0.9)就不一样了,它会这么旋转:

    代码:

    import UIKit
    
    class ViewController: UIViewController {
    
        @IBOutlet weak var hourView: UIImageView!
    
        @IBOutlet weak var minuteView: UIImageView!
    
        @IBOutlet weak var secondView: UIImageView!
    
        @IBOutlet weak var imageView: UIImageView!
    var timer: NSTimer? override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view, typically from a nib.
    self.view.insertSubview(imageView, atIndex: 0)
    self.hourView.layer.anchorPoint = CGPointMake(0.5, 0.9)
    self.minuteView.layer.anchorPoint = CGPointMake(0.5, 0.9)
    self.secondView.layer.anchorPoint = CGPointMake(0.5, 0.9) self.addTimer()
    } func addTimer() {
    self.updateWithAnimate(false)
    self.timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: Selector("tick"), userInfo: nil, repeats: true)
    NSRunLoop.mainRunLoop().addTimer(self.timer!, forMode: NSRunLoopCommonModes) } func removeTimer() {
    self.timer!.invalidate()
    self.timer = nil
    } func tick() {
    self.updateWithAnimate(true)
    } @objc func updateWithAnimate(animated: Bool) {
    let calendar = NSCalendar.currentCalendar() let unit: NSCalendarUnit = [NSCalendarUnit.Hour, NSCalendarUnit.Minute, NSCalendarUnit.Second]
    let nowComponent = calendar.components(unit, fromDate: NSDate()) let hourAngle = CGFloat(nowComponent.hour) / 12.0 * CGFloat(M_PI) * 2.0
    let minuteAngle = CGFloat(nowComponent.minute) / 60.0 * CGFloat(M_PI) * 2.0
    let secondAngle = CGFloat(nowComponent.second) / 60.0 * CGFloat(M_PI) * 2.0 // print("\(hourAngle) \(minuteAngle) \(secondAngle) \(M_PI)") self.setAngle(hourAngle, forView: hourView, animated: animated)
    self.setAngle(minuteAngle, forView: minuteView, animated: animated)
    self.setAngle(secondAngle, forView: secondView, animated: animated)
    } func setAngle(angle: CGFloat, forView view: UIView, animated: Bool) {
    var transform: CATransform3D = CATransform3DMakeRotation(angle, 0, 0, 1)
    if (animated) {
    let basicAnimate = CABasicAnimation(keyPath: "transform")
    basicAnimate.toValue = NSValue(CATransform3D: transform) self.updateWithAnimate(false) basicAnimate.duration = 0.5
    basicAnimate.delegate = self
    basicAnimate.fillMode = kCAFillModeForwards
    // basicAnimate.removedOnCompletion = false
    basicAnimate.setValue(view, forKeyPath: "view")
    view.layer.addAnimation(basicAnimate, forKey: nil)
    }
    else {
    view.layer.transform = transform
    }
    } override func animationDidStop(anim: CAAnimation, finished flag: Bool) {
    // print("animationDidStop")
    let view = anim.valueForKeyPath("view") as! UIView
    let tempAnim = anim as? CABasicAnimation
    view.layer.transform = (tempAnim?.toValue?.CATransform3DValue)!
    } deinit {
    self.removeTimer()
    }
    }

    (它的github地址:https://github.com/wzpziyi1/Core-Animation-Test-One)

    这个Demo里面的所用到的动画,在后面会详细提及。在这里,主要是需要知道anchorPoint在做动画时的巨大价值。

    D. CALayer处理事件
        与UIView类似,CALayer也提供了一系列的坐标转换方法,使用这些方法,可以使得我们可以较为方便的处理layer层面的事件响应:

    public func convertPoint(p: CGPoint, fromLayer l: CALayer?) -> CGPoint
    public func convertPoint(p: CGPoint, toLayer l: CALayer?) -> CGPoint
    public func convertRect(r: CGRect, fromLayer l: CALayer?) -> CGRect
    public func convertRect(r: CGRect, toLayer l: CALayer?) -> CGRect

    需要着重提下hitTest:方法,它使得layer可以进行事件处理,这么说吧,正是因为它的存在,才可以判断,一个点击或者触摸等事件是发生在哪个layer上面。示例代码如下:

    import UIKit
    
    class ViewController: UIViewController, UIAlertViewDelegate {
    
        weak private var redLayer: CALayer!
    weak private var blueLayer: CALayer! override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view, typically from a nib. let redLayer = CALayer()
    redLayer.frame = CGRectMake(100, 50, 100, 100)
    redLayer.backgroundColor = UIColor.redColor().CGColor
    self.view.layer.addSublayer(redLayer)
    self.redLayer = redLayer let blueLayer = CALayer()
    blueLayer.frame = CGRectMake(100, 200, 100, 100)
    blueLayer.backgroundColor = UIColor.blueColor().CGColor
    self.view.layer.addSublayer(blueLayer)
    self.blueLayer = blueLayer
    } override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
    let touch = touches.first
    let point = touch?.locationInView(self.view) let layer = self.view.layer.hitTest(point!) if (layer == self.redLayer) {
    let alertView = UIAlertView(title: "点击了redLayer", message: "", delegate: self, cancelButtonTitle: nil, otherButtonTitles: "确定")
    alertView.show()
    }
    else if (layer == self.blueLayer) {
    let alertView = UIAlertView(title: "点击了blueLayer", message: "", delegate: self, cancelButtonTitle: nil, otherButtonTitles: "确定")
    alertView.show()
    }
    } }
  2. 动画的基础知识

    这里会说一些动画轨迹的具体实现,补充UIBezierPath的使用。如果想要你的动画沿着所写的曲线运动,那么这些基础知识是不可缺少的。

    A. UIBezierPath
        如果熟悉Core Graphics,那么就可以很快掌握它的用法,实际上它就是对Core Graphics的一个封装。示例代码:

    import UIKit
    
    class ZYView: UIView {
    
        override func drawRect(rect: CGRect) {
    
            let path = UIBezierPath()
    path.addArcWithCenter(CGPointMake(150, 150), radius: 50, startAngle: 0, endAngle: CGFloat(M_PI * 2.0), clockwise: true)
    path.lineWidth = 5
    UIColor.blueColor().setStroke()
    UIColor.redColor().setFill()
    path.stroke()
    path.fill()
    } }
        override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view, typically from a nib.
    // redView.backgroundColor = UIColor.redColor()
    // self.view.addSubview(redView) self.view.addSubview(ZYView(frame: CGRectMake(100, 100, 300, 300)))
    }

    可以得到下面的界面:

    那么,现在加入有一个需求,一张图片想要绕着圆转一圈的动画,如何实现?其实只需要定义好运动的轨迹,然后告诉animate给我按照这个轨迹进行运动即可。代码:

    import UIKit
    
    class ViewController: UIViewController {
    
        var redView: UIView = UIView(frame: CGRectMake(40, 200, 50, 50))
    
        override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view, typically from a nib.
    redView.backgroundColor = UIColor.redColor()
    self.view.addSubview(redView)
    } override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
    self.animate1()
    } func animate1() { redView.layer.cornerRadius = redView.frame.width / 2
    redView.layer.anchorPoint = CGPointMake(0.5, 0.5) let keyFrameAnimate = CAKeyframeAnimation(keyPath: "position") let path = UIBezierPath()
    path.addArcWithCenter(CGPointMake(150, 150), radius: 100, startAngle: 0, endAngle: CGFloat(M_PI * 2.0), clockwise: true)
    keyFrameAnimate.path = path.CGPath keyFrameAnimate.duration = 3
    keyFrameAnimate.fillMode = kCAFillModeForwards
    keyFrameAnimate.delegate = self
    keyFrameAnimate.removedOnCompletion = false
    keyFrameAnimate.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn) self.redView.layer.addAnimation(keyFrameAnimate, forKey: "aaa")
    } }

    可以看到这样的代码:

            let path = UIBezierPath()
    path.addArcWithCenter(CGPointMake(150, 150), radius: 100, startAngle: 0, endAngle: CGFloat(M_PI * 2.0), clockwise: true)
    keyFrameAnimate.path = path.CGPath

    正是上面提到的,给animate提供了一条运动轨迹。
         类似的,沿着圆运动的轨迹已经可以定义了,那么沿着四边形运动的轨迹也是相似的。那么,沿着曲线运动的轨迹如何定义呢?

    B. 贝塞尔曲线
        如果你想要定义曲线运动的轨迹,那么这个知识点是必须学习的。

    这是我看到的一份总结贝塞尔曲线很好的博客:http://blog.csdn.net/guo_hongjun1611/article/details/7842110

    下面的关于曲线的总结也是转载自上面的博客。

    Bézier curve(贝塞尔曲线)是应用于二维图形应用程序的数学曲线。 曲线定义:起始点、终止点(也称锚点)、控制点。通过调整控制点,贝塞尔曲线的形状会发生变化。 1962年,法国数学家Pierre Bézier第一个研究了这种矢量绘制曲线的方法,并给出了详细的计算公式,因此按照这样的公式绘制出来的曲线就用他的姓氏来命名,称为贝塞尔曲线。

    以下公式中:B(t)为t时间下 点的坐标;

    P0为起点,Pn为终点,Pi为控制点
    一阶贝塞尔曲线:

    意义:由 P0 至 P1 的连续点, 描述的一条线段

    二阶贝塞尔曲线:

    原理:由 P0 至 P1 的连续点 Q0,描述一条线段。 
          由 P1 至 P2 的连续点 Q1,描述一条线段。 
          由 Q0 至 Q1 的连续点 B(t),描述一条二次贝塞尔曲线。
          经验:P1-P0为曲线在P0处的切线。

    三阶贝塞尔曲线:

  3. CGAffineTransform与CATransform3D
    已经知道了运动轨迹的定义,那么该如何让layer沿着动画轨迹运动呢,如果在运动过程中,还有能缩放、旋转呢?

    A. CGAffineTransform
        从CG就可以看出它是属于Core Graphics的东西,实际上UIView的transform属性就是CGAffineTransform类型,用它可以做二维平面上的缩放、旋转、平移。

        下面的这几个函数都创建了一个CGAffineTransform实例:

            //平移,输入需要平移的x,y值即可
    public func CGAffineTransformMakeTranslation(tx: CGFloat, _ ty: CGFloat) -> CGAffineTransform //缩放x方向缩放的倍数,y方向的缩放倍数,默认都为1
    public func CGAffineTransformMakeScale(sx: CGFloat, _ sy: CGFloat) -> CGAffineTransform //旋转,angle放弧度
    public func CGAffineTransformMakeRotation(angle: CGFloat) -> CGAffineTransform

    当然还有一些其他的接口,并不都一一写出,下面是一份简单的实例代码,一边缩放、一边平移:

    import UIKit
    
    class ViewController: UIViewController {
    
        @IBOutlet weak var redView: UIView!
    
        override func viewDidLoad() {
    super.viewDidLoad() } override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
    self.transformTest2D()
    } func transformTest2D() {
    UIView.animateWithDuration(2) { () -> Void in let transform = CGAffineTransformMakeScale(2, 2) //注意,本意只是想平移200的,但是由于是先缩放后平移的,如果不除以缩放系数的话,会达不到本意
    self.redView.transform = CGAffineTransformTranslate(transform, 200 / 2, 200 / 2)
    }
    }
    }

    B. CATransform3D
        它可以做到让图层在三维空间内平移、旋转等。
        CATransform3D是一个4*4的矩阵:

        与CGAffineTransform类似,它也提供了一系列的接口让我们得以实例化一个CATransform3D实例:

            public func CATransform3DMakeTranslation(tx: CGFloat, _ ty: CGFloat, _ tz: CGFloat) -> CATransform3D
    
            public func CATransform3DMakeScale(sx: CGFloat, _ sy: CGFloat, _ sz: CGFloat) -> CATransform3D
    
            public func CATransform3DMakeRotation(angle: CGFloat, _ x: CGFloat, _ y: CGFloat, _ z: CGFloat) -> CATransform3D
    

    多了一个z值。同样的,它也提一系列用来做混合动画的接口:

            public func CATransform3DTranslate(t: CATransform3D, _ tx: CGFloat, _ ty: CGFloat, _ tz: CGFloat) -> CATransform3D
    
            public func CATransform3DScale(t: CATransform3D, _ sx: CGFloat, _ sy: CGFloat, _ sz: CGFloat) -> CATransform3D
    
            @available(iOS 2.0, *)
    public func CATransform3DRotate(t: CATransform3D, _ angle: CGFloat, _ x: CGFloat, _ y: CGFloat, _ z: CGFloat) -> CATransform3D

    示例代码:

    import UIKit
    
    class ViewController: UIViewController {
    
        @IBOutlet weak var redView: UIView!
    
        @IBOutlet weak var imageView1: UIImageView!
    
        @IBOutlet weak var imageView2: UIImageView!
    
        override func viewDidLoad() {
    super.viewDidLoad() } override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
    self.transformTest3D()
    } func transformTest3D() { UIView.animateWithDuration(2, animations: { () -> Void in
    //绕Y轴旋转四分之三PI
    self.imageView1.layer.transform = CATransform3DMakeRotation(CGFloat(M_PI * 3.0 / 4.0), 0, 1, 0)
    //绕Y轴旋转四分之一PI
    self.imageView2.layer.transform = CATransform3DMakeRotation(-CGFloat(M_PI_4), 0, 1, 0)
    }) { (finish: Bool) -> Void in
    dispatch_after(dispatch_time_t(dispatch_time(DISPATCH_TIME_NOW, (Int64)(2 * NSEC_PER_SEC))), dispatch_get_main_queue(), { () -> Void in //旋转完成2s之后,将他们各自的transform恢复原始值
    //需要注意,CGAffineTransform也有这个CGAffineTransformIdentity,前面忘说了,效果是一样的
    self.imageView1.layer.transform = CATransform3DIdentity
    self.imageView2.layer.transform = CATransform3DIdentity
    })
    }
    }
    }

    CATransform3D还有一个很有意思的m34属性,我们通过调整这个属性可以做一些很nice的视觉。恩,就是通过设置它的值,可以做出一些很好的透视效果。m34的默认值是0,我们可以设置m34的值为(-1/d)来做相应的透视效果,d的值一般在500.0~~1000.0之间效果就很不错了,用它写了一个打开门的动画,设置m34与不设置m34的区别简直就是天差地别。

    代码(代码太少了,上传到github实在浪费时间,我的建议是,百度一张门的图片,然后尝试注释掉m34的设置与不注释m34的视觉区别):

    #import "ViewController.h"
    
    @interface ViewController ()
    @property (nonatomic, strong) CALayer *doorLayer;
    @end @implementation ViewController - (void)viewDidLoad
    {
    [super viewDidLoad];
    self.doorLayer = [CALayer layer];
    self.doorLayer.frame = CGRectMake(0, 0, 128, 256);
    self.doorLayer.position = CGPointMake(150 - 64, 300);
    self.doorLayer.anchorPoint = CGPointMake(0, 0.5);
    self.doorLayer.contents = (__bridge id)[UIImage imageNamed:@"Door.png"].CGImage;
    [self.view.layer addSublayer:self.doorLayer];
    CATransform3D perspective = CATransform3DIdentity; //设置m34,可以尝试注释掉,看看与不注销的视觉差距
    perspective.m34 = -1.0 / 500.0;
    self.view.layer.sublayerTransform = perspective; UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] init];
    [pan addTarget:self action:@selector(pan:)];
    [self.view addGestureRecognizer:pan]; self.doorLayer.speed = 0.0; CABasicAnimation *animation = [CABasicAnimation animation];
    animation.keyPath = @"transform";
    animation.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeRotation(-M_PI_2, 0, 1, 0)];
    animation.duration = 1.0;
    [self.doorLayer addAnimation:animation forKey:nil];
    } - (void)pan:(UIPanGestureRecognizer *)pan
    { CGFloat x = [pan translationInView:self.view].x; x /= 200.0f; CFTimeInterval timeOffset = self.doorLayer.timeOffset;
    timeOffset = MIN(0.999, MAX(0.0, timeOffset - x));
    self.doorLayer.timeOffset = timeOffset; [pan setTranslation:CGPointZero inView:self.view];
    }
    @end

    oc代码,没有改写成Swift代码,这是可以手势控制门打开或者关闭动画的代码。
    图片:



      动画在学习动画之前,有一副图是必须提到的:

        CAAnimation的继承结构。而我这次所深入学习的是CABasicAnimation、CAKeyFrameAnimation、CAAnimationGroup、CATransition。
        CAAnimation:
        所有动画对象的父类,负责控制动画的持续时间和速度,是个抽象类,不能直接使用,应该使用它具体的子类
        duration:动画的持续时间
        repeatCount:动画的重复次数
        repeatDuration:动画的重复时间
        removedOnCompletion:默认为YES,代表动画执行完毕后就从图层上移除,图形会恢复到动画执行前的状态。如果想让图层保持显示动画执行后的状态,那就设置为NO,不过还要设置fillMode为kCAFillModeForwards
        fillMode:决定当前对象在非active时间段的行为.比如动画开始之前,动画结束之后
        beginTime:可以用来设置动画延迟执行时间,若想延迟2s,就设置为CACurrentMediaTime()+2,CACurrentMediaTime()为图层的当前时间
        timingFunction:速度控制函数,控制动画运行的节奏
        delegate:动画代理
        keyPath: 通过指定CALayer的一个属性名称达到相应的动画效果,比如说,指定"position"为keyPath,就修改CALayer的position属性值,以达到平移的动画效果
        A.  CABasicAnimation
CAPropertyAnimation的子类
属性解析:
fromValue:keyPath相应属性的初始值
toValue:keyPath相应属性的结束值
随着动画的进行,在长度为duration的持续时间内,keyPath相应属性的值从fromValue渐渐地变为toValue
代码:
import UIKit

class ViewController: UIViewController {

    weak private var contentView: UIView!

    override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib. let view = UIView(frame: CGRectMake(100, 100, 100, 100))
view.backgroundColor = UIColor.redColor()
self.view.addSubview(view)
self.contentView = view
self.contentView.layer.anchorPoint = CGPointZero
} override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
self.animateOfTransform3D()
} //3D旋转
func animateOfTransform3D() {
let animate = CABasicAnimation(keyPath: "transform") animate.toValue = NSValue(CATransform3D: CATransform3DMakeRotation(CGFloat(M_PI_4), 0, 1, 0)) animate.duration = 2 animate.removedOnCompletion = false animate.fillMode = kCAFillModeForwards animate.delegate = self self.contentView.layer.addAnimation(animate, forKey: "aaa")
} //缩放动画
func animateOfScale() {
let animate = CABasicAnimation(keyPath: "bounds") animate.toValue = NSValue(CGRect: CGRectMake(100, 100, 200, 200)) animate.duration = 2 animate.removedOnCompletion = false animate.fillMode = kCAFillModeForwards animate.delegate = self self.contentView.layer.addAnimation(animate, forKey: "bcd")
} //平移动画
func animateOfTranslation() {
//keyPath放需要执行怎么样的动画
let animate = CABasicAnimation(keyPath: "position") //layer从哪里来
animate.fromValue = NSValue(CGPoint: CGPointMake(100, 100))
//到哪去
animate.toValue = NSValue(CGPoint: CGPointMake(200, 200)) // 在当前位置的基础上增加多少
// animate.byValue = [NSValue valueWithCGPoint:CGPointMake(0, 300)]; animate.duration = 1
//设置动画执行完毕之后不删除动画
animate.removedOnCompletion = false //设置保存动画的最新状态
animate.fillMode = kCAFillModeForwards animate.delegate = self self.contentView.layer.addAnimation(animate, forKey: "abc")
} override func animationDidStart(anim: CAAnimation) {
print("animationDidStart")
} override func animationDidStop(anim: CAAnimation, finished flag: Bool) {
print("animationDidStop")
}
}

B. CAKeyFrameAnimation

CApropertyAnimation的子类,跟CABasicAnimation的区别是:CABasicAnimation只能从一个数值(fromValue)变到另一个数值(toValue),而CAKeyframeAnimation会使用一个NSArray保存这些数值
属性解析:
values:就是上述的NSArray对象。里面的元素称为”关键帧”(keyframe)。动画对象会在指定的时间(duration)内,依次显示values数组中的每一个关键帧
path:可以设置一个CGPathRef\CGMutablePathRef,让层跟着路径移动。path只对CALayer的anchorPoint和position起作用。如果你设置了path,那么values将被忽略
keyTimes:可以为对应的关键帧指定对应的时间点,其取值范围为0到1.0,keyTimes中的每一个时间值都对应values中的每一帧.当keyTimes没有设置的时候,各个关键帧的时间是平分的
CABasicAnimation可看做是最多只有2个关键帧的CAKeyframeAnimation
 
代码:

import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var redView: UIView!

    override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib. // self.redView.layer.anchorPoint = CGPointZero
} override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
self.animateTest2()
} @IBAction func clickBtn(sender: AnyObject) {
self.redView.layer.removeAnimationForKey("aaa")
} //无限摇摆
func animateTest2() {
// self.redView.layer.anchorPoint = CGPointMake(self.redView.frame.width / 2, self.redView.frame.height / 2)
let keyAnimate = CAKeyframeAnimation(keyPath: "transform.rotation") keyAnimate.values = [ -2.14 / 20, 2.14 / 20, -2.14 / 20]; keyAnimate.removedOnCompletion = false
keyAnimate.fillMode = kCAFillModeForwards keyAnimate.duration = 0.05 keyAnimate.delegate = self
keyAnimate.repeatCount = Float(CGFloat.max)
self.redView.layer.addAnimation(keyAnimate, forKey: "aaa")
} //围绕一个圆进行动画
func animateTest1() {
let keyAnimate = CAKeyframeAnimation(keyPath: "position") var path = CGPathCreateMutable()
CGPathAddEllipseInRect(path, nil, CGRectMake(0, 100, 200, 200))
keyAnimate.path = path //设置动画的进度快慢,可以先快后慢,先慢后快等
keyAnimate.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) keyAnimate.removedOnCompletion = false
keyAnimate.fillMode = kCAFillModeForwards keyAnimate.duration = 2 keyAnimate.delegate = self self.redView.layer.addAnimation(keyAnimate, forKey: "aaa")
} func animateTest() {
let keyAnimate = CAKeyframeAnimation(keyPath: "position")
let v1 = NSValue(CGPoint: CGPointMake(self.redView.frame.origin.x, self.redView.frame.origin.y))
let v2 = NSValue(CGPoint: CGPointMake(100, 200))
let v3 = NSValue(CGPoint: CGPointMake(0, 200))
let v4 = NSValue(CGPoint: CGPointMake(0, 100))
let v5 = NSValue(CGPoint: CGPointMake(self.redView.frame.origin.x, self.redView.frame.origin.y))
keyAnimate.values = [v1, v2, v3, v4, v5] //设置动画的进度快慢,可以先快后慢,先慢后快等
keyAnimate.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) keyAnimate.removedOnCompletion = false
keyAnimate.fillMode = kCAFillModeForwards keyAnimate.duration = 2 keyAnimate.delegate = self self.redView.layer.addAnimation(keyAnimate, forKey: "aaa")
} override func animationDidStart(anim: CAAnimation) {
print("animationDidStart")
} override func animationDidStop(anim: CAAnimation, finished flag: Bool) {
print("animationDidStop")
}
}

前面提到了,沿着贝塞尔曲线的动画:

    func animateTestThree() {

        let path = UIBezierPath()
path.moveToPoint(CGPointMake(0, 150)) //这是二次贝塞尔曲线的接口,利用此接口,可以定义一条贝塞尔曲线轨迹
path.addCurveToPoint(CGPointMake(400, 150), controlPoint1: CGPointMake(75, 0), controlPoint2: CGPointMake(225, 300)) let plane = UIImageView(frame: CGRectMake(0, 0, 85, 60))
//这里可以放一张飞机图片
plane.image = UIImage(named: "F2005071315255000000")
plane.center = CGPointMake(0, 150)
plane.layer.anchorPoint = CGPointMake(0.5, 0.5)
self.view.addSubview(plane) let animate = CAKeyframeAnimation(keyPath: "position")
animate.duration = 4
animate.path = path.CGPath
animate.fillMode = kCAFillModeForwards
animate.removedOnCompletion = false
animate.delegate = self //设置此属性,可以使得飞机在飞行时自动调整角度
animate.rotationMode = kCAAnimationRotateAuto // animate.setValue(plane.layer, forKey: "plane.layer")
plane.layer.addAnimation(animate, forKey: "animateTestThree")
}

图片:

 
C. CAAnimationGroup
CAAnimation的子类,可以保存一组动画对象,将CAAnimationGroup对象加入层后,组中所有动画对象可以同时并发运行
属性解析:
animations:用来保存一组动画对象的NSArray
默认情况下,一组动画对象是同时运行的,也可以通过设置动画对象的beginTime属性来更改动画的开始时间
代码:

import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var redView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
} // {
// // 平移动画
// CABasicAnimation *a1 = [CABasicAnimation animation];
// a1.keyPath = @"transform.translation.y";
// a1.toValue = @(100);
// // 缩放动画
// CABasicAnimation *a2 = [CABasicAnimation animation];
// a2.keyPath = @"transform.scale";
// a2.toValue = @(0.0);
// // 旋转动画
// CABasicAnimation *a3 = [CABasicAnimation animation];
// a3.keyPath = @"transform.rotation";
// a3.toValue = @(M_PI_2);
// } override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
let basicAnimate = CABasicAnimation(keyPath: "transform.scale")
basicAnimate.toValue = 1.5 let keyFrameAnimate = CAKeyframeAnimation(keyPath: "position") let path = CGPathCreateMutable()
CGPathAddEllipseInRect(path, nil, CGRectMake(100, 200, 150, 150))
keyFrameAnimate.path = path let animateGroup = CAAnimationGroup()
animateGroup.animations = [basicAnimate, keyFrameAnimate]
animateGroup.duration = 2
animateGroup.fillMode = kCAFillModeForwards
animateGroup.removedOnCompletion = false self.redView.layer.addAnimation(animateGroup, forKey: "aaa")
}
}
 
D. CATransition

CAAnimation的子类,用于做转场动画,能够为层提供移出屏幕和移入屏幕的动画效果。iOS比Mac OS X的转场动画效果少一点
UINavigationController就是通过CATransition实现了将控制器的视图推入屏幕的动画效果
属性解析:
type:动画过渡类型
subtype:动画过渡方向
startProgress:动画起点(在整体动画的百分比)
endProgress:动画终点(在整体动画的百分比)
      代码:
import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var imageView: UIImageView!

    var index: Int = 0

    private var _images: Array<UIImage>?
var images: Array<UIImage>? {
get{
if (_images == nil) {
_images = Array<UIImage>() for(var i = 1; i <= 7; i++) {
let image = UIImage(named: "\(i)")
_images?.append(image!)
}
}
return _images
}
set{
_images = newValue
}
} override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib. self.imageView.image = self.images![0]
} @IBAction func next(sender: AnyObject) {
self.index++
if (self.index == 7) {
self.index = 0
}
self.imageView.image = self.images![self.index] let transition = CATransition()
//动画过渡类型
transition.type = "cube" //动画过渡类型方向
transition.subtype = kCATransitionFromLeft transition.duration = 1 self.imageView.layer.addAnimation(transition, forKey: nil) } @IBAction func previous(sender: AnyObject) {
self.index--
if (self.index < 0) {
self.index = 6
} self.imageView.image = self.images![self.index] let transition = CATransition() transition.type = "cube" transition.subtype = kCATransitionFromRight transition.duration = 1 self.imageView.layer.addAnimation(transition, forKey: nil)
}
}

第一篇就到这里结束吧,下一篇打算记录自定义UITabBarController以及presented一个ViewController的转场动画,以及自定义动画的缓冲函数、定时动画、性能优化等方面的只是。

Swift: 深入理解Core Animation(一)的更多相关文章

  1. Objective-C Core Animation深入理解

    Core Animation(核心动画),是从Layer Kit(图层工具包)演变而来的,不仅仅可以用来做动画. 1.视图 一个视图就是屏幕上显示的一个矩形,例如文字.图片和视频. 视图是UIView ...

  2. 老司机带你走进Core Animation

    为什么时隔这么久我又回来了呢? 回来圈粉. 开玩笑的,前段时间ipv6被拒啊,超级悲剧的,前后弄了好久,然后需求啊什么的又超多,所以写好的东西也没有时间整理.不过既然我现在回来了,那么这将是一个井喷的 ...

  3. IOS Core Animation Advanced Techniques的学习笔记(五)

    第六章:Specialized Layers   类别 用途 CAEmitterLayer 用于实现基于Core Animation粒子发射系统.发射器层对象控制粒子的生成和起源 CAGradient ...

  4. iOS——Core Animation 知识摘抄(一)

    本文是对http://www.cocoachina.com/ios/20150104/10814.html文章的关键段落的摘抄,有需要的看原文 CALayer和UIView的关系: CALayer类在 ...

  5. core animation (转)

    iOS Core Animation 简明系列教程 看到无数的CA教程,都非常的难懂,各种事务各种图层关系看的人头大.自己就想用通俗的语言翻译给大家听,尽可能准确表达,如果哪里有问题,请您指出我会尽快 ...

  6. 转 iOS Core Animation 动画 入门学习(一)基础

    iOS Core Animation 动画 入门学习(一)基础 reference:https://developer.apple.com/library/ios/documentation/Coco ...

  7. Core Animation编程指南

    本文是<Core Animation Programming Guide>2013-01-28更新版本的译文.本文略去了原文中关于OS X平台上Core Animation相关内容.因为原 ...

  8. Core Animation之框架简介(一)

    Core Animation之框架简介(一) 作者:wangzz 原文地址:http://blog.csdn.net/wzzvictory/article/details/11180241 转载请注明 ...

  9. Core Animation之多种动画效果

    前面介绍了Core Animation基础知识,还有CALayer的简单使用,最终还是有要动画的滴,这里列出几个动画效果,参考下能加深对Core Animation的认识和理解 1.把图片移到右下角变 ...

随机推荐

  1. 知识总结提炼-AP模块

    http://www.cnblogs.com/bruce_zhao/p/3809686.html 应付模块业务操作流程 供应商管理 供应商概述 在您使用 Oracle Purchasing 之前,需要 ...

  2. 你在用什么思想编码:事务脚本 OR 面向对象?

    最近在公司内部做技术交流的时候,说起技能提升的问题,调研大家想要培训什么,结果大出我意料,很多人想要培训:面向对象编码.于是我抛出一个问题:你觉得我们现在的代码是面向对象的吗?有人回答:是,有人回答否 ...

  3. unity3D ——自带寻路Navmesh入门教程(一)(转)

    转自:http://liweizhaolili.blog.163.com/blog/static/16230744201271161310135/ 说明:从今天开始,我阿赵打算写一些简单的教程,方便自 ...

  4. Navicat for MySQL的服务器连接管理

    Navicat for MySQL可以导入导出数据库服务器的连接,方便你换机器时不用再设置连接.    导出为一个.ncx的XML文件. 导入后,在执行一个查询时,可能会报以下错误 这是因为源机器和本 ...

  5. Mac 快捷键整理

    Mac 快捷键整理 文本编辑 适用于文本编辑器,浏览器等 跳到页首 cmd + ↑ 类似windows下的 ctrl + home 跳到页尾 cmd + ↓ 类似windows下的 ctrl + en ...

  6. [JAVA] 基于TCP的起重机运行模拟器

    1.客户端 package ClientMain; import java.io.DataInputStream; import java.io.DataOutputStream; import ja ...

  7. Gradle的属性设置大全

    Gradle作为一款项目构建工具,由于其强大.灵活.快速.完全兼容Ant等特性,越来越受到人们欢迎.Gradle的灵活有时候也会引起人们的困惑.比如在Gradle中设置变量和属性就有N种办法.由于Gr ...

  8. Clojure的并行与并发

    这次来聊聊clojure的并行与并发,如果你还不知clojure为何物,请翻翻我的上一篇推文.“并行”是指clojure对并行计算的支持(parallel computing),“并发”是其并发特性( ...

  9. aehyok.com的成长之路三——框架结构

    前言 首先奉上个人网站地址传送门:aehyok.com. aehyok.com的成长之路一——开篇 中主要阐述了自己为什么建立自己的网站,以及个人网站的大致方向. aehyok.com的成长之路二—— ...

  10. C#与C++之间类型的对应

    Windows Data Type .NET Data Type BOOL, BOOLEAN Boolean or Int32 BSTR String BYTE Byte CHAR Char DOUB ...