iOS動畫筆記

在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.png

這里主要介紹的是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";
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容