1. 程式人生 > >iOS 動畫篇(新編)

iOS 動畫篇(新編)

宣告

該篇文章的內容參考自 iOS核心動畫高階技巧 一文,非常感謝其作者和中文版的作者,讓我能夠相對系統的學習 CoreAnimation 的知識,我受益匪淺,再次感謝。

如果有興趣的小夥伴可以訪問其網站,詳細的,完整的學習 CoreAnimation。

CAAnimation 篇

CAAnimation 是一個抽象動畫類。 遵循著 CAMediaTiming 和 CAAciotn 兩個協議。 要為 Core Animation 圖層或 Scene Kit 物件設定動畫,請建立其子類 CABasicAnimation,CAKeyframeAnimation,CAAnimationGroup 或 CATransition 的例項。Core Animation 可以用在 Mac OS X 和 iOS 平臺。Core Animation 的動畫執行過程都是在後臺操作的,不會阻塞主執行緒。

隱式動畫

當你改變 CALayer 的一個可做動畫的屬性,它並不會立刻在螢幕上呈現出來,而是從先前的值平滑過渡到新值。典型的例子就是改變圖層的背景填充色。

示例:

假如我們現在有一個圖層,那我們在點選螢幕時嘗試去改變此圖層的背景填充色。

-(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    // 生成隨機顏色
    CGFloat red = arc4random() / (CGFloat)INT_MAX;
    CGFloat green = arc4random() / (CGFloat)INT_MAX;
    CGFloat blue = arc4random() / (CGFloat)INT_MAX;
    self.colorLayer.backgroundColor = [UIColor colorWithRed:red green:green blue:blue alpha:1.0].CGColor;
}

隱式動畫

我們可以看到,但我們點選螢幕以改變圖層的背景時,檢視從舊的背景逐漸地過度到了新值。在這過程中,我們沒有做其他額外的操作,這種自行完成的平滑過渡動畫就是隱式動畫。

事務

那麼這一過程時如何完成的呢?實際上動畫是由當前事務來完成的,事務是什麼?事務是 Core Animation 用來包含一系列屬性動畫集合的機制,你可以設定動畫的執行時間等,這些動畫的圖層屬性新值的設定都不會立刻發生變化,而是當事務提交時由 run loop 自動開始。

事務是通過 CATransaction 類來管理。該類沒有屬性或者例項方法,因此你不能建立它,但是你可以通過 +begincommit 來將當前屬性設定分別進行入棧和出棧操作。

任何可以做動畫的圖層屬性都會新增到棧頂的事務,你可以通過 +setAnimationDuration: 方法來設定當前事務的動畫時間,如果不進行設定,預設的時間是0.25s。

我們現在來顯示的完成上一個例子中的動畫,並將動畫時間延長。

-(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    // 開始一個動畫事務
    [CATransaction begin];
    // 設定動畫的執行時間
    [CATransaction setAnimationDuration:1.0];
    // 生成顏色,作為動畫的變化新值
    CGFloat red = arc4random() / (CGFloat)INT_MAX;
    CGFloat green = arc4random() / (CGFloat)INT_MAX;
    CGFloat blue = arc4random() / (CGFloat)INT_MAX;
    self.colorLayer.backgroundColor = [UIColor colorWithRed:red green:green blue:blue alpha:1.0].CGColor;
    // 提交動畫事務
    [CATransaction commit];
}

顯示動畫

我們可以看到圖層的動畫效果依舊沒變,但是漸變的時間明顯變長了很多。從程式碼上看,我們僅僅將圖層需要改變的屬性加到 +begincommit 之間,併為此事務設定了一個時間。

如果你使用過 UIView 的動畫,那麼應該使用過 +beginAnimations:context:+commitAnimations,實際上這兩個都是對 CATransaction 的封裝,其所做動畫都是由 CATransaction 完成的。

完成回撥

CATranscation 的 API 除了提供設定動畫時間 +setAnimationDuration: 還提供了動畫完成的回撥方法:+ setCompletionBlock:。你可以在該方法中接著完成一些事情。

修改一下程式碼:

-(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    // 開始一個動畫事務
    [CATransaction begin];
    // 設定動畫的執行時間
    [CATransaction setAnimationDuration:1.0];
    // 生成顏色,作為動畫的變化新值
    CGFloat red = arc4random() / (CGFloat)INT_MAX;
    CGFloat green = arc4random() / (CGFloat)INT_MAX;
    CGFloat blue = arc4random() / (CGFloat)INT_MAX;
    self.colorLayer.backgroundColor = [UIColor colorWithRed:red green:green blue:blue alpha:1.0].CGColor;
    // 提交動畫事務
    [CATransaction commit];
    // 動畫完成回撥,可以寫在 commit 後面
    [CATransaction setCompletionBlock:^{
        self.colorLayer.affineTransform = CGAffineTransformRotate(self.colorLayer.affineTransform, M_PI_4);
    }];
}

動畫完成回撥

圖層動畫的過程

當我們給對 CALayer 的屬性設新值時,圖層經過以下幾個過程來檢測應該如何呈現新值。

  • 圖層首先檢測它是否有委託者,並且是否實現了協議 CALayerDelegate 中的方法 -actionForLayer:forKey:,如果有,直接呼叫並返回結果。
  • 如果沒有委託者,或者委託沒有實現上述方法,圖層會檢查屬性 actions 字典,試圖找到對應的屬性名。
  • 如果依舊沒有,圖層還是檢查屬性 style 字典,再次嘗試搜尋對應的屬性名。
  • 最後,如果都未能找到,那麼圖層直接會呼叫預設的行為 defaultActionForKey: 方法來展現對應屬性的新值。

