在iOS各類動畫效果中,習(xí)慣分為兩類:隱式動畫和顯式動畫。
隱式動畫
簡單的講,由系統(tǒng)進(jìn)行相關(guān)動畫配置,執(zhí)行動畫效果。
例如:
//UIView 調(diào)用相關(guān)api進(jìn)行動畫
[UIView animateWithDuration:3 animations:^{
self.view.center = CGPointMake(10, 10);
}];
//CALayer 修改相應(yīng)屬性 執(zhí)行的動畫效果
self.layer.position = CGPointMake(10, 10);
//等...
->問題:為什么layer只需要給position賦值就會有動畫效果,而view需要在animate...的block中才會有動畫效果??
->問題的答案,一起看看下面的探索,應(yīng)該可以得出。
ps: 先看看下面顯式動畫CABasicAnimation的使用,再回過頭看探索,在某些問題上會比較清晰
為了更好的探索,定義了三個(gè)類:LLayer: CALayer LView: UIView LAnimation: NSObject<CAAction>
ps: LAnimation 導(dǎo)入<UIKit/UIKit.h> 方便遵守CAAction
### LLayer.m
@implementation LLayer
- (id<CAAction>)actionForKey:(NSString *)event {
//這里有一段注釋,最后告知會返回一個(gè)遵守<CAAction>協(xié)議的對象,
//下面只要有一步找到了,便不會往下走了
/*
*主要是下面這幾部
* 1. if defined, call the delegate method -actionForLayer:forKey:
* 先找layer的delegate方法,打印得知改變layer屬性時(shí)delegate為nil,
所以猜想在改變layer屬性時(shí),應(yīng)該是直接走第二步去了
* 2. look in the layer's `actions' dictionary
* 上面沒有找到,找actions集合
* 3. look in any `actions' dictionaries in the `style' hierarchy
* 上面還沒有找到,在style中查找
* 4. call +defaultActionForKey: on the layer's class
* 最后么,通過default嘗試返回id<CAAction>
* 5. 如果都沒有找到,返回NSNull,
在官方api中有個(gè)NSNull遵守了<CAAction>協(xié)議,但是應(yīng)該沒有在協(xié)議方法中做相應(yīng)操作
*/
//這里返回了自定義的一個(gè)LAnimation: NSObject<CAAction>
//ps:CAAnimation都是遵守<CAAction>的
return [LAnimation new];
}
- (void)addAnimation:(CAAnimation *)anim forKey:(NSString *)key {
[super addAnimation:anim forKey:key];
//通過這個(gè)將CAAnimation 添加到layer
}
@end
### LView.m
@implementation LView
- (id<CAAction>)actionForLayer:(CALayer *)layer forKey:(NSString *)event {
//這里也是嘗試返回一個(gè) id<CAAction>
//但是當(dāng)View的屬性不是在animation...block中時(shí),打印得到的NSNull對象
//當(dāng)在block中時(shí),打印得到<_UIViewAdditiveAnimationAction: 0x60000294c940>
//這個(gè)UIViewAdditiveAnimationAction是一個(gè)私有的類,查看不到,猜想里面進(jìn)行了相關(guān)動畫的添加
/*
* id objc = [super actionForLayer:layer forKey:event];
* NSLog(@"%@",objc);
*/
//這里返回了自定義的一個(gè)LAnimation: NSObject<CAAction>
return [LAnimation new];
}
@end
### LAnimation
@implementation LAnimation
- (void)runActionForKey:(NSString *)event object:(id)anObject arguments:(NSDictionary *)dict {
//先明白下參數(shù)
//event 屬性的key 類似于 position
//anObject 是layer
//dict 動畫的一些參數(shù)
//猜想:在這個(gè)協(xié)議方法中, 假設(shè)是按照下面這種方式進(jìn)行添加animation的
//1. 按照系統(tǒng)的調(diào)用,執(zhí)行這個(gè)方法的應(yīng)該是CAAnimation
//2. 這里應(yīng)該就是 [anObject addAnimation:self forKey:event];
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:event];
animation.toValue = @(CGPointMake(300, 300));
animation.duration = 10;
[anObject addAnimation:animation forKey:event];
}
@end
探索1:給layer.position賦值,會走那些方法?
/*
*1. 修改layer相關(guān)屬性,調(diào)用layer的方法 - (id<CAAction>)actionForKey:(NSString *)event
*2. - (id<CAAction>)actionForKey:(NSString *)event會返回一個(gè)遵守<CAAction>的對象
*3. 遵守<CAAction>的對象會調(diào)用 CAAction協(xié)議方法- (void)runActionForKey:(NSString *)event object:(id)anObject arguments:(NSDictionary *)dict
*4. 在協(xié)議方法中 會為layer添加一個(gè)CAAnimation對象 進(jìn)行相關(guān)動畫配置
*5. runloop會開啟動畫事務(wù),執(zhí)行一個(gè)0.25s的動畫
*/
關(guān)于第4點(diǎn),是猜想。在LAnimation中實(shí)現(xiàn)的操作,只是向layer添加了一個(gè)CAAnimation。
但我不能100%確定官方是否是這樣做的,
即使我們打印出來,在actionForKey 方法中父類返回的是一個(gè)CAAnimation
探索2:給view.center賦值,會走那些方法?
ps. 通過兩種方式賦值,
view.center = ..
[UIView animateWithDuration:0.25 animations:^{
view.center = ..;
}];
/*
* 1. 修改view的屬性,會去改變view.layer 對應(yīng)屬性
* 2. 調(diào)用layer方法 - (id<CAAction>)actionForKey:(NSString *)event
* 3. 調(diào)用layer.delegete 方法- (id<CAAction>)actionForLayer:(CALayer *)layer forKey:(NSString *)event
* 4. 接下來的幾步,猜想和layer接下來的調(diào)用一樣
*/
關(guān)于第2點(diǎn)調(diào)用delegate的方法,打印得到layer.delegate指向的是當(dāng)前view
而上面打印得到layer.delegate為nil
結(jié)論猜想:
1. 每個(gè)隱式動畫,都是在layer上面添加了一個(gè)CAAnimation
2. UIView 切斷了layer層CAAnimation的添加,達(dá)到無動畫效果
實(shí)操:
//關(guān)閉隱式動畫,利用動畫事務(wù)CATransaction
[CATransaction begin];
[CATransaction setDisableActions:YES];//主要這里配置 禁用動畫
//這里是可能引起隱式動畫的相關(guān)屬性修改
layer.position = point;
[CATransaction commit];
顯式動畫

