?前幾天茅姜,朋友推薦了一款Loading動(dòng)畫(huà)闪朱,感覺(jué)挺有意思,動(dòng)畫(huà)是這樣的
正好這段時(shí)間在學(xué)習(xí)動(dòng)畫(huà),就試著實(shí)現(xiàn)了一版奋姿,
為了降低難度锄开,我對(duì)動(dòng)畫(huà)做了一些簡(jiǎn)化,做完后是這樣的
考慮到拋磚引玉是最好的學(xué)習(xí)方式之一胀蛮,我就分幾篇把自己的實(shí)現(xiàn)思路寫(xiě)出來(lái)院刁,請(qǐng)大家把更好的想法砸過(guò)來(lái)吧!
這個(gè)動(dòng)畫(huà)乍一看很復(fù)雜粪狼,但我們相信一點(diǎn):
一個(gè)復(fù)雜任務(wù)可以拆分成一組簡(jiǎn)單任務(wù)退腥。
因此,我把這段復(fù)雜動(dòng)畫(huà)按時(shí)間拆分成了幾個(gè)階段再榄,又把每個(gè)階段拆分成了幾個(gè)并行的簡(jiǎn)單動(dòng)畫(huà)狡刘。
怎么拆分呢,如果我們有動(dòng)畫(huà)的gif困鸥,我們可以用系統(tǒng)自帶的Preview看一下嗅蔬,像這樣
在gif中一幀一幀的看一下,心里大約就有拆分的思路了疾就。
每個(gè)人拆分的可能都不一樣澜术,答案本來(lái)就不只一種,每個(gè)階段我會(huì)寫(xiě)一篇文字?猬腰,這一篇我們一起看看第一階段鸟废。
?第一階段是這樣的,為了方便大家觀看姑荷,我放慢了動(dòng)畫(huà)速度
?看上去盒延,它就是一段起點(diǎn)和終點(diǎn)不停變化的弧,于是我決定用重繪弧的方式實(shí)現(xiàn)鼠冕。
關(guān)于繪制添寺,我決定使用UIBezierPath,初次實(shí)現(xiàn)懈费,我總是選擇自己熟悉的方式计露。
要畫(huà)弧,我們用到UIBezierPath的這個(gè)方法
+ (instancetype)bezierPathWithArcCenter:(CGPoint)center
radius:(CGFloat)radius
startAngle:(CGFloat)startAngle
endAngle:(CGFloat)endAngle
clockwise:(BOOL)clockwise
為了后文的敘述方便憎乙,我要祭出UIBezierPath的官方文檔中的這張圖了薄坏,大家在后文看我手繪的丑圖時(shí),可能需要參考此圖
開(kāi)始了寨闹,
假設(shè)弧的起點(diǎn)為O(origin),終點(diǎn)為D(dest)君账,動(dòng)畫(huà)中弧是逆時(shí)針轉(zhuǎn)動(dòng)的繁堡,那我們畫(huà)弧時(shí)也采用逆時(shí)針,也就是說(shuō),我們的弧是從O點(diǎn)逆時(shí)針畫(huà)到D點(diǎn)椭蹄。
先看下這張圖闻牡,動(dòng)畫(huà)開(kāi)始和結(jié)束時(shí)的弧的樣子(注意:結(jié)束時(shí)的弧其實(shí)是個(gè)圓,為了方便說(shuō)明绳矩,我故意留了個(gè)缺口)
再回頭觀察動(dòng)畫(huà)可知罩润,結(jié)束時(shí),O點(diǎn)和D點(diǎn)在0(或2π)處重合翼馆,
因此割以,結(jié)束時(shí)可以認(rèn)為弧是從2π逆時(shí)針畫(huà)到0(雖然0和2π在一個(gè)點(diǎn),但從0到0应媚、從0到2π严沥、從2π到0畫(huà)弧是不一樣的,推薦大家大家動(dòng)手畫(huà)一下)
結(jié)束時(shí)O中姜、D我們確定了消玄,那么開(kāi)始的時(shí)候呢,我們要看下動(dòng)畫(huà)中O丢胚、D的運(yùn)行軌跡了翩瓜。
觀察動(dòng)畫(huà),我們可以得出O携龟、D的運(yùn)行軌跡是這樣的
可以看出兔跌,O點(diǎn)逆時(shí)針(逆時(shí)針可以認(rèn)為角度在減小,可以再參考上文中UIBezierPath的官方文檔中的那張圖)繞了3/4圈到2π骨宠,D點(diǎn)逆時(shí)針繞了1.5圈到0浮定;
因此我們可以得出O、D的角度變化层亿,是這樣的
即O點(diǎn)從7/2的π減小到2π桦卒,D點(diǎn)從3π減小到0。
現(xiàn)在我們知道了O匿又、D的起點(diǎn)方灾,可以將前面圖上的文字補(bǔ)全了
由此我們可以得出,O碌更、D在動(dòng)畫(huà)階段中的角度(圖中的progress取值范圍0~1)
只要我們的progress從0逐漸變到1裕偿,我們O、D就逐漸從起點(diǎn)運(yùn)動(dòng)到終點(diǎn)了痛单,每次變化的時(shí)候繪制從O到D逆時(shí)針的弧嘿棘,我們動(dòng)畫(huà)就實(shí)現(xiàn)了。
到了這一步旭绒,我們的動(dòng)畫(huà)思路已經(jīng)有了鸟妙,重要節(jié)點(diǎn)的值也知道了焦人,剩下的就是寫(xiě)代碼了。
寫(xiě)代碼
我們的思路可以認(rèn)為是重父,屬性變化觸發(fā)重繪花椭,
自定義CALayer的子類(lèi),重寫(xiě)它的這兩個(gè)方法可以實(shí)現(xiàn)這個(gè)思路
+ (BOOL)needsDisplayForKey:(NSString *)key;
- (void)drawInContext:(CGContextRef)ctx;
可以參考needsDisplayForKey:的官方文檔的這段文字
Discussion
Subclasses can override this method and return YES
if the layer should be redisplayed when the value of the specified attribute changes. Animations changing the value of the attribute also trigger redisplay.
我們定義progress屬性
@interface ArcToCircleLayer : CALayer
@property (nonatomic) CGFloat ?progress;
@end
重寫(xiě)CALayer的這個(gè)方法
(下面代碼中的@dynamic progress;
我們?cè)诒疚淖詈蠼忉?
@implementation ArcToCircleLayer
@dynamic progress;
+ (BOOL)needsDisplayForKey:(NSString *)key {
if ([key isEqualToString:@"progress"]) {
return YES;
}
return [super needsDisplayForKey:key];
}
這樣當(dāng)progress的值改變的時(shí)候房午,CALayer會(huì)標(biāo)記自己為需要重繪矿辽,
如果我們重寫(xiě)了drawInContext:方法,
系統(tǒng)就會(huì)在適當(dāng)?shù)臅r(shí)候調(diào)用drawInContext:重繪Layer郭厌;
注意到我們上文引用的文檔中有這句
Animations changing the value of the attribute also trigger redisplay.
因此我們可以使用CA動(dòng)畫(huà)來(lái)修改progress的值袋倔,就像下面這樣
self.arcToCircleLayer.progress = 1; // end status
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"progress"];
animation.duration = 5;
animation.fromValue = @0.0;
animation.toValue = @1.0;
[self.arcToCircleLayer addAnimation:animation forKey:nil];
這樣當(dāng)CA動(dòng)畫(huà)執(zhí)行時(shí),progress值會(huì)不斷變化沪曙,從而觸發(fā)drawInContext:重繪奕污,實(shí)現(xiàn)動(dòng)畫(huà)。
(第一句代碼是為了讓動(dòng)畫(huà)結(jié)束時(shí)液走,停留在?動(dòng)畫(huà)結(jié)束時(shí)的狀態(tài)碳默。
簡(jiǎn)單的說(shuō),動(dòng)畫(huà)執(zhí)行時(shí)改變的是presentation Layer的值缘眶,model Layer的值?不會(huì)變化嘱根,
動(dòng)畫(huà)結(jié)束后會(huì)顯示model Layer的值,因?yàn)閙odel Layer的值沒(méi)有變化巷懈,看上去就是直接跳回了動(dòng)畫(huà)開(kāi)始時(shí)的值该抒,上面第一句代碼的作用就是將model Layer的值修改為動(dòng)畫(huà)結(jié)束時(shí)的值。
這部分內(nèi)容可以參考Core Animation Programming Guided的這一節(jié))
看到CABasicAnimation顶燕,大家可能覺(jué)得有很多屬性可以設(shè)置凑保,比如,將代碼修改為這樣
self.arcToCircleLayer.progress = 0; // end status
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"progress"];
animation.duration = 5;
animation.fromValue = @0.0;
animation.toValue = @1.0;
animation.autoreverses = YES;
[self.arcToCircleLayer addAnimation:animation forKey:nil];
如你所料涌攻,動(dòng)畫(huà)變成了這樣
我們發(fā)現(xiàn)了欧引,這種方式可以充分利用CA動(dòng)畫(huà)系統(tǒng)。
動(dòng)畫(huà)流程?已經(jīng)明確了恳谎,接下來(lái)只要重寫(xiě)drawInContext: 的繪制代碼就可以了芝此。
前文已經(jīng)得出了繪制思路和各節(jié)點(diǎn)的值,直接上代碼因痛,為表達(dá)清晰婚苹,我聲明了多個(gè)局部變量,大家可以和這張圖對(duì)照一下
- (void)drawInContext:(CGContextRef)ctx {
UIBezierPath *path = [UIBezierPath bezierPath];
CGFloat radius = MIN(CGRectGetWidth(self.bounds), CGRectGetHeight(self.bounds)) / 2 - kLineWidth / 2;
CGPoint center = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));
// O
CGFloat originStart = M_PI * 7 / 2;
CGFloat originEnd = M_PI * 2;
CGFloat currentOrigin = originStart - (originStart - originEnd) * self.progress;
// D
CGFloat destStart = M_PI * 3;
CGFloat destEnd = 0;
CGFloat currentDest = destStart - (destStart - destEnd) * self.progress;
[path addArcWithCenter:center radius:radius startAngle: currentOrigin endAngle:currentDest clockwise:NO];
CGContextAddPath(ctx, path.CGPath);
CGContextSetLineWidth(ctx, kLineWidth);
CGContextSetStrokeColorWithColor(ctx, [UIColor blueColor].CGColor);
CGContextStrokePath(ctx);
}
至此鸵膏,第一階段代碼的主要部分就完成了膊升,
第一階段的完整代碼大家可以參考GitHub上這個(gè)項(xiàng)目的OneLoadingAnimationStep1目錄。
上文中的@dynamic progress;
的解釋
由于我對(duì)property的了解還不深谭企,對(duì)此的解釋之后會(huì)補(bǔ)上廓译,
目前可參考CALayer.h的這段注釋
/** Property methods. **/
/* CALayer implements the standard NSKeyValueCoding protocol for all
* Objective C properties defined by the class and its subclasses. It
* dynamically implements missing accessor methods for properties
* declared by subclasses.
*
完整代碼
請(qǐng)參考GitHub上這個(gè)項(xiàng)目的OneLoadingAnimationStep1目錄结胀。
本系列的?傳送門(mén)
- 一款Loading動(dòng)畫(huà)的實(shí)現(xiàn)思路(?一)
- 一款Loading動(dòng)畫(huà)的實(shí)現(xiàn)思路(二)
- 一款Loading動(dòng)畫(huà)的實(shí)現(xiàn)思路(三)
- 一款Loading動(dòng)畫(huà)的實(shí)現(xiàn)思路(四·完結(jié)篇)
- Loading動(dòng)畫(huà)外篇·圓的不規(guī)則變形
鳴謝及推薦
- 原動(dòng)效的設(shè)計(jì)者 moonjoin
- Kitten的A-GUIDE-TO-iOS-ANIMATION
- 優(yōu)秀的動(dòng)效教程 葉孤城___