iOS動畫詳解(學習動畫看這一篇就夠了)

動效設計一直是iOS平臺的優勢,良好的動效設計可以很好地提升用戶體驗。而動畫則是動效的基礎支撐。本動畫將從易到難逐步分析,從CABasicAnimation,UIBezierPath,CAShapeLayer三個方面完整的闡述iOS動畫的實現。最終的效果如下:

WuWeilogin.gif

例子來源與網絡,不是我寫的,我只是加上了詳細的注釋,方便大家理解(我只是代碼的搬運工...)。這個例子是CABasicAnimation,UIBezierPath,CAShapeLayer的綜合實現,如果能完全理解這個例子,相信其它的iOS動畫也難不倒你了。demo下載地址

CABasicAnimation

一、概念
這個部分你需要了解以下概念: CALayer、CAAnimation、CAAnimationGroup

1、CALayer

CALayer是個與UIView很類似的概念,同樣有backgroundColor、frame等相似的屬性,我們可以將UIView看做一種特殊的CALayer。但實際上UIView是對CALayer封裝,在CALayer的基礎上再添加交互功能。UIView的顯示必須依賴于CALayer。我們同樣可以跟新建view一樣新建一個layer,然后添加到某個已有的layer上,同樣可以對layer調整大小、位置、透明度等。一般來說,layer可以有兩種用途:一是對view相關屬性的設置,包括圓角、陰影、邊框等參數,更詳細的參數請點擊這里;二是實現對view的動畫操控。因此對一個view進行動畫,本質上是對該view的.layer進行動畫操縱。

2、CAAnimation

CAAnimation可以分為以下幾類:

CABasicAnimation基礎動畫,通過設定起始點,終點,時間,動畫會沿著你這設定點進行移動??梢钥醋鎏厥獾腃AKeyFrameAnimation
CAKeyframeAnimation關鍵幀動畫,可定制度比CABasicAnimation高,也是本系列的接下來的內容
CAAnimationGroup組動畫,支持多個CABasicAnimation或者CAKeyframeAnimation動畫同時執行

實例化

使用方法animationWithKeyPath:對 CABasicAnimation進行實例化,并指定Layer的屬性作為關鍵路徑進行注冊。

//圍繞y軸旋轉CABasicAnimation *transformAnima = [CABasicAnimation animationWithKeyPath:@"transform.rotation.y"];

設定動畫的屬性和說明屬性說明


CABasicAnimation的屬性
transformAnima.fromValue = @(M_PI_2);
transformAnima.toValue = @(M_PI);
transformAnima.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
transformAnima.autoreverses = YES;
transformAnima.repeatCount = HUGE_VALF;
transformAnima.beginTime = CACurrentMediaTime() + 2;

防止動畫結束后回到初始狀態只需設置removedOnCompletion、fillMode兩個屬性就可以了。

transformAnima.removedOnCompletion = NO;
transformAnima.fillMode = kCAFillModeForwards;

解釋:為什么動畫結束后返回原狀態?首先我們需要搞明白一點的是,layer動畫運行的過程是怎樣的?其實在我們給一個視圖添加layer動畫時,真正移動并不是我們的視圖本身,而是 presentation layer 的一個緩存。動畫開始時 presentation layer開始移動,原始layer隱藏,動畫結束時,presentation layer從屏幕上移除,原始layer顯示。這就解釋了為什么我們的視圖在動畫結束后又回到了原來的狀態,因為它根本就沒動過。
這個同樣也可以解釋為什么在動畫移動過程中,我們為何不能對其進行任何操作。
所以在我們完成layer動畫之后,最好將我們的layer屬性設置為我們最終狀態的屬性,然后將presentation layer 移除掉。
添加動畫

[self.imageView.layer addAnimation:transformAnima forKey:@"A"];

fillMode屬性的理解該屬性定義了你的動畫在開始和結束時的動作。默認值是 kCAFillModeRemoved。

kCAFillModeRemoved 這個是默認值,也就是說當動畫開始前和動畫結束后,動畫對layer都沒有影響,動畫結束后,layer會恢復到之前的狀態
kCAFillModeForwards 當動畫結束后,layer會一直保持著動畫最后的狀態
kCAFillModeBackwards 這個和kCAFillModeForwards是相對的,就是在動畫開始前,你只要將動畫加入了一個layer,layer便立即進入動畫的初始狀態。因為有可能出現fromValue不是目前layer的初始狀態的情況,如果fromValue就是layer當前的狀態,則這個參數就沒太大意義。
kCAFillModeBoth 理解了上面兩個,這個就很好理解了,這個其實就是上面兩個的合成.動畫加入后開始之前,layer便處于動畫初始狀態,動畫結束后layer保持動畫最后的狀態.

Animation Easing的使用

也即是屬性timingFunction值的設定,有種方式來獲取屬性值
(1)使用方法functionWithName:
這種方式很簡單,這里只是簡單說明一下取值的含義:

kCAMediaTimingFunctionLinear 傳這個值,在整個動畫時間內動畫都是以一個相同的速度來改變。也就是勻速運動。
kCAMediaTimingFunctionEaseIn 使用該值,動畫開始時會較慢,之后動畫會加速。
kCAMediaTimingFunctionEaseOut 使用該值,動畫在開始時會較快,之后動畫速度減慢。
kCAMediaTimingFunctionEaseInEaseOut 使用該值,動畫在開始和結束時速度較慢,中間時間段內速度較快。

動畫的實現

CABasicAnimation *positionAnima = [CABasicAnimation animationWithKeyPath:@"position.y"];
positionAnima.fromValue = @(self.imageView.center.y);
positionAnima.toValue = @(self.imageView.center.y-30);
positionAnima.timingFunction = [CAMediaTimingFunction functionWithName:
kCAMediaTimingFunctionEaseIn];
CABasicAnimation *transformAnima = [CABasicAnimation animationWithKeyPath:@"transform.rotation.y"];
transformAnima.fromValue = @(0);
transformAnima.toValue = @(M_PI);
transformAnima.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
CAAnimationGroup *animaGroup = [CAAnimationGroup animation];
animaGroup.duration = 2.0f;
animaGroup.fillMode = kCAFillModeForwards;
animaGroup.removedOnCompletion = NO;
animaGroup.animations = @[positionAnima,transformAnima];[self.imageView.layer addAnimation:animaGroup forKey:@"Animation"];

動畫開始和結束時的事件為了獲取動畫的開始和結束事件,需要實現協議

positionAnima.delegate = self;

代理方法實現

//動畫開始時- (void)animationDidStart:(CAAnimation *)anim{ 
NSLog(@"開始了");
}
//動畫結束時- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag{
 //方法中的flag參數表明了動畫是自然結束還是被打斷,比如調用了removeAnimationForKey:方法
或removeAnimationForKey方法,flag為NO,如果是正常結束,flag為YES。
 NSLog(@"結束了");
}

其實比較重要的是有多個動畫的時候如何在代理方法中區分不同的動畫兩種方式
方式一:
如果我們添加動畫的視圖是全局變量,可使用該方法。添加動畫時,我們使用了

[self.imageView.layer addAnimation:animaGroup forKey:@"Animation"];

所以,可根據key來區分不同的動畫

//動畫開始時- (void)animationDidStart:(CAAnimation *)anim{
 if ([anim isEqual:[self.imageView.layer animationForKey:@"Animation"]]) { 
NSLog(@"動畫組執行了");
 }
}

Note:把動畫存儲為一個屬性然后再回調中比較,用來判定是哪個動畫是不可行的。應為委托傳入的動畫參數是原始值的一個深拷貝,不是同一個值
方式二
添加動畫的視圖是局部變量時,可使用該方法添加動畫給動畫設置key-value對

[positionAnima setValue:@"PositionAnima" forKey:@"AnimationKey"];
[transformAnima setValue:@"TransformAnima" forKey:@"AnimationKey"];

所以,可以根據key中不同的值來進行區分不同的動畫

//動畫結束時- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag{
 if ([[anim valueForKey:@"AnimationKey"]isEqualToString:@"PositionAnima"]) { 
        NSLog(@"位置移動動畫執行結束");
 } else if ([[anim valueForKey:@"AnimationKey"]isEqualToString:@"TransformAnima"]){ 
        NSLog(@"旋轉動畫執行結束");
 }}

一些常用的animationWithKeyPath值的總結

animationWithKeyPath值

UIBezierPath

使用UIBezierPath可以創建基于矢量的路徑,此類是Core Graphics框架關于路徑的封裝。使用此類可以定義簡單的形狀,如橢圓、矩形或者有多個直線和曲線段組成的形狀等。

UIBezierPath是CGPathRef數據類型的封裝。如果是基于矢量形狀的路徑,都用直線和曲線去創建。我們使用直線段去創建矩形和多邊形,使用曲線去創建圓?。╝rc)、圓或者其他復雜的曲線形狀。

87FE4D73-A87A-4B8C-9A0E-73941FA532EC.png
+ (instancetype)bezierPath;

這個使用比較多,因為這個工廠方法創建的對象,我們可以根據我們的需要任意定制樣式,可以畫任何我們想畫的圖形。

+ (instancetype)bezierPathWithRect:(CGRect)rect;

這個工廠方法根據一個矩形畫貝塞爾曲線。

+ (instancetype)bezierPathWithOvalInRect:(CGRect)rect;

這個工廠方法根據一個矩形畫內切曲線。通常用它來畫圓或者橢圓。

+ (instancetype)bezierPathWithRoundedRect:(CGRect)rect cornerRadius:(CGFloat)cornerRadius;
+ (instancetype)bezierPathWithRoundedRect:(CGRect)rect byRoundingCorners:(UIRectCorner)corners cornerRadii:(CGSize)cornerRadii;

第一個工廠方法是畫矩形,但是這個矩形是可以畫圓角的。第一個參數是矩形,第二個參數是圓角大小。
第二個工廠方法功能是一樣的,但是可以指定某一個角畫成圓角。像這種我們就可以很容易地給UIView擴展添加圓角的方法了。