這里主要介紹的是CAAnimation,將CAAnimation主動添加到layer上。
CAAnimation是一個(gè)抽象類,不會直接使用,一般都是使用子類。
一般也不會使用CAPropertyAnimation
這里針對坐標(biāo)大小進(jìn)行的相關(guān)動畫操作,改變的都不是layer本身,動畫結(jié)束后,會發(fā)現(xiàn)視圖回到初始位置,這里會有代理監(jiān)聽動畫結(jié)束開始,可以在代理里面進(jìn)行原始參數(shù)的修改。
另:在動畫里面,有兩個(gè)參數(shù)
[basicAnimation setRemovedOnCompletion:NO];
basicAnimation.fillMode = kCAFillModeBoth;
設(shè)置之后,動畫結(jié)束后視圖不會回到初始位置,這只是表象,看到的是執(zhí)行動畫的那個(gè)臨時(shí)圖層,并不是原始的那個(gè)圖層。這一點(diǎn),當(dāng)動畫結(jié)束后,看圖層會很清晰
CASpringAnimation
//這個(gè)動畫效果,類似于拉開彈簧松手
CASpringAnimation *springAnimation = [CASpringAnimation animationWithKeyPath:@"position"];
springAnimation.mass = 5; //墜物
springAnimation.stiffness = 10000; //剛度
springAnimation.damping = 300; //阻尼
springAnimation.initialVelocity = 100;//初速度
springAnimation.toValue = @(point);
//動畫需要的時(shí)長 這里參數(shù)要么不配置 要么這樣配置成預(yù)估的時(shí)間
springAnimation.duration = springAnimation.settlingDuration;
[self.animationView.layer addAnimation:springAnimation forKey:nil];
CABasicAnimation
//這個(gè)就一個(gè)平滑的動畫過程
CABasicAnimation *basicAnimation = [CABasicAnimation animationWithKeyPath:@"position"];
basicAnimation.toValue = @(point);
[basicAnimation setRemovedOnCompletion:NO];
basicAnimation.fillMode = kCAFillModeBoth;
[self.animationView.layer addAnimation:basicAnimation forKey:nil];
CAKeyframeAnimation
UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:self.view.center radius:200 startAngle:0 endAngle:2 * M_PI clockwise:YES];
CAKeyframeAnimation *keyframeAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
keyframeAnimation.path = path.CGPath;
keyframeAnimation.repeatCount = NSNotFound; //執(zhí)行次數(shù)無數(shù)次
[self.animationView.layer addAnimation:keyframeAnimation forKey:nil];
//也可以通過values 指定幾個(gè)值進(jìn)行相關(guān)配置 根據(jù)keyPath具體調(diào)試
CAAnimationGroup
CABasicAnimation *animation1 = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
animation1.toValue = @(M_PI);
animation1.duration = 1;
animation1.repeatCount = NSNotFound;
CABasicAnimation *animation2 = [CABasicAnimation animationWithKeyPath:@"transform.scale"];
animation2.toValue = @0.2;
animation2.duration = 1;
animation2.repeatCount = NSNotFound;
animation2.autoreverses = YES;
UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:self.view.center radius:200 startAngle:0 endAngle:2 * M_PI clockwise:YES];
CAKeyframeAnimation *keyframeAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
keyframeAnimation.path = path.CGPath;
keyframeAnimation.repeatCount = NSNotFound; //執(zhí)行次數(shù)無數(shù)次
keyframeAnimation.duration = 2;
keyframeAnimation.beginTime = 3;//進(jìn)行組動畫的時(shí)候 這個(gè)參數(shù)還是很好用的 當(dāng)動畫執(zhí)行3秒后 才執(zhí)行關(guān)鍵幀動畫 關(guān)鍵幀動畫2秒執(zhí)行一次
CAAnimationGroup *group = [CAAnimationGroup animation];
group.duration = 10;
group.animations = @[animation1,animation2,keyframeAnimation];
group.repeatCount = NSNotFound;
[self.animationView.layer addAnimation:group forKey:nil];
//這里改變單個(gè)動畫不同參數(shù) 改變組動畫不同參數(shù) 看看變化 多實(shí)踐吧
CATransition
CATransition *trans = [CATransition animation];
// 效果
/*
kCATransitionFade 交叉淡化過渡
kCATransitionMoveIn 新視圖移到舊視圖上面
kCATransitionPush 新視圖把舊視圖推出去
kCATransitionReveal 將舊視圖移開,顯示下面的新視圖
2.用字符串表示
pageCurl 向上翻一頁
pageUnCurl 向下翻一頁
rippleEffect 滴水效果
suckEffect 收縮效果,如一塊布被抽走
cube 立方體效果
oglFlip 上下翻轉(zhuǎn)效果
*/
trans.type = @"pageUnCurl";
// 開始位置 (0-1)
trans.startProgress = 0;
// 結(jié)束位置 (0-1)
trans.endProgress = 1;
// 效果方向
trans.subtype = kCATransitionFromTop;
// 重復(fù)次數(shù)
trans.repeatCount = 10;
// 持續(xù)時(shí)間
trans.duration = 3;
[self.animationView.layer addAnimation:trans forKey:nil];
這里還有一個(gè)比較重要的協(xié)議CAMediaTiming,里面包含了動畫的執(zhí)行時(shí)長、執(zhí)行次數(shù)、是否需要原路返回等參數(shù)
//動畫多久開始執(zhí)行 在組動畫總效果非常明顯
@property CFTimeInterval beginTime;
//單次動畫執(zhí)行時(shí)長
@property CFTimeInterval duration;
//理解成一個(gè)時(shí)間單位
@property float speed;
//額外偏移量 這個(gè)參數(shù)影響這beginTime
//假設(shè)beginTime=3 timeOffset=2 實(shí)際效果是1秒后動畫便會執(zhí)行
//默認(rèn)0
@property CFTimeInterval timeOffset;
//執(zhí)行次數(shù) NSNotFound 無數(shù)次
@property float repeatCount;
//動畫執(zhí)行總時(shí)長 上面有一個(gè)speed
//repeatDuration / speed 可以得到動畫執(zhí)行次數(shù)
@property CFTimeInterval repeatDuration;
//是否原路返回 默認(rèn)NO
@property BOOL autoreverses;
/*
關(guān)于fillMode,有一段很清晰的探索,結(jié)合組動畫配置beginTime等
kCAFillModeRemoved也是fillMode的默認(rèn)屬性,
動畫的效果:
開始,colorLayer的size為(20,20),當(dāng)?shù)?s的時(shí)候,
2s時(shí),colorLayer的size突然就變?yōu)椋?00,100),
然后開始動畫,當(dāng)?shù)?s的時(shí)候colorLayer的duration已經(jīng)完成,
此時(shí)colorLayer的size會突然 變?yōu)椋?0,20),然后在持續(xù)3s,當(dāng)總時(shí)間到10s時(shí)結(jié)束。
當(dāng)fillMode的屬性設(shè)置為kCAFillModeForwards的時(shí)候,
動畫效果為:
開始時(shí),colorLayer的size為(20,20),
當(dāng)?shù)?s的時(shí)候,colorLayer的size突然就變?yōu)椋?00,100),
然后開始做動畫,之前都和kCAFillModeRemoved都一樣,
不一樣的時(shí)當(dāng)?shù)?s的時(shí)候colorLayer的duration已經(jīng)完成,
此時(shí)colorLayer的size還會保持在(400,400),
然后在持續(xù)3s,當(dāng)總時(shí)間到10s時(shí)結(jié)束,此時(shí)size才變?yōu)椋?0,20)
當(dāng)fillMode的屬性設(shè)置為kCAFillModeBackwards的時(shí)候,
動畫效果為:
開始時(shí),colorLayer的size就為(100,100),
當(dāng)?shù)?s的時(shí)候,colorLayer開始做動畫,
當(dāng)?shù)?s的時(shí)候colorLayer的duration已經(jīng)完成,
此時(shí)colorLayer的size會突然 變?yōu)椋?0,20),
然后在持續(xù)3s,當(dāng)總時(shí)間到10s時(shí)結(jié)束。
kCAFillModeBoth是kCAFillModeForwards和kCAFillModeBackwards的結(jié)合。
配合removedOnCompletion一起使用,kCAFillModeForwards kCAFillModeBoth這兩個(gè)是保存最后狀態(tài)
*/
@property(copy) CAMediaTimingFillMode fillMode;
額外資料
查看layer中那些屬性可以有動畫,注釋最后帶Animatable都是可以進(jìn)行動畫的
/* CATransform3D Key Paths */
/* 旋轉(zhuǎn)x,y,z分別是繞x,y,z軸旋轉(zhuǎn) */
static NSString *kCARotation = @"transform.rotation";
static NSString *kCARotationX = @"transform.rotation.x";
static NSString *kCARotationY = @"transform.rotation.y";
static NSString *kCARotationZ = @"transform.rotation.z";
/* 縮放x,y,z分別是對x,y,z方向進(jìn)行縮放 */
static NSString *kCAScale = @"transform.scale";
static NSString *kCAScaleX = @"transform.scale.x";
static NSString *kCAScaleY = @"transform.scale.y";
static NSString *kCAScaleZ = @"transform.scale.z";
/* 平移x,y,z同上 */
static NSString *kCATranslation = @"transform.translation";
static NSString *kCATranslationX = @"transform.translation.x";
static NSString *kCATranslationY = @"transform.translation.y";
static NSString *kCATranslationZ = @"transform.translation.z";
/* 平面 */
/* CGPoint中心點(diǎn)改變位置,針對平面 */
static NSString *kCAPosition = @"position";
static NSString *kCAPositionX = @"position.x";
static NSString *kCAPositionY = @"position.y";
/* CGRect */
static NSString *kCABoundsSize = @"bounds.size";
static NSString *kCABoundsSizeW = @"bounds.size.width";
static NSString *kCABoundsSizeH = @"bounds.size.height";
static NSString *kCABoundsOriginX = @"bounds.origin.x";
static NSString *kCABoundsOriginY = @"bounds.origin.y";
/* 透明度 */
static NSString *kCAOpacity = @"opacity";
/* 背景色 */
static NSString *kCABackgroundColor = @"backgroundColor";
/* 圓角 */
static NSString *kCACornerRadius = @"cornerRadius";
/* 邊框 */
static NSString *kCABorderWidth = @"borderWidth";
/* 陰影顏色 */
static NSString *kCAShadowColor = @"shadowColor";
/* 偏移量CGSize */
static NSString *kCAShadowOffset = @"shadowOffset";
/* 陰影透明度 */
static NSString *kCAShadowOpacity = @"shadowOpacity";
/* 陰影圓角 */
static NSString *kCAShadowRadius = @"shadowRadius";