那麼,既然我們知道了圖層的行為過程,我們是否可以以此做些什麼?實際上,我們可以參與圖層的行為過程來改變隱式動畫的行為。

我們首先通過圖層的委託代理完成新的動畫過程。

@interface ViewController ()
<
    CALayerDelegate
>
@property (strong, nonatomic)CALayer* colorLayer;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    self.colorLayer = [CALayer layer];
    self.colorLayer.frame = CGRectMake(50.0f, 50.0f, 100.0f, 100.0f);
    self.colorLayer.backgroundColor = [UIColor blueColor].CGColor;
    [self.view.layer addSublayer:self.colorLayer];
    // 設定圖層的委託代理
    
    self.colorLayer.delegate = self;
}

// 完成圖層行為協議

-(id<CAAction>)actionForLayer:(CALayer *)layer forKey:(NSString *)event{
    // 設定新的動畫
    
    CATransition *transition = [CATransition animation];
    transition.type = kCATransitionReveal;
    transition.subtype = kCATransitionFromLeft;
    return transition;
}

-(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    // 生成隨機顏色.
    
    CGFloat red = arc4random() / (CGFloat)INT_MAX;
    CGFloat green = arc4random() / (CGFloat)INT_MAX;
    CGFloat blue = arc4random() / (CGFloat)INT_MAX;
    self.colorLayer.backgroundColor = [UIColor colorWithRed:red green:green blue:blue alpha:1.0].CGColor;
}

圖層行為1

我們也可以通過 actions 字典來完成:

@interface ViewController ()
<
    CALayerDelegate
>
@property (strong, nonatomic)CALayer* colorLayer;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    self.colorLayer = [CALayer layer];
    self.colorLayer.frame = CGRectMake(50.0f, 50.0f, 100.0f, 100.0f);
    self.colorLayer.backgroundColor = [UIColor blueColor].CGColor;
    [self.view.layer addSublayer:self.colorLayer];
    
    // 設定 actions 字典 
    
    CATransition *transition = [CATransition animation];
    transition.type = kCATransitionPush;
    transition.subtype = kCATransitionFromLeft;
    transition.duration = 1.0;  // 動畫時間設定稍長
    
    self.colorLayer.actions = @{@"backgroundColor": transition};
}

-(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    // 生成隨機顏色
    
    CGFloat red = arc4random() / (CGFloat)INT_MAX;
    CGFloat green = arc4random() / (CGFloat)INT_MAX;
    CGFloat blue = arc4random() / (CGFloat)INT_MAX;
    self.colorLayer.backgroundColor = [UIColor colorWithRed:red green:green blue:blue alpha:1.0].CGColor;
}

圖層行為2

顯示動畫

和隱式動畫相對的,顯示動畫一般是開發者們主動去實現的動畫效果,完成圖層從舊狀態到新狀態到過渡切換。和隱式動畫不通,顯示動畫需要開發者關心動畫從產生到消失的每一個細節,如變化的狀態、執行的時長、動畫的次數等等,相比系統提供的簡單的過渡動畫效果,顯示動畫可以完成圖層的各種各樣的酷炫效果。

屬性動畫

顧名思義,屬性動畫(CAPropertyAnimation)類的是針對圖層的一些可作動畫的屬性而言的,該類不能直接拿來使用,開發中通常使用其子類(這一點類似手勢),如:CABasicAnimation 經典動畫、CAKeyframeAnimation 關鍵幀動畫、CASpringAnimation 彈性動畫,基礎動畫的子類。

  • CABasicAnimation

CABasicAnimation 動畫,需要我們為其提供兩個狀態值,一個是初始狀態值,一個是終止狀態值。一般來說,初始值都是圖層最初的狀態,當然,你也可以指定從初始狀態到非終止狀態的之間的任意時刻。

示例:

我們接著上面的例子,將圖層的圓角值做一些改變。

@interface ViewController ()
@property (strong, nonatomic)CALayer* colorLayer;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    self.colorLayer = [CALayer layer];
    self.colorLayer.frame = CGRectMake(50.0f, 50.0f, 100.0f, 100.0f);
    self.colorLayer.backgroundColor = [UIColor blueColor].CGColor;
    [self.view.layer addSublayer:self.colorLayer];  
}

-(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
	// 修改圓角屬性  
	
	CABasicAnimation* animation = [CABasicAnimation animationWithKeyPath:@"cornerRadius"];
   animation.toValue = @(self.colorLayer.bounds.size.height/2.0);
   animation.duration = 2;
   animation.autoreverses = YES;   // 執行逆動畫
   
   [self.colorLayer addAnimation:animation forKey:@"cornerRadius_animation"];
}

經典動畫

注:animationWithKeyPath: 所帶的字串表示需要修改的 layer 可動畫的屬性,不是隨便寫的字串,一般常用的可動畫屬性如下:

