Core Animation譯為核心動畫,它是一組非常強(qiáng)大的動畫處理API。Core Animation的動畫執(zhí)行過程都是在后臺操作的宠叼,不會阻塞主線程。要注意的是其爵,Core Animation是直接作用在CALayer上的冒冬,并非UIView。
CALayer與UIView
- 每個 UIView 內(nèi)部都有一個 CALayer 在背后提供內(nèi)容的繪制和顯示摩渺,并且 UIView 的尺寸樣式都由內(nèi)部的 Layer 所提供简烤。兩者都有樹狀層級結(jié)構(gòu),layer 內(nèi)部有 SubLayers摇幻,View 內(nèi)部有 SubViews.但是 Layer 比 View 多了個AnchorPoint
- 在 View顯示的時候横侦,UIView 做為 Layer 的 CALayerDelegate,View 的顯示內(nèi)容由內(nèi)部的 CALayer 的 display
- CALayer 是默認(rèn)修改屬性支持隱式動畫的,在給 UIView 的 Layer 做動畫的時候绰姻,View 作為 Layer 的代理枉侧,Layer 通過 actionForLayer:forKey:向 View請求相應(yīng)的 action(動畫行為)
- layer 內(nèi)部維護(hù)著三分 layer tree,分別是 presentLayer Tree(動畫樹),modeLayer Tree(模型樹), Render Tree (渲染樹),在做 iOS動畫的時候,我們修改動畫的屬性狂芋,在動畫的其實(shí)是 Layer 的 presentLayer的屬性值,而最終展示在界面上的其實(shí)是提供 View的modelLayer
- 兩者最明顯的區(qū)別是 View可以接受并處理事件榨馁,而 Layer 不可以
Core Animation的結(jié)構(gòu)
核心動畫中所有類都遵守CAMediaTiming協(xié)議。
CAAnaimation是個抽象類帜矾,不具備動畫效果翼虫,必須用它的子類才有動畫效果。
CAAnimationGroup和CATransition才有動畫效果屡萤,CAAnimationGroup是個動畫組珍剑,可以同時進(jìn)行縮放,旋轉(zhuǎn)(同時進(jìn)行多個動畫)灭衷。
CATransition是轉(zhuǎn)場動畫次慢,界面之間跳轉(zhuǎn)(切換)都可以用轉(zhuǎn)場動畫旁涤。
CAPropertyAnimation也是個抽象類翔曲,本身不具備動畫效果,只有子類才有劈愚。
CABasicAnimation和CAKeyframeAnimation:
CABasicAnimation基本動畫瞳遍,做一些簡單效果。
CAKeyframeAnimation幀動畫菌羽,做一些連續(xù)的流暢的動畫掠械。
CAAnimation協(xié)議-CAMediaTiming的屬性
//持續(xù)時間,默認(rèn)值是0.25秒
@property CFTimeInterval duration;
//重復(fù)次數(shù),無線循環(huán)可以設(shè)置HUGE_VALF或者CGFLOAT_MAX
@property float repeatCount;
//重復(fù)時間
@property CFTimeInterval repeatDuration;
/*
可以用來設(shè)置動畫延時執(zhí)行猾蒂,若想延遲2s均唉,就設(shè)置為CACurrentMediaTIme() + 2
CACurrentMediaTIme():圖層的當(dāng)前時間
*/
@property CFTimeInterval beginTime;
//動畫執(zhí)行速度
@property float speed;
//動畫的時間時間偏移量
@property CFTimeInterval timeOffset;
//是否自動返轉(zhuǎn)動畫,默認(rèn)NO.
@property BOOL autoreverses;
//決定當(dāng)前對象在非active時間段的行為.比如動畫開始之前,動畫結(jié)束之后
@property(copy) NSString *fillMode;
/*
kCAFillModeForwards //當(dāng)動畫結(jié)束后,layer會一直保持著動畫最后的狀態(tài).
kCAFillModeBackwards //layer進(jìn)入動畫的初始狀態(tài)并等待動畫開始
kCAFillModeBoth //動畫加入后開始之前,layer便處于動畫初始狀態(tài),動畫結(jié)束后layer保持動畫最后的狀
kCAFillModeRemoved //動畫結(jié)束后,layer會恢復(fù)到之前的狀態(tài).
*/
CAAnimation的屬性
/*
速度控制函數(shù),控制動畫運(yùn)行的節(jié)奏
kCAMediaTimingFunctionLinear 勻速
kCAMediaTimingFunctionEaseIn 慢進(jìn)快出
kCAMediaTimingFunctionEaseOut 快進(jìn)慢出
kCAMediaTimingFunctionEaseInEaseOut 慢進(jìn)慢出 中間加速
kCAMediaTimingFunctionDefault 默認(rèn)
*/
@property(nullable, strong) CAMediaTimingFunction *timingFunction;
/*
默認(rèn)為YES肚菠,代表動畫執(zhí)行完畢后就從圖層上移除舔箭,圖形會恢復(fù)到動畫執(zhí)行前的狀態(tài)。
如果想讓圖層保持顯示動畫執(zhí)行后的狀態(tài)蚊逢,那就設(shè)置為NO层扶,并且設(shè)置fillMode為kCAFillModeForwards
*/
@property(getter=isRemovedOnCompletion) BOOL removedOnCompletion;
//動畫的代理回調(diào)
@property(nullable, strong) id delegate;
delegate方法
//動畫開始回調(diào)
- (void)animationDidStart:(CAAnimation *)anim;
//動畫結(jié)束回調(diào)
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag;
CABaseAnimation 基礎(chǔ)動畫
主要提供了對于CALayer對象中的可變屬性進(jìn)行簡單動畫的操作
CABasicAnimation的屬性:
//開始值
@property(nullable, strong) id fromValue;
//結(jié)束值
@property(nullable, strong) id toValue;
//過渡值
@property(nullable, strong) id byValue;
三個屬性之間的規(guī)則
-
fromValue
和toValue
不為空,動畫的效果會從fromValue
的值變化到toValue
. -
fromValue
和byValue
都不為空,動畫的效果將會從fromValue
變化到fromValue+byValue
-
toValue
和byValue
都不為空,動畫的效果將會從toValue-byValue
變化到toValue
- 只有
fromValue
的值不為空,動畫的效果將會從fromValue
的值變化到當(dāng)前的狀態(tài). - 只有
toValue
的值不為空,動畫的效果將會從當(dāng)前狀態(tài)的值變化到toValue
的值. - 只有
byValue
的值不為空,動畫的效果將會從當(dāng)前的值變化到(當(dāng)前狀態(tài)的值+byValue
)的值.
平移動畫
- (void)startSimpleAnimation
{
//初始化平移動畫
CABasicAnimation *baseAnimation = [CABasicAnimation animationWithKeyPath:@"position"];
//設(shè)置屬性
baseAnimation.fromValue = [NSValue valueWithCGPoint:CGPointMake(100, 100)];
baseAnimation.toValue = [NSValue valueWithCGPoint:CGPointMake(200, 300)];
baseAnimation.duration = 5.0;
baseAnimation.repeatCount = 2;
//動畫執(zhí)行完,保持在最后的狀態(tài)
baseAnimation.removedOnCompletion = NO;
baseAnimation.fillMode = kCAFillModeForwards;
baseAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
//layer添加動畫
[self.simpleView.layer addAnimation:baseAnimation forKey:@"positionAniamtion"];
}
注:
- 如果removedOnComletion=NO并且fillMode=kCAFillModeForwards烙荷,那么在動畫執(zhí)行完畢后镜会,圖層會保持顯示動畫執(zhí)行后的狀態(tài)。但在實(shí)質(zhì)上终抽,圖層的屬性值還是動畫執(zhí)行前的初始值戳表,并沒有真正被改變。比如拿诸,CALayer的position初始值為(0,0)扒袖,CABasicAnimation的fromValue為(100,100)坦刀,toValue為(200,300)灭抑,雖然動畫執(zhí)行完畢后圖層保持在(200,300)這個位置,實(shí)質(zhì)上圖層的position還是為(0,0)
- 如果不設(shè)置removedOnComletion=NO躲庄,那么默認(rèn)為YES描沟,動畫執(zhí)行后飒泻,圖層會回到原來的位置。為什么動畫結(jié)束后返回原狀態(tài)吏廉?首先我們需要搞明白一點(diǎn)的是泞遗,layer動畫運(yùn)行的過程是怎樣的?其實(shí)在我們給一個視圖添加layer動畫時席覆,真正移動并不是我們的視圖本身史辙,而是 presentation layer 的一個緩存。動畫開始時 presentation layer開始移動佩伤,原始layer隱藏聊倔,動畫結(jié)束時,presentation layer從屏幕上移除生巡,原始layer顯示耙蔑。這就解釋了為什么我們的視圖在動畫結(jié)束后又回到了原來的狀態(tài),因為它根本就沒動過孤荣。
- 這個同樣也可以解釋為什么在動畫移動過程中甸陌,我們?yōu)楹尾荒軐ζ溥M(jìn)行任何操作须揣。所以在我們完成layer動畫之后,最好將我們的layer屬性設(shè)置為我們最終狀態(tài)的屬性钱豁,然后將presentation layer 移除掉耻卡。
透明度變化動畫
-(void)opacityAniamtion{
CABasicAnimation *anima = [CABasicAnimation animationWithKeyPath:@"opacity"];
anima.fromValue = [NSNumber numberWithFloat:1.0f];
anima.toValue = [NSNumber numberWithFloat:0.2f];
anima.duration = 1.0f;
[self.simpleView.layer addAnimation:anima forKey:@"opacityAniamtion"];
}
背景色變化動畫
-(void)backgroundAnimation{
CABasicAnimation *anima = [CABasicAnimation animationWithKeyPath:@"backgroundColor"];
anima.toValue =(id) [UIColor greenColor].CGColor;
anima.duration = 1.0f;
[self.simpleView.layer addAnimation:anima forKey:@"backgroundAnimation"];
}
KeyPath的值
CASpringAnimation 彈性動畫(iOS9+)
繼承與CABasicAnimation
屬性
//質(zhì)量,振幅和質(zhì)量成反比
@property CGFloat mass;
//剛度系數(shù)(勁度系數(shù)/彈性系數(shù)),剛度系數(shù)越大,形變產(chǎn)生的力就越大,運(yùn)動越快
@property CGFloat stiffness;
//阻尼系數(shù),阻止彈簧伸縮的系數(shù),阻尼系數(shù)越大,停止越快,可以認(rèn)為它是阻力系數(shù)
@property CGFloat damping;
//初始速率,動畫視圖的初始速度大小速率為正數(shù)時,速度方向與運(yùn)動方向一致,速率為負(fù)數(shù)時,速度方向與運(yùn)動方向相反.
@property CGFloat initialVelocity;
//結(jié)算時間,只讀.返回彈簧動畫到停止時的估算時間,根據(jù)當(dāng)前的動畫參數(shù)估算通常彈簧動畫的時間使用結(jié)算時間比較準(zhǔn)確
@property(readonly) CFTimeInterval settlingDuration;
應(yīng)用
- (void)springAnimation{
CASpringAnimation *spring = [CASpringAnimation animationWithKeyPath:@"position.y"];
spring.damping = 5;
spring.stiffness = 100;
spring.mass = 1;
spring.initialVelocity = 0;
spring.duration = spring.settlingDuration;
spring.fromValue = @(self.simpleView.center.y);
spring.toValue = @(self.simpleView.center.y + 200);
spring.fillMode = kCAFillModeForwards;
[self.simpleView.layer addAnimation:spring forKey:nil];
}
CAKeyframeAnimation 關(guān)鍵幀動畫
Keyframe顧名思義就是關(guān)鍵點(diǎn)的frame牲尺,你可以通過設(shè)定CALayer的始點(diǎn)劲赠、中間關(guān)鍵點(diǎn)、終點(diǎn)的frame秸谢,時間凛澎,動畫會沿你設(shè)定的軌跡進(jìn)行移動 。
屬性解析
/*
NSArray對象估蹄。里面的元素稱為”關(guān)鍵幀”(keyframe)塑煎。
動畫對象會在指定的時間(duration)內(nèi),依次顯示values數(shù)組中的每一個關(guān)鍵幀 .
*/
@property(nullable, copy) NSArray *values;
/*
可以設(shè)置一個CGPathRef\CGMutablePathRef,讓層跟著路徑移動臭蚁。
path只對CALayer的anchorPoint和position起作用最铁。如果你設(shè)置了path,那么values將被忽略
*/
@property(nullable) CGPathRef path;
/*
可以為對應(yīng)的關(guān)鍵幀指定對應(yīng)的時間點(diǎn),其取值范圍為0到1.0,keyTimes中的每一個時間值都對應(yīng)values中的每一幀.
當(dāng)keyTimes沒有設(shè)置的時候,各個關(guān)鍵幀的時間是平分的 .
*/
@property(nullable, copy) NSArray<NSNumber *> *keyTimes;
CABasicAnimation可看做是最多只有2個關(guān)鍵幀的CAKeyframeAnimation
anchorPoint和position的區(qū)別
- position用來設(shè)置CALayer在父層中的位置以父層的左上角為原點(diǎn)(0, 0)
- anchorPoint稱為“定位點(diǎn)”垮兑、“錨點(diǎn)”冷尉,決定著CALayer身上的哪個點(diǎn)會在position屬性所指的位置。以自己的左上角為原點(diǎn)(0, 0)系枪,它的x雀哨、y取值范圍都是0~1,默認(rèn)值為中心點(diǎn)(0.5, 0.5)
- 假如錨點(diǎn)anchorPoint為默認(rèn)值即中點(diǎn)(0.5私爷,0.5)雾棺,而該層的position設(shè)置為(0,0)即為父層的左上點(diǎn)衬浑。anchorPoint默認(rèn)值情況可以理解position為center捌浩。
- (void)positionAniamtion
{
CAKeyframeAnimation *keyAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
//這個點(diǎn)是初始點(diǎn),從這個點(diǎn)開始動畫
NSValue *value_1 = [NSValue valueWithCGPoint:CGPointMake(100, 500)];
NSValue *value_2 = [NSValue valueWithCGPoint:CGPointMake(100, 200)];
NSValue *value_3 = [NSValue valueWithCGPoint:CGPointMake(100, 400)];
//keyTimes中的值是從0.0遞增到1.0
NSNumber *time_1 = [NSNumber numberWithFloat:0.1];
NSNumber *time_2 = [NSNumber numberWithFloat:0.6];
NSNumber *time_3 = [NSNumber numberWithFloat:1.0];
keyAnimation.values = [NSArray arrayWithObjects:value_1,value_2,value_3, nil];
//keyTimes是對應(yīng)values的
keyAnimation.keyTimes = [NSArray arrayWithObjects:time_1,time_2,time_3, nil];
/*
設(shè)置path工秩,values會失效
UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(100, 100, 200, 200)];
keyAnimation.path = path.CGPath;
*/
keyAnimation.timingFunctions = @[[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn],
[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut],
[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]];
/*
kCAAnimationLinear //連續(xù)運(yùn)算模式尸饺,線性。表示當(dāng)關(guān)鍵幀為座標(biāo)點(diǎn)的時候,關(guān)鍵幀之間直接直線相連進(jìn)行插值計算;
kCAAnimationDiscrete //離散運(yùn)算模式助币,只顯示關(guān)鍵幀浪听。就是不進(jìn)行插值計算,所有關(guān)鍵幀直接逐個進(jìn)行顯示;
kCAAnimationPaced //均勻執(zhí)行運(yùn)算模式,線性奠支。使動畫均勻進(jìn)行,而不是按keyTimes設(shè)置的或者按關(guān)鍵幀平分時間,此時keyTimes和timingFunctions無效;
kCAAnimationCubic //平滑運(yùn)算模式馋辈。對關(guān)鍵幀為座標(biāo)點(diǎn)的關(guān)鍵幀進(jìn)行圓滑曲線相連后插值計算抚芦,這里的主要目的是使得運(yùn)行的軌跡變得圓滑倍谜;
kCAAnimationCubicPaced//平滑均勻運(yùn)算模式迈螟。在kCAAnimationCubic的基礎(chǔ)上使得動畫運(yùn)行變得均勻,就是系統(tǒng)時間內(nèi)運(yùn)動的距離相同,此時keyTimes以及timingFunctions也是無效的.
*/
keyAnimation.calculationMode = kCAAnimationPaced;
keyAnimation.duration = 5.0;
[self.simpleView.layer addAnimation:keyAnimation forKey:@"positionAniamtion"];
}
CAAnimationGroup 動畫組
可以保存一組動畫對象,將CAAnimationGroup對象加入層后尔崔,組中所有動畫對象可以同時并發(fā)運(yùn)行.
animations:用來保存一組動畫對象的NSArray.
默認(rèn)情況下答毫,一組動畫對象是同時運(yùn)行的,也可以通過設(shè)置動畫對象的beginTime屬性來更改動畫的開始時間.
CATransition 轉(zhuǎn)場動畫
用于做轉(zhuǎn)場動畫季春,能夠為層提供移出屏幕和移入屏幕的動畫效果洗搂。UINavigationController就是通過CATransition實(shí)現(xiàn)了將控制器的視圖推入屏幕的動畫效果.
屬性
//動畫過渡類型
@property(copy) NSString *type;
//動畫過渡方向
@property(nullable, copy) NSString *subtype;
//動畫起點(diǎn)(在整體動畫的百分比)
@property float startProgress;
//動畫終點(diǎn)(在整體動畫的百分比)
@property float endProgress;
應(yīng)用
/*type過渡效果
fade //交叉淡化過渡(不支持過渡方向) kCATransitionFade
push //新視圖把舊視圖推出去 kCATransitionPush
moveIn //新視圖移到舊視圖上面 kCATransitionMoveIn
reveal //將舊視圖移開,顯示下面的新視圖 kCATransitionReveal
cube //立方體翻滾效果
oglFlip //上下左右翻轉(zhuǎn)效果
suckEffect //收縮效果,如一塊布被抽走(不支持過渡方向)
rippleEffect //滴水效果(不支持過渡方向)
pageCurl //向上翻頁效果
pageUnCurl //向下翻頁效果
cameraIrisHollowOpen //相機(jī)鏡頭打開效果(不支持過渡方向)
cameraIrisHollowClose //相機(jī)鏡頭關(guān)上效果(不支持過渡方向)
*/
/*subtype過渡方向
kCATransitionFromRight
kCATransitionFromLeft
kCATransitionFromBottom
kCATransitionFromTop
*/
// CATransition的使用
CATransition *transition = [CATransition animation];
transition.type = @"pageUnCurl";
transition.subtype = kCATransitionFromBottom;
transition.duration = 2.0;
transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
[self.simpleView.layer addAnimation:transition forKey:@"transition"];
暫停動畫/恢復(fù)動畫
//暫停主要是設(shè)置timeOffset
- (void)pauseAnimation
{
// t = (tp - begin) * speed + timeOffset
//將圖層當(dāng)前時間(CACurrentMediaTime())轉(zhuǎn)換成parent time
CFTimeInterval pauseTime = [self.simpleView.layer convertTime:CACurrentMediaTime() fromLayer:nil];
/*
speed = parent time / local time (默認(rèn)為1.0)
speed = 0.0時意味暫停;
speed = 2.0時是local time的進(jìn)度是parent time的2倍载弄,
即同一動畫效果parent time需要2秒耘拇,而local time只需要1秒
*/
self.simpleView.layer.speed = 0.0;
/*
timeOffset是local time的時間偏移量
與speed = 0結(jié)合,表示layer在timeOffset時間偏移量的位置暫停
*/
self.simpleView.layer.timeOffset = pauseTime;
}
/* beginTime的理解
1. 默認(rèn)beginTime=0.0,并且是相對parent time的
2. 所以在某種意義上 0.0 是等于 [self.simpleView.layer convertTime:CACurrentMediaTime() fromLayer:nil];
可以通過代理方法animationDidStart宇攻,animationDidStop惫叛,獲取parent time來確認(rèn)
3. beginTime是用來設(shè)置動畫執(zhí)行時間的,設(shè)置方式如下:
beginTime = CACurrentMediaTIme()+2,即延遲2秒執(zhí)行動畫逞刷,且動畫執(zhí)行時間還是duration
beginTime = CACurrentMediaTIme()-2,即從動畫的第2秒開始執(zhí)行嘉涌,并且動畫執(zhí)行時間為duration-2,意味舍棄了前2秒的動畫
4. 當(dāng)speed = 1.0(默認(rèn)值)&& timeOffset = 0.0(默認(rèn)值)&& beginTime = 0.0(默認(rèn)值)時
CACurrentMediaTime() = [self.simpleView.layer convertTime:CACurrentMediaTime() fromLayer:nil];
因為[self.simpleView.layer convertTime:CACurrentMediaTime() fromLayer:nil]做的事就是t = (tp-begin) * speed+timeOffset
*/
//恢復(fù)開始主要是將timeOffset轉(zhuǎn)化為beginTime
- (void)startAnimation
{
CFTimeInterval pausedTime = self.simpleView.layer.timeOffset;
// 1. 讓CALayer的時間繼續(xù)行走
self.simpleView.layer.speed = 1.0;
// 2. 取消上次記錄的停留時刻
self.simpleView.layer.timeOffset = 0.0;
// 3. 取消上次設(shè)置的時間
self.simpleView.layer.beginTime = 0.0;
// 4. 計算暫停的時間(這里也可以用CACurrentMediaTime()-pausedTime)
CFTimeInterval timeSincePause = [self.simpleView.layer convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime;
// 5. 設(shè)置相對于父坐標(biāo)系的開始時間(往后退timeSincePause)
self.simpleView.layer.beginTime = timeSincePause;
}
參考資料:
iOS動畫-從不會到熟練應(yīng)用
iOS中的動畫
iOS Core Animation詳解
詳解 CALayer 和 UIView 的區(qū)別和聯(lián)系