UIView Animation 動畫學習總結

2022-09-01 12:05:07

一、前言

動畫一直是 iOS 開發中很重要的一部分。設計良好,效果炫酷的動畫往往能對使用者體驗的提升起到很大的作用,在這裡將自己學習 iOS 動畫的體會記錄下來,希望能對別人有所幫助。

iOS 的動畫框架,即 CoreAnimation,本身十分龐大和複雜,這裡暫時分兩個部分進行介紹,分別是 UIView 動畫CALayer 動畫

二、UIView Animation

2.1 簡單動畫

對於 UIView 上簡單的動畫,iOS 提供了很方便的函數:

animateWithDuration:animations:
  • 第一個引數是動畫的持續時間,
  • 第二個引數是一個 block,在 animations block 中對 UIView 的屬性進行調整,設定 UIView 動畫結束後最終的效果,iOS 就會自動補充中間幀,形成動畫。

可以更改的屬性有:

  • frame
  • bounds
  • center
  • transform
  • alpha
  • backgroundColor
  • contentStretch

這些屬性大都是 View 的基本屬性,下面是一個例子,這個例子中的動畫會同時改變 View 的 framebackgroundColoralpha

[UIView animateWithDuration:2.0 animations:^{
    myView.frame = CGRectMake(50, 200, 200, 200);
    myView.backgroundColor = [UIColor blueColor];
    myView.alpha = 0.7;
}];

其中有一個比較特殊的 transform 屬性,它的型別是 CGAffineTransform,即 2D 仿射變換,這是個數學中的概念,用一個三維矩陣來表述 2D 圖形的向量變換。用 transform 屬性對 View 進行:

  • 旋轉
  • 縮放
  • 其他自定義 2D 變換

iOS 提供了下面的函數可以建立簡單的 2D 變換:

  • CGAffineTransformMakeScale
  • CGAffineTransformMakeRotation
  • CGAffineTransformMakeTranslation

例如下面的程式碼會將 View 縮小至原來的 1/4 大小:

[UIView animateWithDuration:2.0 animations:^{
    myView.transform = CGAffineTransformMakeScale(0.5, 0.5);
}];

調節引數

完整版的 animate 函數其實是這樣的:

animateWithDuration:delay:options:animations:completion:

可以通過 delay 引數調節讓動畫延遲產生,同時還一個 options 選項可以調節動畫進行的方式。可用的 options 可分為兩類:

一、控制過程

例如 UIViewAnimationOptionRepeat 可以讓動畫反覆進行, UIViewAnimationOptionAllowUserInteraction 可以讓允許使用者對動畫進行過程中同 View 進行互動(預設是不允許的)

二、控制速度

動畫的進行速度可以用速度曲線來表示(參考這裡),提供的選項例如 :

  • UIViewAnimationOptionCurveEaseIn 是先慢後快,
  • UIViewAnimationOptionCurveEaseOut 是先快後慢。

不同的選項直接可以通過「」操作進行合併,同時使用,例如:

UIViewAnimationOptionRepeat | UIViewAnimationOptionAllowUserInteraction

2.2 關鍵幀動畫

上面介紹的動畫中,我們只能控制開始和結束時的效果,然後由系統補全中間的過程,有些時候我們需要自己設定若干關鍵幀,實現更復雜的動畫效果,這時候就需要關鍵幀動畫的支援了。下面是一個範例:

[UIView animateKeyframesWithDuration:2.0 delay:0.0 options:UIViewKeyframeAnimationOptionRepeat | UIViewKeyframeAnimationOptionAutoreverse animations:^{

    [UIView addKeyframeWithRelativeStartTime:0.0 relativeDuration:0.5 animations:^{
        self.myView.frame = CGRectMake(10, 50, 100, 100);
    }];

    [UIView addKeyframeWithRelativeStartTime: 0.5 relativeDuration:0.3 animations:^{
        self.myView.frame = CGRectMake(20, 100, 100, 100);
    }];

    [UIView addKeyframeWithRelativeStartTime:0.8 relativeDuration:0.2 animations:^{
        self.myView.transform = CGAffineTransformMakeScale(0.5, 0.5);
    }];
} completion:nil];