key 說明 使用樣例
transform.scale 縮放 @(0.5)
transform.scale.x 寬的比例 @(0.5)
transform.scale.y 高的比例 @(0.5)
opacity 透明度 @(0.5)
cornerRadius 圓角的設定 @(50)
transform.rotation.x 圍繞x軸旋轉 @(M_PI)
transform.rotation.y 圍繞y軸旋轉 @(M_PI)
transform.rotation.z 圍繞z軸旋轉 @(M_PI)
strokeStart 結合CAShapeLayer使用 賦值多變
strokeEnd 結合CAShapeLayer使用 賦值都變
bounds 大小,中心不變 [NSValue valueWithCGRect:CGRectMake(0, 0, 100, 100)];
position 位置(中心點的改變) [NSValue valueWithCGPoint:CGPointMake(100, 100)];
contents 內容, 比如UIImageView的圖片 imageAnima.toValue = (id)[UIImage imageNamed:@”imageName”].CGImage;
……

動畫開始和完成事件

和隱式動畫中的完成回撥不同,CAAnimation 採用了委託模式,因此你如果需要處理動畫的開始和完成事件時,你需要完成 CAAnimationDelegate 的代理方法:

- (void)animationDidStart:(CAAnimation *)anim;
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag;

其中 flag 標識了動畫是否是正常結束。另外,事件傳遞不是完成block塊,而是採用委託模式會帶來一個問題,就是你有多個動畫時,你需要判斷當前是那個圖層的動畫事件。

這裡提供兩個用來區別的方案。一種就是在新增動畫時,-addAnimation:forKey: 設定每個動畫對應不同的key值,然後通過 animationKeys 獲取到圖層上所有的動畫key,然後對每個圖層迴圈所有建,通過 -animationForKey: 找到結果。顯然這種是非常的麻煩的方式。好在 CAAnimation 實現了 KVC 協議,我們可以像使用字典一樣,隨意的存取屬性。

示例:

我們將圖層在完成動畫之後,進行背景色的更改。

@interface ViewController ()
<
    CAAnimationDelegate
>
@property (strong, nonatomic)CALayer* colorLayer;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    self.colorLayer = [CALayer layer];
    self.colorLayer.frame = CGRectMake(50.0f, 50.0f, 100.0f, 100.0f);
    self.colorLayer.backgroundColor = [UIColor blueColor].CGColor;
    [self.view.layer addSublayer:self.colorLayer];  
}

-(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
	
   CABasicAnimation* animation = [CABasicAnimation animationWithKeyPath:@"cornerRadius"];
   animation.toValue = @(self.colorLayer.bounds.size.height/2.0);
   animation.duration = 2;
   animation.autoreverses = YES;   // 執行逆動畫 
   
   animation.delegate = self;
   // 將檢視附加到動畫上  
   
   [animation setValue:self.colorLayer forKey:@"colorLayer"];
   [self.colorLayer addAnimation:animation forKey:@"cornerRadius_animation"];
}

// 動畫結束事件 

