如果想在底層做一些改變,想實現一些特別的動畫,這時除了學習Core Animation之外,別無選擇。
最近在看《iOS Core Animation:Advanced Techniques》這本書籍,尚有所收獲,並將之記錄下來。
- 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() } } } - 動畫的基礎知識
這里會說一些動畫軌跡的具體實現,補充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() } }那么,現在加入有一個需求,一張圖片想要繞着圓轉一圈的動畫,如何實現?其實只需要定義好運動的軌跡,然后告訴animate給我按照這個軌跡進行運動即可。代碼: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))) }
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提供了一條運動軌跡。
這是我看到的一份總結貝塞爾曲線很好的博客:http://blog.csdn.net/guo_hongjun1611/article/details/7842110
類似的,沿着圓運動的軌跡已經可以定義了,那么沿着四邊形運動的軌跡也是相似的。那么,沿着曲線運動的軌跡如何定義呢?
B. 貝塞爾曲線
如果你想要定義曲線運動的軌跡,那么這個知識點是必須學習的。
下面的關於曲線的總結也是轉載自上面的博客。
Bézier curve(貝塞爾曲線)是應用於二維圖形應用程序的數學曲線。 曲線定義:起始點、終止點(也稱錨點)、控制點。通過調整控制點,貝塞爾曲線的形狀會發生變化。 1962年,法國數學家Pierre Bézier第一個研究了這種矢量繪制曲線的方法,並給出了詳細的計算公式,因此按照這樣的公式繪制出來的曲線就用他的姓氏來命名,稱為貝塞爾曲線。
以下公式中:B(t)為t時間下 點的坐標;
意義:由 P0 至 P1 的連續點, 描述的一條線段
二階貝塞爾曲線:

原理:由 P0 至 P1 的連續點 Q0,描述一條線段。
由 P1 至 P2 的連續點 Q1,描述一條線段。
由 Q0 至 Q1 的連續點 B(t),描述一條二次貝塞爾曲線。
經驗:P1-P0為曲線在P0處的切線。
三階貝塞爾曲線:

- CGAffineTransform與CATransform3D
已經知道了運動軌跡的定義,那么該如何讓layer沿着動畫軌跡運動呢,如果在運動過程中,還有能縮放、旋轉呢?
A. CGAffineTransform
從CG就可以看出它是屬於Core Graphics的東西,實際上UIView的transform屬性就是CGAffineTransform類型,用它可以做二維平面上的縮放、旋轉、平移。
下面的這幾個函數都創建了一個CGAffineTransform實例:B. CATransform3D
//平移,輸入需要平移的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) } } }
它可以做到讓圖層在三維空間內平移、旋轉等。
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示例代碼:
CATransform3D還有一個很有意思的m34屬性,我們通過調整這個屬性可以做一些很nice的視覺。恩,就是通過設置它的值,可以做出一些很好的透視效果。m34的默認值是0,我們可以設置m34的值為(-1/d)來做相應的透視效果,d的值一般在500.0~~1000.0之間效果就很不錯了,用它寫了一個打開門的動畫,設置m34與不設置m34的區別簡直就是天差地別。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 }) } } }
代碼(代碼太少了,上傳到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]; } @endoc代碼,沒有改寫成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
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
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")
}
圖片:
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")
}
}
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的轉場動畫,以及自定義動畫的緩沖函數、定時動畫、性能優化等方面的只是。