這個例子新增了三個關鍵幀,在外面的 animateKeyframesWithDuration 中我們設定了持續時間為 2.0 秒,這是真實意義上的時間,裡面的 startTimerelativeDuration 都是相對時間。以第一個為例,startTime 為 0.0,relativeTime 為 0.5,這個動畫會直接開始,持續時間為 2.0 X 0.5 = 1.0 秒,下面第二個的開始時間是 0.5,正好承接上一個結束,第三個同理,這樣三個動畫就變成連續的動畫了。

2.3 View 的轉換

iOS 還提供了兩個函數,用於進行兩個 View 之間通過動畫換場:

  • transitionWithView:duration:options:animations:completion:
  • transitionFromView:toView:duration:options:completion:

需要注意的是,換場動畫會在這兩個 View 共同的父 View 上進行,在寫動畫之前,先要設計好 View 的繼承結構。

同樣,View 之間的轉換也有很多選項可選,例如

  • UIViewAnimationOptionTransitionFlipFromLeft 從左邊翻轉,
  • UIViewAnimationOptionTransitionCrossDissolve 漸變等等。

三、CALayer Animation

UIView 的動畫簡單易用,但是能實現的效果相對有限,上面介紹的 UIView 的幾種動畫方式,實際上是對底層 CALayer 動畫的一種封裝。直接使用 CALayer 層的動畫方法可以實現更多高階的動畫效果。

3.1 基本動畫(CABasicAnimation)

CABasicAnimation 用於建立一個 CALayer 上的基本動畫效果,下面是一個例子:

CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position.x"];
animation.toValue = @200;
animation.duration = 0.8;
animation.repeatCount = 5;
animation.beginTime = CACurrentMediaTime() + 0.5;
animation.fillMode = kCAFillModeRemoved;
[self.myView.layer addAnimation:animation forKey:nil];

KeyPath

這裡我們使用了 animationWithKeyPath 這個方法來改變 layer 的屬性,可以使用的屬性有很多,具體可以參考這裡這裡。其中很多屬性在前面介紹的 UIView 動畫部分我們也看到過,進一步驗證了 UIView 的動畫方法是對底層 CALayer 的一種封裝。

需要注意的一點是,上面我們使用了 position 屬性, layer 的這個 position 屬性和 View 的 frame 以及 bounds 屬性都不相同,而是和 Layer 的 anchorPoint有關,可以由下面的公式計算得到:

position.x = frame.origin.x + 0.5 * bounds.size.width;  
position.y = frame.origin.y + 0.5 * bounds.size.height;

關於 anchorPointposition 屬性的以及具體計算的原理可以參考這篇文章

屬性

·CABasicAnimation 的屬性有下面幾個:

  • beginTime
  • duration
  • fromValue
  • toValue
  • byValue
  • repeatCount
  • autoreverses
  • timingFunction

可以看到,其中 beginTimedurationrepeatCount 等屬性和上面在 UIView 中使用到的 durationUIViewAnimationOptionRepeat等選項是相對應的,不過這裡的選項能夠提供更多的擴充套件性。

需要注意的是 fromValuetoValuebyValue 這幾個選項,支援的設定模式有下面幾種:

  • 設定 fromValuetoValue:從 fromValue 變化到 toValue
  • 設定 fromValuebyValue:從 fromValue 變化到 fromValue + byValue
  • 設定 byValuetoValue:從 toValue - byValue 變化到 toValue
  • 設定 fromValue: 從 fromValue 變化到屬性當前值
  • 設定 toValue從屬性當前值變化到 toValue
  • 設定 byValue從屬性當前值變化到屬性當前值 + toValue

看起來挺複雜,其實概括起來基本就是 :如果某個值不設定,就是用這個屬性當前的值

另外,可以看到上面我們使用的:

animation.toValue = @200;

而不是直接使用 200,因為 toValue 之類的屬性為 id 型別,或者像這樣使用 @ 符號,或者使用:

animation.toValue = [NSNumber numberWithInt:200];

最後一個比較有意思的是 timingFunction 屬性,使用這個屬性可以自定義動畫的運動曲線(節奏,pacing),系統提供了五種值可以選擇:

  • kCAMediaTimingFunctionLinear 線性動畫
  • kCAMediaTimingFunctionEaseIn 先快後慢
  • kCAMediaTimingFunctionEaseOut 先慢後快
  • kCAMediaTimingFunctionEaseInEaseOut 先慢後快再慢
  • kCAMediaTimingFunctionDefault 預設,也屬於中間比較快