-(void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag{
	// 通過key值,取回附加的檢視 
	
   CALayer* layer = [anim valueForKey:@"colorLayer"];
   layer.backgroundColor = UIColor.redColor.CGColor;
}

animation通過KVC附加檢視

  • CAKeyframeAnimation

相比於經典動畫關注於起始和終止的狀態值,關鍵幀動畫更注重整個動畫過程中多個關鍵點的狀態,因此關鍵幀動畫需要一連串的值來做動畫,你甚至可以說,經典動畫是關鍵動畫的一種。

-(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    CAKeyframeAnimation* animation = [CAKeyframeAnimation animationWithKeyPath:@"backgroundColor"];
    animation.duration = 4;
    animation.values = @[
                         (__bridge id)UIColor.blueColor.CGColor,
                         (__bridge id)UIColor.redColor.CGColor,
                         (__bridge id)UIColor.yellowColor.CGColor,
                         (__bridge id)UIColor.greenColor.CGColor,
                         (__bridge id)UIColor.blueColor.CGColor
                         ];
    [self.colorLayer addAnimation:animation forKey:nil];
}

關鍵幀動畫

上述例子演示了給予關鍵幀動畫的關鍵位置的陣列值,實際上,關鍵幀還可以是無數個位置,如果此時的動畫屬性是針對位置一類的,我們就可以將這些關鍵幀看作是路徑,這就演變出了另一種方式做動畫,即 path

下面通過移動圖層來演示這種方式的關鍵幀動畫:

-(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
	CAKeyframeAnimation* animation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
   animation.duration = 4.0;
   // 關鍵幀路徑
   animation.path = self.path.CGPath;
   [self.imageLayer addAnimation:animation forKey:nil];
}

關鍵路徑

我們的飛船可以沿著關鍵路徑進行移動,但是我們發現飛船的方向一直是橫向的,就如最初設定的方向,而不是指向曲線切線的方向。好在蘋果發現了這一點,並且給 CAKeyFrameAnimation 添加了一個 rotationMode 的屬性,設定它為常量 kCAAnimationRotateAuto,圖層將會根據曲線的切線自動旋轉。

-(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
   CAKeyframeAnimation* animation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
   animation.duration = 4.0;
   animation.path = self.path.CGPath;
   animation.rotationMode = kCAAnimationRotateAuto;
   [self.imageLayer addAnimation:animation forKey:nil];
}

沿著切線

  • CASpringAnimation

CABasicAnimation 動畫的子類,可以實現彈性動畫。這個動畫是在 iOS9 之後才出現的,UIView 有其對應的動畫塊。

CASpringAnimation 通過幾個物理相關屬性來計算出圖層執行的動畫效果。這些屬性如下:

mass:質量,影響慣性、拉伸幅度

stiffness:剛度係數,剛度係數越大,形變產生的力就越大,運動越快

damping:阻尼係數,阻止彈簧伸縮的係數,阻尼係數越大,停止越快

initialVelocity:初始速率

settlingDuration:(只讀)結算時間,根據當前的動畫引數估算彈簧動畫到停止時的時間

-(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    CASpringAnimation *animation = [CASpringAnimation animationWithKeyPath:@"position.y"];
    animation.damping = 5;                 // 阻尼係數
    animation.stiffness = 100;             // 剛度係數
    animation.mass = 1;                    // 質量
    animation.initialVelocity = 0;         // 初始速率
    animation.duration = animation.settlingDuration;  //結束時間
    animation.fromValue = @(self.subLayer.position.y);
    animation.toValue = @(self.subLayer.position.y+100);
    animation.removedOnCompletion = NO;
    animation.fillMode = kCAFillModeForwards;
    [self.subLayer addAnimation:animation forKey:nil];
}

彈性動畫

注:動畫並沒會改變圖層和檢視的 frame,因此在執行完動畫後,都預設被重置到最初的位置。如果你需要動畫執行完之後保持當前的位置狀態,可以設定 removedOnCompletion 為 NO,並設定 fillMode 模式為 kCAFillModeForwards

動畫組

之前提到的幾個屬性動畫,都僅僅是作用於單一屬性,但是如果我們需要幾個動畫一起作用到圖層上,該怎麼辦呢?蘋果為我們提供了一個組合動畫 CAAnimationGroup,他是另一個繼承 CAAnimation 的子類,和幾個屬性動畫的父類同級,它只有一個屬性 animations 陣列,就是用來存放多個動畫的。

-(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    // 動畫1
    CABasicAnimation* animation1 = [CABasicAnimation animationWithKeyPath:@"transform.scale"];
    animation1.toValue = @(0.5);
    // 動畫2
    CABasicAnimation* animation2 = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
    animation2.toValue = @(M_PI*2);
    // 動畫組
    CAAnimationGroup* group = [CAAnimationGroup animation];
    // 設定所有的動畫的執行時間
    group.duration = 1.5;
    group.removedOnCompletion = NO;
    group.fillMode = kCAFillModeForwards;
    // 將所有動畫都新增到組中
    group.animations = @[animation1,animation2];
    [self.subLayer addAnimation:group forKey:nil];
}

動畫組的應用

需要注意的是,動畫組的動畫時間取所有動畫最短時間,超出時間的部分會立刻被停止,因此使用動畫組的時候,最好是一些動畫時間統一的組合,比如上面例子中,動畫時間並非由某個動畫來決定,而是由動畫組來設定。

過渡動畫

屬性動畫只會對圖層的一些可動畫的屬性起到作用,當我們想要改變一個不能動畫的屬性(比如圖片),或者從層級關係中新增或者移除圖層(過場效果),屬性動畫將不起作用。因此,蘋果又提供了一個用來做過渡動畫的類 CATransition,注意這個類和上面提到的事物 CATransaction 不是同一個東西。

CATransition是 CAAnimation 的子類,它由兩個過渡型別來控制變換效果,一個是 type:用來控制過渡效果,一個是 subtype:用來控制過渡的方向。

type 的幾種型別:

kCATransitionFade       // 預設,漸變消失
kCATransitionMoveIn     // 從當前圖層上面劃入
kCATransitionPush       // 當前圖層被推出,用新值替換
kCATransitionReveal     // 從當前圖層上面劃出,效果和 kCATransitionMoveIn 相反

除了系統開放出來的四種類型,還有幾種私有API,可以通過字串來設定:

cube                    //立方體翻滾效果
oglFlip                 //上下左右翻轉效果
suckEffect              //收縮效果,如一塊布被抽走(不支援過渡方向)
rippleEffect            //滴水效果(不支援過渡方向)
pageCurl                //向上翻頁效果
pageUnCurl              //向下翻頁效果
cameraIrisHollowOpen    //相機鏡頭開啟效果(不支援過渡方向)
cameraIrisHollowClose   //相機鏡頭關上效果(不支援過渡方向)

subtype 的幾種型別:

kCATransitionFromRight
kCATransitionFromLeft
kCATransitionFromTop
kCATransitionFromBottom

示例:

我們來使用過渡動畫來切換幾張圖片

// 獲取隨機整數
#define randomFromAtoB(A,B) (int)(A+(arc4random()%(B-A+1)))

@interface ViewController ()
{
    NSInteger currentIndex;
}
@property (strong, nonatomic) CALayer* subLayer;
@property (strong,nonatomic) NSArray *images;       // 圖片陣列

@property (strong,nonatomic) NSArray *animations;   // 動畫型別陣列

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    [self.view.layer addSublayer:self.subLayer];
}

-(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    CATransition *transition = [CATransition new];
    // 設定動畫型別,注意對於蘋果官方沒公開的動畫型別只能使用字串,並沒有對應的常量定義
    
    transition.type = self.animations[randomFromAtoB(0, self.animations.count-1)];
    // 設定子型別,方向
    
    transition.subtype = @[kCATransitionFromRight,
                           kCATransitionFromLeft,
                           kCATransitionFromTop,
                           kCATransitionFromBottom][randomFromAtoB(0, 3)];
    // 設定動畫時間
    
    transition.duration = 1.0;
    // 新增新的檢視
    
    currentIndex = (currentIndex+1)%self.images.count;
    NSString *imageName = self.images[currentIndex];
    self.subLayer.contents = (__bridge id)[UIImage imageNamed:imageName].CGImage;
    [self.subLayer addAnimation:transition forKey:@"KCATransitionAnimation"];
}

