前言
hihi,勇敢的小夥伴兒們,大家好,loadingView在生活中已經是很常見了,各大平臺都有不同的loadingView,最常見的瀏覽器就能看到的如下圖這種Google Chrome的等待動畫,
還有很多種其他的loading動畫需要我們學習,
有時候我們使用做好的gif就可以實現,那麼我們爲什麼要學習動畫的繪製呢?
因爲CALayer動畫可以實現比UIView動畫更豐富、更底層、效率更高的動畫。能用代碼實現的儘量不用資源文件,實際上也是對我們應用的一種瘦身。
話不多說,送上我的Demo地址:CALayerDemo1 (名字起得很隨意hhhh),然後開始我們今天的學習吧~
正文
這篇文章通過兩個加載動畫向大家介紹CALayer的動畫。按照面向對象的思想說,Layer其實就是一個模型類,它包含若干屬性,並沒有任何處理邏輯的方法,這些屬性影響着顯示在Layer中的內容。我們先來看看UIView和CALayer之間有什麼區別和聯繫。
- 聯繫:Layer是View背後的那個女人。每一個UIView後面都有對應的CALayer,大家看到的在UIView中顯示的內容其實是在CALayer中。
- 區別:
- View有複雜的、各種組合的佈局機制。Layer只有極簡單的佈局。
- View可以響應用戶交互。Layer不能響應用戶交互。
- View中的繪畫邏輯有CPU執行。Layer中的繪畫直接有GPU執行。
- View有豐富的、功能強大的子類。Layer只有很少的幾個子類。
- View動畫屬性較少,侷限性較大。Layer由於更底層、動畫屬性更多,所以可以實現出更靈活、更豐富的動畫。
第一個CALayer動畫
Layer動畫系列的文章,我不準備系統的從簡單到複雜的知識進行講解,我會通過各種實戰示例,示例中用到什麼知識點就講什麼知識點。
第一個動畫讓我們來實現Google Chrome瀏覽器加載時頁簽上的等待動畫~
新建項目CALayerDemo1,打開Main.storyboard
,拖拽一個UIView到ViewController中,添加好約束,自行設置ViewController和UIView的背景色,這裏UIView的背景色我設置爲無色:
然後添加該View的Outlet到ViewController
中,這個UIView就是要展示加載動畫的View:
@property (weak, nonatomic) IBOutlet UIView *loadingView;
打開ViewController.m
,申明一個常量屬性ovalShapleLayer
:
CAShapeLayer *ovalShapeLayer = [CAShapeLayer layer];
ovalShapleLayer
的類型是CAShapleLayer
,它是CALayer
的爲數不多的子類之一。它的作用是在屏幕上畫出各種形狀,不論是簡單的圓形、方形還是複雜的五角星或不規則圖形都難不住它。CAShapeLayer
有如下一些主要屬性:
- strokeColor:筆畫顏色。
- strokeStart:筆畫開始位置。
- strokeEnd:筆畫結束位置。
- fillColor:圖形填充顏色。
- lineWidth:筆畫寬度,即筆畫的粗細程度。
- lineDashPattern:虛線模式。
- path:圖形的路徑。
- lineCap:筆畫未閉合位置的形狀。
我們之所要申明一個CAShapeLayer
,是因爲要用它在屏幕上畫出一個圓形。下面在viewDidLoad()
方法中添加如下代碼:
ovalShapeLayer.strokeColor = [UIColor whiteColor].CGColor;
ovalShapeLayer.fillColor = [UIColor clearColor].CGColor;
ovalShapeLayer.lineWidth = 7;
這幾個屬性剛纔已經向大家介紹過了,這三行代碼的意思是我們畫出的圓形筆畫顏色是白色,沒有填充色,筆畫的寬度爲7。接着我們申明這個圓形的半徑,使這個圓形的大小爲容納它視圖大小的80%:
CGFloat ovalRadius = _loadingView.frame.size.width / 2. * 0.8;
最後我們設置ovalShapeLayer
的路徑,這是最關鍵的一步,因爲你要告知CAShapeLayer
按照什麼路徑繪製圖形,讓我們接着添加如下代碼:
CGPathRef path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(_loadingView.frame.size.width/2 - ovalRadius , _loadingView.frame.size.height/2 - ovalRadius, ovalRadius * 2, ovalRadius * 2)].CGPath;
ovalShapeLayer.path = path;
這裏出現了新面孔UIBezierPath
,它可以創建基於矢量的路徑,是Core Graphics框架關於path的封裝。UIBezierPath
可以定義簡單的形狀路徑,如橢圓、矩形,或者有多個直線和曲線段組成的形狀。在這裏我們要使用它的初始化方法init(ovalInRect rect: CGRect)
定義一個正圓的路徑。設置完路徑後,將ovalShapeLayer
添加到loadingView
視圖的Layer中,它就可以按照設定好的路徑在loadingView
中繪製圖形了:
[_loadingView.layer addSublayer:ovalShapeLayer];
編譯運行看看效果:
完美的一個圓形。接下來我們要做的是讓這個圓只顯示一部分,因爲Google的加載動畫只有大概五分之二的圓形輪廓。讓我們繼續將目光集中在viewDidLoad()
方法中,在[_loadingView.layer addSublayer:ovalShapeLayer]
這行代碼上面添加另一行代碼:
ovalShapeLayer.strokeEnd = 0.4;
上面的代碼將ovalShapeLayer
的strokeEnd
屬性設置爲0.4,意思是ovalShapeLayer
在繪製圓形時只畫整個圓形的五分之二,即筆畫結束的位置在整個圓形輪廓的五分之二處。編譯運行看看效果:
看來是我們想要的效果,但是仍有一處細節需要我們完善,看看Google的加載動畫,藍色的部分圓形輪廓兩頭是圓形的,而我們的圓形輪廓兩頭是方形的。這個問題很好解決,仍然在[_loadingView.layer addSublayer:ovalShapeLayer]
這行代碼上面添加一行代碼:
ovalShapeLayer.lineCap = kCALineCapRound;
這行代碼的意思是將筆畫兩頭的形狀設置爲圓形,對應的還有兩個常量kCALineCapButt
,kCALineCapSquare
,大家可以試試。再次編譯運行看看效果:
到目前爲止,我們通過CALayer繪製出了動畫的主體,接下來要讓它動起來。在ViewController.m
中添加beginSimpleAnimate()
方法:
- (void)beginSimpleAnimation {
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform.rotation"];
animation.duration = 1.5;
animation.fromValue = 0;
animation.toValue = @(M_PI * 2);
animation.repeatCount = HUGE;
[_loadingView.layer addAnimation:animation forKey:nil];
}
在這個方法中,我們又看到了新面孔CABasicAnimation
,該類提供了基本的、單關鍵幀的Layer屬性動畫,通過animationWithKeyPath:
初始化方法,根據keyPath
創建不同的CAPropertyAnimation
實例。常用的keyPath
有如下一些:
transform.rotation
:旋轉動畫。transform.ratation.x
:按x軸旋轉動畫。transform.ratation.y
:按y軸旋轉動畫。transform.ratation.z
:按z軸旋轉動畫。transform.scale
:按比例放大縮小動畫。transform.scale.x
:在x軸按比例放大縮小動畫。transform.scale.y
:在y軸按比例放大縮小動畫。transform.scale.z
:在z軸按比例放大縮小動畫。position
:移動位置動畫。opacity
:透明度動畫。
以上只是一部分常用的動畫keyPath
,更多的希望大家在實際運用中去挖掘。在beginSimpleAnimation()
方法中,我們使用了transform.rotation
,創建了一個旋轉動畫的實例,然後給該動畫設置了四個屬性:
duration
:動畫持續時間。fromValue
:動畫起始值。toValue
:動畫結束值。repeatCount
:重複次數。
該方法設置這幾個屬性的含義爲使動畫主體不停的旋轉,旋轉一圈的時間爲1.5秒。以上這幾個概念在UIView的動畫中同樣存在,大家應該都已經比較熟悉了。然後使用Layer的addAnimation
方法將旋轉動畫實例添加到目標Layer中,該方法的key
是用來標示添加的動畫,便於以後重複使用時能方便的檢索,如果沒有需求可以傳值nil
。最後viewWillAppear
方法中調用beginSimpleAnimation()
方法:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self beginSimpleAnimation];
}
編譯運行看看效果:
至此我們的第一個簡單的CALayer動畫就完成了,在下一節我們一起實現一個更加有意思的加載動畫,從而向大家介紹新的動畫類型及動畫組合。
Stroke Animation與Animation Group
讓我們先看看要實現的效果:
這種加載動畫在很多應用中都出現過,比如網易新聞、Win版的谷歌瀏覽器中都有使用。下面就讓我們一步一步來實現吧,首先打開Main.storyboard
,新添加一個UIView,在ViewController.m
中添加Outlet:
@property (weak, nonatomic) IBOutlet UIView *animationView;
然後定義一個新的CAShapeLayer:
CAShapeLayer *aniShapeLayer = [CAShapeLayer layer];
在viewDidLoad()
方法中對它進行設置,並將其添加到剛纔創建的animationView中:
aniShapeLayer.strokeColor = [UIColor whiteColor].CGColor;
aniShapeLayer.fillColor = [UIColor clearColor].CGColor;
aniShapeLayer.lineWidth = 7;
CGFloat aniRadius = _animationView.frame.size.width / 2. * 0.8;
CGPathRef aniPath = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(_animationView.frame.size.width/2 - aniRadius , _animationView.frame.size.height/2 - aniRadius, aniRadius * 2, aniRadius * 2)].CGPath;
aniShapeLayer.path = aniPath;
aniShapeLayer.lineCap = kCALineCapRound;
[_animationView.layer addSublayer:aniShapeLayer];
這些操作在上一個動畫都已經做過一遍了,這裏就不再解釋。編譯運行看看是否屏幕上又出現了一個圓圈呢:
接下來在ViewController.m
中添加一個方法beginComplexAnimation()
:
- (void)beginComplexAnimation {
/*
strokeStartAnimate動畫讓繪製圓圈的筆畫起始位置從–0.5開始,目的是讓筆畫起始繪製時等待一段時間,也就是起始位置延遲繪製。而strokeEndAnimate動畫讓繪製圓圈的筆畫終止位置正常的從0繪製到1。這樣一來筆畫兩頭繪製的時間就會不一樣,會有一個時間差,這樣就有圓圈不斷繪製又不斷被擦除的效果。
*/
//筆畫開始動畫
CABasicAnimation *strokeStartAnimate = [CABasicAnimation animationWithKeyPath:@"strokeStart"];
//畫到0.5的時候筆畫結束的動畫執行形成在半圓處出現擦除效果0.25則爲在1/4圓處出現擦除效果
strokeStartAnimate.fromValue = @(-0.5);
strokeStartAnimate.toValue = @(1);
//筆畫結束動畫
CABasicAnimation *strokeEndAnimate = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
//0代表繪製路徑的起始位置
strokeEndAnimate.fromValue = @(0);
//1代表繪製路徑的終止位置
strokeEndAnimate.toValue = @(1);
CAAnimationGroup *group = [[CAAnimationGroup alloc] init];
group.duration = 1.5;
group.repeatCount = HUGE;
group.animations = @[strokeStartAnimate,strokeEndAnimate];
//用_animationView.layer 添加動畫 不可行 需要加在layer上
[aniShapeLayer addAnimation:group forKey:nil];
}
這裏出現了兩個新的動畫類型,筆畫開始動畫和筆畫結束動畫,我們雖然使用CAShapeLayer繪製了一個圓圈,但是它也存在筆畫起始位置和筆畫終止位置,只不過它倆在同一個位置而已,筆畫動畫的位置取值在0–1之間,0代表繪製路徑的起始位置,1代表繪製路徑的終止位置。
所以strokeStartAnimate
動畫讓繪製圓圈的筆畫起始位置從–0.5開始,目的是讓筆畫起始繪製時等待一段時間,也就是起始位置延遲繪製。而strokeEndAnimate
動畫讓繪製圓圈的筆畫終止位置正常的從0繪製到1。這樣一來筆畫兩頭繪製的時間就會不一樣,會有一個時間差,這樣就有圓圈不斷繪製又不斷被擦除的效果。
strokeStartAnimate
和strokeEndAnimate
是兩個動畫,如何作用於一個Layer上呢?這時就要用到CAAnimationGroup
,顧名思義它是將多個動畫組成一個組,在一個動畫組裏,子動畫會同時進行。動畫組可以設置動畫持續時間、重複次數以及子動畫數組。最後將動畫組加在Layer上即可。
最後在viewWillAppear()
方法中調用beginComplexAnimation()
方法:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self beginSimpleAnimation];
[self beginComplexAnimation];
}
編譯運行看看效果:
總結
CALayer動畫可以實現比UIView動畫更豐富、更底層、效率更高的動畫。但是在實際的應用開發中,我們應該按需所用,能用UIView動畫實現的我們就可以不用CALayer動畫,它倆沒有誰優誰劣之分。這篇文章只是CALayer動畫的引子,讓大家對CALayer動畫有初步的瞭解和認識,之後我在文章中會通過更多的實例幫大家更深入的認識CALayer動畫,從而提升自己應用的用戶體驗。
感謝大家看到最後,這篇文章是我按照付宇軒(@DevTalking)的博文裏一步一步學習,改成OC的代碼並添加自己的學習筆記做了自己的Demo完成的,在這裏非常感謝前輩的分享,Swift的原文地址:CALayer Animation - Loading Indicator,如果有問題,還請大家留言給我,感激不盡~
另外,第二個loading圖的實現也希望大家能去動腦想一下~
經過半小時的努力,emmm,我的實現效果如下,代碼在demo裏更新了~