此外,我們還可以使用 [CAMediaTimingFunction functionWithControlPoints] 方法來自定義運動曲線,這個網站提供了一個將引數調節視覺化的效果,關於動畫時間系統的具體介紹可以參考這篇文章。

3.2 關鍵幀動畫(CAKeyframeAnimation)

同 UIView 中的類似,CALayer 層也提供了關鍵幀動畫的支援,CAKeyFrameAnimationCABasicAnimation 都繼承自 CAPropertyAnimation,因此它有具有上面提到的那些屬性,此外,CAKeyFrameAnimation 還有特有的幾個屬性。

values 和 keyTimes

使用 valueskeyTimes 可以共同確定一個動畫的若干關鍵幀,範例程式碼如下:

CAKeyframeAnimation *anima = [CAKeyframeAnimation animationWithKeyPath:@"transform.rotation"];//在這裡@"transform.rotation"==@"transform.rotation.z"
NSValue *value1 = [NSNumber numberWithFloat:-M_PI/180*4];
NSValue *value2 = [NSNumber numberWithFloat:M_PI/180*4];
NSValue *value3 = [NSNumber numberWithFloat:-M_PI/180*4];

anima.values = @[value1,value2,value3];
// anima.keyTimes = @[@0.0, @0.5, @1.0];
anima.repeatCount = MAXFLOAT;

[_demoView.layer addAnimation:anima forKey:@"shakeAnimation"];

可以看到上面這個動畫共有三個關鍵幀,如果沒有指定 keyTimes 則各個關鍵幀會平分整個動畫的時間(duration)。

path

使用 path 屬性可以設定一個動畫的運動路徑,注意 path 只對 CALayer 的 anchorPoint position 屬性起作用另外如果你設定了 path ,那麼 values 將被忽略。

CAKeyframeAnimation *anima = [CAKeyframeAnimation animationWithKeyPath:@"position"];
UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(SCREEN_WIDTH/2-100, SCREEN_HEIGHT/2-100, 200, 200)];
anima.path = path.CGPath;
anima.duration = 2.0f;
[_demoView.layer addAnimation:anima forKey:@"pathAnimation"];

3.3 動畫組(CAAnimationGroup)

組動畫可以將一組動畫組合在一起,所有動畫物件可以同時執行,範例程式碼如下:

CAAnimationGroup *group = [[CAAnimationGroup alloc] init];

//1.基礎動畫一
CABasicAnimation *animationOne = [CABasicAnimation animationWithKeyPath:@"transform.scale"];
animationOne.toValue = @2.0;
animationOne.duration = 1.0;

//2.基礎動畫二
CABasicAnimation *animationTwo = [CABasicAnimation animationWithKeyPath:@"position.x"];
animationTwo.toValue = @400;
animationTwo.duration = 1.0;

[group setAnimations:@[animationOne, animationTwo]];
[self.myView.layer addAnimation:group forKey:nil];

需要注意的是,一個 group 組內的某個動畫的持續時間(duration),如果超過了整個組的動畫持續時間,那麼多出的動畫時間將不會被展示。例如一個 group 的持續時間是 5s,而組內一個動畫持續時間為 10s ,那麼這個 10s 的動畫只會展示前 5s 。

3.4 切換動畫(CATransition)

CATransition 可以用於 View 或 ViewController 直接的換場動畫:

self.myView.backgroundColor = [UIColor blueColor];
CATransition *trans = [CATransition animation];
trans.duration = 1.0;
trans.type = @"push";
[self.myView.layer addAnimation:trans forKey:nil];

// 這句放在下面也可以
// self.myView.backgroundColor = [UIColor blueColor];

為什麼改變顏色放在前後都可以呢?具體的解釋可以參考 SO 上的這個回答。簡單來說就是動畫和繪製之間並不衝突。

四、後記

如果本文對你有一點幫助的話,歡迎收藏、點贊,感謝。文中如有不對之處,也歡迎大家在評論區指出,共勉。