-(CALayer *)subLayer{
    if (_subLayer==nil) {
        _subLayer = [CALayer new];
        _subLayer.frame = self.view.bounds;
        _subLayer.backgroundColor = UIColor.blueColor.CGColor;
        _subLayer.contents = (__bridge id)[UIImage imageNamed:@"0.jpg"].CGImage;
    }
    return _subLayer;
}

-(NSArray *)animations{
    if (_animations == nil) {
        _animations = @[@"fade",                    
        // 淡出效果
                        @"movein",                  
                        // 新檢視移動到舊檢視
                        @"push",                    
                        // 新檢視推出到舊檢視
                        @"reveal",                  
                        // 移開舊檢視現實新檢視
                        @"cube",                    
                        // 立方體翻轉效果
                        @"oglFlip",                 
                        // 翻轉效果
                        @"suckEffect",              
                        // 吸收效果
                        @"rippleEffect",            
                        // 水滴效果
                        @"pageCurl",                
                        // 向上翻頁
                        @"pageUnCurl",              
                        // 向下翻頁
                        @"cameralIrisHollowOpen",   
                        // 攝像頭開啟
                        @"cameraIrisHollowClose",   
                        // 攝像頭關閉
                        ];
    }
    return _animations;
}

-(NSArray *)images{
    if (_images==nil) {
        _images = @[@"0.jpg",
                    @"1.jpg",
                    @"2.jpg",
                    @"3.jpg",
                    @"4.jpg",
                    @"5.jpg"];
    }
    return _images;
}

過渡動畫

CATransition 並不作用於指定的圖層屬性,這就是說你可以在即使不能準確得知改變了什麼的情況下對圖層做動畫,例如,在不知道 UITableView 哪一行被新增或者刪除的情況下,直接就可以平滑地重新整理它,又如在 UITabBarController 切換檢視時新增上過渡動畫,可以比如淡入淡出的效果,又或者在不知道 UIViewController 內部的檢視層級的情況下對兩個不同的例項做過渡動畫。

這些例子和我們之前所討論的情況完全不同,因為它們不僅涉及到圖層的屬性,而且是整個圖層樹的改變–我們在這種動畫的過程中手動在層級關係中新增或者移除圖層。

自定義過渡動畫

過渡動畫做基礎的原則就是對原始的圖層外觀截圖,然後新增一段動畫,平滑過渡到圖層改變之後那個截圖的效果。如果我們對圖層截圖,就可以使用屬性動畫來代替 CATransition 或者是 UIKit 的過渡方法來實現動畫。

CALayer 有一個 -renderInContext: 方法,可以通過把它繪製到 Core Graphics 的上下文中捕獲當前內容的圖片,然後在另外的檢視中顯示出來。如果我們把這個截圖檢視置於原始檢視之上,就可以遮住真實檢視的所有變化,於是重新建立了一個簡單的過渡效果。

-(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    // 獲取當前螢幕的截圖
    UIGraphicsBeginImageContextWithOptions(self.view.bounds.size, YES, 0.0);
    [self.view.layer renderInContext:UIGraphicsGetCurrentContext()];
    UIImage *coverImage = UIGraphicsGetImageFromCurrentImageContext();
    //insert snapshot view in front of this one
    UIView *coverView = [[UIImageView alloc] initWithImage:coverImage];
    coverView.frame = self.view.bounds;
    // 將截圖覆蓋到當前檢視上
    [self.view addSubview:coverView];
    // 為了演示過渡效果,我們修改一下當前檢視的背景色,以區分之前的檢視
    CGFloat red = arc4random() / (CGFloat)INT_MAX;
    CGFloat green = arc4random() / (CGFloat)INT_MAX;
    CGFloat blue = arc4random() / (CGFloat)INT_MAX;
    self.view.backgroundColor = [UIColor colorWithRed:red green:green blue:blue alpha:1.0];
    // 執行過渡動畫
    [UIView animateWithDuration:0.75 animations:^{
        CGAffineTransform transform = CGAffineTransformMakeScale(0.01, 1);
        coverView.transform = transform;
    } completion:^(BOOL finished) {
        // 最後移除掉障眼法的圖層
        [coverView removeFromSuperview];
    }];
}-(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    // 獲取當前螢幕的截圖
    UIGraphicsBeginImageContextWithOptions(self.view.bounds.size, YES, 0.0);
    [self.view.layer renderInContext:UIGraphicsGetCurrentContext()];
    UIImage *coverImage = UIGraphicsGetImageFromCurrentImageContext();
    //insert snapshot view in front of this one
    UIView *coverView = [[UIImageView alloc] initWithImage:coverImage];
    coverView.frame = self.view.bounds;
    // 將截圖覆蓋到當前檢視上
    [self.view addSubview:coverView];
    // 為了演示過渡效果,我們修改一下當前檢視的背景色,以區分之前的檢視
    CGFloat red = arc4random() / (CGFloat)INT_MAX;
    CGFloat green = arc4random() / (CGFloat)INT_MAX;
    CGFloat blue = arc4random() / (CGFloat)INT_MAX;
    self.view.backgroundColor = [UIColor colorWithRed:red green:green blue:blue alpha:1.0];
    // 執行過渡動畫
    [UIView animateWithDuration:0.75 animations:^{
        CGAffineTransform transform = CGAffineTransformMakeScale(0.01, 1);
        coverView.transform = transform;
    } completion:^(BOOL finished) {
        // 最後移除掉障眼法的圖層
        [coverView removeFromSuperview];
    }];
}