+ (instancetype)bezierPathWithArcCenter:(CGPoint)center
 radius:(CGFloat)radius
 startAngle:(CGFloat)startAngle
 endAngle:(CGFloat)endAngle
 clockwise:(BOOL)clockwise;

這個工廠方法用于畫弧,參數說明如下:
center: 弧線中心點的坐標
radius: 弧線所在圓的半徑
startAngle: 弧線開始的角度值
endAngle: 弧線結束的角度值
clockwise: 是否順時針畫弧線

- (void)closePath;//閉合弧線
 // 畫三角形
- (void)drawTrianglePath { UIBezierPath *path = [UIBezierPath bezierPath];
 [path moveToPoint:CGPointMake(20, 20)];
 [path addLineToPoint:CGPointMake(self.frame.size.width - 40, 20)]; 
[path addLineToPoint:CGPointMake(self.frame.size.width / 2, self.frame.size.height - 20)];

 // 最后的閉合線是可以通過調用closePath方法來自動生成的,也可以調用-addLineToPoint:方法來添加
 // [path addLineToPoint:CGPointMake(20, 20)];
 [path closePath];
 // 設置線寬
 path.lineWidth = 1.5; 
// 設置填充顏色
 UIColor *fillColor = [UIColor greenColor];
 [fillColor set]; [path fill];
 // 設置畫筆顏色
 UIColor *strokeColor = [UIColor blueColor];
 [strokeColor set];
 // 根據我們設置的各個點連線
 [path stroke];
}

我們設置畫筆顏色通過set方法:

UIColor *strokeColor = [UIColor blueColor];[strokeColor set];

如果我們需要設置填充顏色,比如這里設置為綠色,那么我們需要在設置畫筆顏色之前先設置填充顏色,否則畫筆顏色就被填充顏色替代了。也就是說,如果要讓填充顏色與畫筆顏色不一樣,那么我們的順序必須是先設置填充顏色再設置畫筆顏色。如下,這兩者順序不能改變。因為我們設置填充顏色也是跟設置畫筆顏色一樣調用UIColor的-set方法。

// 設置填充顏色UIColor *fillColor = [UIColor greenColor];
[fillColor set];[path fill];
// 設置畫筆顏色
UIColor *strokeColor = [UIColor blueColor];[strokeColor set];

CAShapeLayer

CAShapeLayer是在其坐標系統內繪制貝塞爾曲線(UIBezierPath)的。因此,使用CAShapeLayer需要與UIBezierPath一起使用。

它有一個path屬性,而UIBezierPath就是對CGPathRef類型的封裝,因此這兩者配合起來使用才可以的哦!
CAShapeLayer與UIBezierPath的關系:

CAShapeLayer中shape代表形狀的意思,所以需要形狀才能生效
貝塞爾曲線可以創建基于矢量的路徑,而UIBezierPath類是對CGPathRef的封裝
貝塞爾曲線給CAShapeLayer提供路徑,CAShapeLayer在提供的路徑中進行渲染。路徑會閉環,所以繪制出了Shape
用于CAShapeLayer的貝塞爾曲線作為path,其path是一個首尾相接的閉環的曲線,即使該貝塞爾曲線不是一個閉環的曲線

CAShapeLayer與UIBezierPath畫圓

- (CAShapeLayer *)drawCircle {
CAShapeLayer *circleLayer = [CAShapeLayer layer];
 // 指定frame,只是為了設置寬度和高度
 circleLayer.frame = CGRectMake(0, 0, 200, 200);
 // 設置居中顯示
 circleLayer.position = self.view.center; 
 // 設置填充顏色
 circleLayer.fillColor = [UIColor clearColor].CGColor;
 // 設置線寬
 circleLayer.lineWidth = 2.0;
 // 設置線的顏色
 circleLayer.strokeColor = [UIColor redColor].CGColor;
 // 使用UIBezierPath創建路徑
 CGRect frame = CGRectMake(0, 0, 200, 200);
 UIBezierPath *circlePath = [UIBezierPath bezierPathWithOvalInRect:frame]; 
// 設置CAShapeLayer與UIBezierPath關聯
 circleLayer.path = circlePath.CGPath;
 // 將CAShaperLayer放到某個層上顯示
 [self.view.layer addSublayer:circleLayer]; return circleLayer;}

登錄例子下載地址:
demo下載地址

如果你都看到這里了,請給我點個贊吧,你的喜歡是我堅持原創的不竭動力。
參考資料:
iOS 動畫效果:Core Animation & Facebook
拍電影與CABasicAnimation
標哥的技術博客
CABasicAnimation使用總結
蘋果文檔
放肆的使用UIBezierPath和CAShapeLaye

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,622評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,716評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,746評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,991評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,706評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,036評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,029評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,203評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,725評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,451評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,677評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,161評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,857評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,266評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,606評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,407評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,643評論 2 380

推薦閱讀更多精彩內容