自定義的一種過渡動畫

取消動畫

在我們使用-addAnimation:forKey:方法中的key引數來在新增動畫之後檢索一個動畫,使用如下方法:

- (CAAnimation *)animationForKey:(NSString *)key;

但並不支援在動畫執行過程中修改動畫,所以這個方法主要用來檢測動畫的屬性,或者判斷它是否被新增到當前圖層中。

為了終止一個指定的動畫,你可以用如下方法把它從圖層移除掉:

- (void)removeAnimationForKey:(NSString *)key;

也可以根據需要移除所有動畫:

- (void)removeAllAnimations;

動畫一旦被移除,圖層的外觀就立刻更新到當前的模型圖層的值。一般說來,動畫在結束之後被自動移除,除非設定 removedOnCompletion 為NO,如果你設定動畫在結束之後不被自動移除,那麼當它不需要的時候你要手動移除它;否則它會一直存在於記憶體中,直到圖層被銷燬。

時間相關

CAMediaTiming 協議

CAMediaTiming 協議定義了在一段動畫內用來控制逝去時間的屬性的集合, CALayer 和 CAAnimation 都實現了這個協議,所以時間可以被任意基於一個圖層或者一段動畫的類控制。

CAAnimation 中幾個常用的屬性:

duration

duration(CAMediaTiming的屬性之一),duration是一個 CFTimeInterval 的型別(類似於 NSTimeInterval 的一種雙精度浮點型別),對將要進行的動畫的一次迭代指定了時間。

repeatCount

代表動畫重複的迭代次數。

repeatDuration

它讓動畫重複一個指定的時間,而不是指定次數。

autoreverses

在每次間隔交替迴圈過程中自動回放。在設定此值為 YES 時,duration 的一半時間會用來做自動回放。

相對時間的幾個屬性:

在 Core Animation 中,時間都是相對的,每個動畫都有它自己描述的時間,可以獨立地加速,延時或者偏移。

beginTime

指定了動畫開始之前的的延遲時間。這裡的延遲從動畫新增到可見圖層的那一刻開始測量,預設是0(就是說動畫會立刻執行)。

speed

是一個時間的倍數,預設1.0,減少它會減慢圖層/動畫的時間,增加它會加快速度。如果2.0的速度,那麼對於一個 duration 為1的動畫,實際上在0.5秒的時候就已經完成了。

特別的,前面提到到 CALayer 也實現了 CAMediaTiming 協議,如果把圖層的 speed 的值設定為0,它會暫停任何新增到圖層上的動畫,如果 speed 的值大於1.0則變現為快進,如果設定成一個負值則變為倒回的動畫。

如果設定主 window 圖層的 speed 為0時,可以將整個應用程式的動畫暫停。同樣的,如果我們將其設定為快進,就可以完成加速多有的檢視動畫來進行自動化測試。設定程式碼如下:

self.window.layer.speed = 100;

timeOffset

和 beginTime 類似,但是和增加 beginTime 導致的延遲動畫不同,增加 timeOffset 只是讓動畫快進到某一點,例如,對於一個持續1秒的動畫來說,設定 timeOffset 為0.5意味著動畫將從一半的地方開始。

timeOffset 一個很有用的功能在於你可以它可以讓你手動控制動畫程序,通過設定 speed 為0,可以禁用動畫的自動播放,然後來使用 timeOffset 來來回顯示動畫序列。這可以使得運用手勢來手動控制複雜動畫或者多個圖層的動畫組變得很簡單。

動畫結束後的填充模式:

fillMode

kCAFillModeForwards 
kCAFillModeBackwards 
kCAFillModeBoth 
kCAFillModeRemoved

這個屬性表示動畫結束之後,是保持動畫最開始的那一幀還是保持動畫結束之後的那一幀。預設情況是kCAFillModeRemoved

緩衝過渡

動畫的速度

動畫時間決定了圖層變換的時長,而動畫的速度表示動畫執行的“速率”,通常是變化量和時間的比值。這裡的變化量可以是圖層移動的距離,縮放的大小,也可以是圖層的透明度、填充色等。實際上,任意的可以做動畫的屬性的變化差值都可以稱作變化量。

預設情況下,我們的動畫都是線性變化的,即速率是恆定不變的,就如前面的那些示例,但是有時候,我們並不希望動畫的速度一層不變,那麼該怎麼做呢?幸運的事,Core Animation 已經為我們設計了一系列標準函式提供給我們使用。

timingFunction:

CAAnimation 的 timingFunction 屬性,是 CAMediaTimingFunction 類的一個物件。(如果想改變隱式動畫的計時函式,同樣也可以使用CATransaction的+setAnimationTimingFunction:方法)

我們可以通過下面的建構函式來建立緩衝物件:

+ (instancetype)functionWithName:(CAMediaTimingFunctionName)name;

傳入如下幾個常量之一:

kCAMediaTimingFunctionLinear  	// 線性
kCAMediaTimingFunctionEaseIn 		// 緩慢起步
kCAMediaTimingFunctionEaseOut 	// 緩慢停止
kCAMediaTimingFunctionEaseInEaseOut	// 先慢起步後快最後慢停止
kCAMediaTimingFunctionDefault		// 類似於上面

示例:

-(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    CABasicAnimation* animation = [CABasicAnimation animationWithKeyPath:@"position.x"];
    animation.duration = 2.0;
    animation.toValue = @(self.subLayer.frame.origin.y+400);
    animation.removedOnCompletion = NO;
    animation.fillMode = kCAFillModeForwards;
    animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
    [self.subLayer addAnimation:animation forKey:nil];
}

幾種緩衝對比

上圖的緩衝模式分別為:

kCAMediaTimingFunctionLinear  	
kCAMediaTimingFunctionEaseIn 		
kCAMediaTimingFunctionEaseOut 	
kCAMediaTimingFunctionEaseInEaseOut	

CAKeyframeAnimation 有一個NSArray型別的 timingFunctions 屬性,我們可以用它來對每次動畫的步驟指定不同的計時函式。但是指定函式的個數一定要等於 keyframes 陣列的元素個數減一,因為它是描述每一幀之間動畫速度的函式。

自定義緩衝函式

在上一節中,介紹了幾個系統為我們定義好的緩衝函式,能適用於大部分的應用環境。我們注意到,除了 +functionWithName: 之外,CAMediaTimingFunction 同樣有另一個建構函式,一個有四個浮點引數的 +functionWithControlPoints::::,使用這個方法,我們可以建立一個自定義的緩衝函式,來匹配我們的動畫。

CAMediaTimingFunction 函式的主要原則在於它把輸入的時間轉換成起點和終點之間成比例的改變。我們可以用一個簡單的圖示來解釋,橫軸代表時間,縱軸代表改變的量,於是線性的緩衝就是一條從起點開始的簡單的斜線。

線性緩衝函式的影象

這條曲線的斜率代表了速度,斜率的改變代表了加速度,原則上來說,任何加速的曲線都可以用這種影象來表示,但是 CAMediaTimingFunction 使用了一個叫做三次貝塞爾曲線的函式,它只可以產出指定緩衝函式的子集。

三次貝塞爾緩衝函式

三次貝塞爾緩衝函式表達出先加速,然後減速,最後快到達終點的時候又加速的情況,那麼標準的緩衝函式又該如何用影象來表示呢?

CAMediaTimingFunction 有一個叫做-getControlPointAtIndex:values:的方法,可以用來檢索曲線的點,使用它我們可以找到標準緩衝函式的點,然後用 UIBezierPath 和 CAShapeLayer 來把它畫出來。

曲線的起始和終點始終是{0, 0}和{1, 1},於是我們只需要檢索曲線的第二個和第三個點(控制點)。

- (void)viewDidLoad{
    [super viewDidLoad];
    CAMediaTimingFunction *function = [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseOut];
    // 獲取到兩個控制點
    CGPoint controlPoint1, controlPoint2;
    [function getControlPointAtIndex:1 values:(float *)&controlPoint1];
    [function getControlPointAtIndex:2 values:(float *)&controlPoint2];
    // 建立曲線
    UIBezierPath *path = [[UIBezierPath alloc] init];
    [path moveToPoint:CGPointZero];
    [path addCurveToPoint:CGPointMake(1, 1)
            controlPoint1:controlPoint1 controlPoint2:controlPoint2];
    // 轉換點,讓其可見
    [path applyTransform:CGAffineTransformMakeScale(200, 200)];
    CAShapeLayer *shapeLayer = [CAShapeLayer layer];
    shapeLayer.strokeColor = [UIColor redColor].CGColor;
    shapeLayer.fillColor = [UIColor clearColor].CGColor;
    shapeLayer.lineWidth = 4.0f;
    shapeLayer.path = path.CGPath;
    [self.layerView.layer addSublayer:shapeLayer];
    self.layerView.layer.geometryFlipped = YES;
}

所有的標準緩衝函式的影象如下:

標準CAMediaTimingFunction緩衝曲線

UIView 動畫

2018-11-15 新編

相關推薦

iOS 動畫新編

宣告 該篇文章的內容參考自 iOS核心動畫高階技巧 一文,非常感謝其作者和中文版的作者,讓我能夠相對系統的學習 CoreAnimation 的知識,我受益匪淺,再次感謝。 如果有興趣的小夥伴可以訪問其網站,詳細的,完整的學習 CoreAnimation。 CAA

iOS 動畫 CADisplayLink與CoreGraphics實現動畫

posit 其中 sin interface raw gpa cti sha syn   本文主要介紹利用CoreGraphics和CADisplayLink來實現一個註水動畫。來一個效果圖先:      在介紹註水動畫前,先介紹利用CoreGraphics實現進度條的繪制

Android動畫:最終效果CircleProgressSuperBar

前言 今天終於有時間把最後的成果分享給大家了,為了提高一下部落格的逼格,我也找了一個專門做原型、導圖的線上網站:processon(www.processon.com),這個工具真的很棒,也很方便,這裡給他點個贊。 CircleProgressSuperBa

自定義控制元件三部曲之動畫——alpha、scale、translate、rotate、set的xml屬性及用法

前言:這幾天做客戶回訪,感觸很大,使用者只要是留反饋資訊,總是一種恨鐵不成鋼的心態,想用你的app,卻是因為你的技術問題,讓他們不得不放棄,而你一個回訪電話卻讓他們盡釋前嫌,當最後把手機號留給他們以便隨時溝通的時候,總會發來一條條的鼓勵簡訊,讓我不自主的開始內疚。哎,多麼可愛

動畫——android屬性動畫

本文講介紹android在3.0之後推出的一種新的動畫機制,屬性動畫,對動畫不瞭解的同學,可以先去看看動畫篇(一)——android動畫基礎這篇文章。 本人水平有限,文章中如果出現什麼不正確或者模糊的地方,還請各位小夥伴留下評論,多多指教 : ) 好了,現在

轉:定義控制元件三部曲之動畫——alpha、scale、translate、rotate、set的xml屬性及用法

前言:這幾天做客戶回訪,感觸很大,使用者只要是留反饋資訊,總是一種恨鐵不成鋼的心態,想用你的app,卻是因為你的技術問題,讓他們不得不放棄,而你一個回訪電話卻讓他們盡釋前嫌,當最後把手機號留給他們以便隨時溝通的時候,總會發來一條條的鼓勵簡訊,讓我不自主的開始內疚。哎,多麼

自定義控制元件三部曲之動畫——Interpolator插值器

前言:雖然我不太能欣賞的了帕爾哈提的音樂,但我確實很欣賞他的人生態度,專心做自己,不想名利得失,有一天,你想要的東西都會來。其實我覺得,人生最可怕的就是停止不前,只要一直前行,總有一天會到達人生巔峰。相關文章:一、概述Interpolator屬性是Animation類的一個X

iOS開發Reachability三方框架的使用與介紹

如今開發99%的手機應用都需要依賴網路,那麼開發一款應用首先應該具備判斷應用是否是否有網路的功能。 1. 下面就來介紹今天的主角:Reachability Reachability類: 這個類用於

Android動畫:圓形進度條CircleProgressBar

前言 最近看框架和原始碼比較多,很久沒有寫動畫了,相信很多的朋友都對動畫感興趣,我也不例外,畢竟做前端還是要靠動畫特效吃飯的,並且比寫功能模組更有成就感。 今天我們就來個稍微簡單一點的CircleProgressBar熱個身。 首先需要對ValueAnim

iOS CoreAnimation專題——原理 UIView block動畫實現原理

前言 上一章中我們深入研究了UIView和它持有的那個CALayer之間的關係,知道了我們對UIView的各種屬性的操作實際上都是間接的操作了CALayer對應的屬性。 這一章中我們將進一步探究iOS動畫,看看UIView是如何將CoreAnima

iOS Threading編程指南 官方文檔翻譯第一序言

線程安全 loop 在線 os x 多線程 lib 翻譯 threads read 序言 ? Thread是能夠使多個code paths 在同一個APP內並發運行的幾種技術之一。雖然新的技術為並發運行提供了先進、高效的工具(例如operation 對象和GCD),但是O

Android 動畫原始碼學習

概述 Android提供了各種功能強大的應用動畫的使用者介面元素和繪製自定義的二維和三維圖形的應用。我們可以大致按照下面分類來學習理解 Animation 安卓框架提供了兩個動畫系統:屬性動畫和檢視動畫。屬性動畫在一般情況下,是首選的方法來使用,因為它更

iOS開發——圖形程式設計OC&CALayer介紹與基本使用

在iOS中,你能看得見摸得著的東西基本上都是UIView,比如一個按鈕、一個文字標籤、一個文字輸入框、一個圖示等等,這些都是UIView。 其實UIView之所以能顯示在螢幕上,完全是因為它內部的一個圖層,在建立UIView物件時,UIView內部會自動建立一個圖層(即CALayer物件),通過UIVi

iOS開發之基礎14—— Block

版本 Xcode 9.1 block簡介 block是一個OC物件,於iOS4開始引入。其本身封裝了一段程式碼,可被當作變數、當作引數或作為返回值。block常用於GCD、動畫、排序及各類回撥傳值中。 block程式碼結構圖 注:圖片來自

Android UI效果-2炫酷動畫原始碼

FileBrowserView 一個強大的檔案選擇控制元件。介面比較漂亮,使用也很簡單。特點:可以自定義UI;支援複製、剪下、刪除、移動檔案;可以用在Fragment、ativity、DialogFragment中;支援快速切換目錄。 MultiItemRowL

(原)Unreal源碼搬山-動畫 自定義動畫節點

prot win 微軟 pmo tty tsp specified affect display @author:黑袍小道 太忙了,來更新下,嘿嘿 前言: 本文是接著上文 Unreal搬山之動畫模塊_Unreal動畫流程和框架,進行簡單入門如何自定義動畫圖標的An

利用css3的animation實現點點點loading動畫效果

設置 str ack rdp 提交 ssi frame spin color box-shadow實現的打點效果 簡介 box-shadow理論上可以生成任意的圖形效果,當然也就可以實現點點點的loading效果了。 實現原理 html代碼,首先需要寫如下html代

Django中級

[0 新頁面 cor except setup png int ros action Form驗證 django中的Form一般有兩種功能: 輸入html-----------不能你自己寫一些標簽,而幫你自動生成 驗證用戶輸入-------將用戶驗證信息保存起來,可以傳到

Django中級

dump signal nec 檢測 == csr providing https messages 中間件 django 中的中間件(middleware),在django中,中間件其實就是一個類,在請求到來和結束後,django會根據自己的規則在合適的時機執行中間件中相

linux操作系統基礎

空閑 僵屍進程 標準 為什麽 嘗試 mount命令 性能分析 包含 put 系統監控 1. 系統監視和進程控制工具—top和free1) 掌握top命令的功能:top命令是Linux下常用的性能分析工具,能夠實時顯示系統中各個進程的資源占用狀況,類似於Windows的