iOS動畫詳解

iOS動畫詳解(學習動畫看這一篇就夠了)


動效設計一直是iOS平臺的優勢,良好的動效設計可以很好地提升用戶體驗。而動畫則是動效的基礎支撐。本動畫將從易到難逐步分析,從CABasicAnimation,UIBezierPath,CAShapeLayer三個方面完整的闡述iOS動畫的實現。最終的效果如下:


WuWeilogin.gif


例子來源與網絡,不是我寫的,我只是加上了詳細的註釋,方便大家理解(我只是代碼的搬運工...)。這個例子是CABasicAnimation,UIBezierPath,CAShapeLayer的綜合實現,如果能完全理解這個例子,相信其它的iOS動畫也難不倒你了。demo下載地址

CABasicAnimation

一、概念
這個部分你需要了解以下概念: CALayer、CAAnimation、CAAnimationGroup

1、CALayer

CALayer是個與UIView很類似的概念,同樣有backgroundColor、frame等相似的屬性,我們可以將UIView看做一種特殊的CALayer。但實際上UIView是對CALayer封裝,在CALayer的基礎上再添加交互功能。UIView的顯示必須依賴於CALayer。我們同樣可以跟新建view一樣新建一個layer,然後添加到某個已有的layer上,同樣可以對layer調整大小、位置、透明度等。一般來說,layer可以有兩種用途:一是對view相關屬性的設置,包括圓角、陰影、邊框等參數,更詳細的參數請點擊這裏;二是實現對view的動畫操控。因此對一個view進行動畫,本質上是對該view的.layer進行動畫操縱。

2、CAAnimation

CAAnimation可以分爲以下幾類:

CABasicAnimation基礎動畫,通過設定起始點,終點,時間,動畫會沿着你這設定點進行移動。可以看做特殊的CAKeyFrameAnimation
CAKeyframeAnimation關鍵幀動畫,可定製度比CABasicAnimation高,也是本系列的接下來的內容
CAAnimationGroup組動畫,支持多個CABasicAnimation或者CAKeyframeAnimation動畫同時執行

實例化

使用方法animationWithKeyPath:對 CABasicAnimation進行實例化,並指定Layer的屬性作爲關鍵路徑進行註冊。

//圍繞y軸旋轉CABasicAnimation *transformAnima = [CABasicAnimation animationWithKeyPath:@"transform.rotation.y"];

設定動畫的屬性和說明屬性說明


CABasicAnimation的屬性
transformAnima.fromValue = @(M_PI_2);
transformAnima.toValue = @(M_PI);
transformAnima.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
transformAnima.autoreverses = YES;
transformAnima.repeatCount = HUGE_VALF;
transformAnima.beginTime = CACurrentMediaTime() + 2;

防止動畫結束後回到初始狀態只需設置removedOnCompletion、fillMode兩個屬性就可以了。

transformAnima.removedOnCompletion = NO;
transformAnima.fillMode = kCAFillModeForwards;

解釋:爲什麼動畫結束後返回原狀態?首先我們需要搞明白一點的是,layer動畫運行的過程是怎樣的?其實在我們給一個視圖添加layer動畫時,真正移動並不是我們的視圖本身,而是 presentation layer 的一個緩存。動畫開始時 presentation layer開始移動,原始layer隱藏,動畫結束時,presentation layer從屏幕上移除,原始layer顯示。這就解釋了爲什麼我們的視圖在動畫結束後又回到了原來的狀態,因爲它根本就沒動過。
這個同樣也可以解釋爲什麼在動畫移動過程中,我們爲何不能對其進行任何操作。
所以在我們完成layer動畫之後,最好將我們的layer屬性設置爲我們最終狀態的屬性,然後將presentation layer 移除掉。
添加動畫

[self.imageView.layer addAnimation:transformAnima forKey:@"A"];

fillMode屬性的理解該屬性定義了你的動畫在開始和結束時的動作。默認值是 kCAFillModeRemoved。

kCAFillModeRemoved 這個是默認值,也就是說當動畫開始前和動畫結束後,動畫對layer都沒有影響,動畫結束後,layer會恢復到之前的狀態
kCAFillModeForwards 當動畫結束後,layer會一直保持着動畫最後的狀態
kCAFillModeBackwards 這個和kCAFillModeForwards是相對的,就是在動畫開始前,你只要將動畫加入了一個layer,layer便立即進入動畫的初始狀態。因爲有可能出現fromValue不是目前layer的初始狀態的情況,如果fromValue就是layer當前的狀態,則這個參數就沒太大意義。
kCAFillModeBoth 理解了上面兩個,這個就很好理解了,這個其實就是上面兩個的合成.動畫加入後開始之前,layer便處於動畫初始狀態,動畫結束後layer保持動畫最後的狀態.

Animation Easing的使用

也即是屬性timingFunction值的設定,有種方式來獲取屬性值
(1)使用方法functionWithName:
這種方式很簡單,這裏只是簡單說明一下取值的含義:

kCAMediaTimingFunctionLinear 傳這個值,在整個動畫時間內動畫都是以一個相同的速度來改變。也就是勻速運動。
kCAMediaTimingFunctionEaseIn 使用該值,動畫開始時會較慢,之後動畫會加速。
kCAMediaTimingFunctionEaseOut 使用該值,動畫在開始時會較快,之後動畫速度減慢。
kCAMediaTimingFunctionEaseInEaseOut 使用該值,動畫在開始和結束時速度較慢,中間時間段內速度較快。

動畫的實現

CABasicAnimation *positionAnima = [CABasicAnimation animationWithKeyPath:@"position.y"];
positionAnima.fromValue = @(self.imageView.center.y);
positionAnima.toValue = @(self.imageView.center.y-30);
positionAnima.timingFunction = [CAMediaTimingFunction functionWithName:
kCAMediaTimingFunctionEaseIn];
CABasicAnimation *transformAnima = [CABasicAnimation animationWithKeyPath:@"transform.rotation.y"];
transformAnima.fromValue = @(0);
transformAnima.toValue = @(M_PI);
transformAnima.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
CAAnimationGroup *animaGroup = [CAAnimationGroup animation];
animaGroup.duration = 2.0f;
animaGroup.fillMode = kCAFillModeForwards;
animaGroup.removedOnCompletion = NO;
animaGroup.animations = @[positionAnima,transformAnima];[self.imageView.layer addAnimation:animaGroup forKey:@"Animation"];

動畫開始和結束時的事件爲了獲取動畫的開始和結束事件,需要實現協議

positionAnima.delegate = self;

代理方法實現

//動畫開始時- (void)animationDidStart:(CAAnimation *)anim{ 
NSLog(@"開始了");
}
//動畫結束時- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag{
 //方法中的flag參數表明了動畫是自然結束還是被打斷,比如調用了removeAnimationForKey:方法
或removeAnimationForKey方法,flag爲NO,如果是正常結束,flag爲YESNSLog(@"結束了");
}

其實比較重要的是有多個動畫的時候如何在代理方法中區分不同的動畫兩種方式
方式一:
如果我們添加動畫的視圖是全局變量,可使用該方法。添加動畫時,我們使用了

[self.imageView.layer addAnimation:animaGroup forKey:@"Animation"];

所以,可根據key來區分不同的動畫

//動畫開始時- (void)animationDidStart:(CAAnimation *)anim{
 if ([anim isEqual:[self.imageView.layer animationForKey:@"Animation"]]) { 
NSLog(@"動畫組執行了");
 }
}

Note:把動畫存儲爲一個屬性然後再回調中比較,用來判定是哪個動畫是不可行的。應爲委託傳入的動畫參數是原始值的一個深拷貝,不是同一個值
方式二
添加動畫的視圖是局部變量時,可使用該方法添加動畫給動畫設置key-value對

[positionAnima setValue:@"PositionAnima" forKey:@"AnimationKey"];
[transformAnima setValue:@"TransformAnima" forKey:@"AnimationKey"];

所以,可以根據key中不同的值來進行區分不同的動畫

//動畫結束時- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag{
 if ([[anim valueForKey:@"AnimationKey"]isEqualToString:@"PositionAnima"]) { 
        NSLog(@"位置移動動畫執行結束");
 } else if ([[anim valueForKey:@"AnimationKey"]isEqualToString:@"TransformAnima"]){ 
        NSLog(@"旋轉動畫執行結束");
 }}

一些常用的animationWithKeyPath值的總結


animationWithKeyPath值

UIBezierPath

使用UIBezierPath可以創建基於矢量的路徑,此類是Core Graphics框架關於路徑的封裝。使用此類可以定義簡單的形狀,如橢圓、矩形或者有多個直線和曲線段組成的形狀等。

UIBezierPath是CGPathRef數據類型的封裝。如果是基於矢量形狀的路徑,都用直線和曲線去創建。我們使用直線段去創建矩形和多邊形,使用曲線去創建圓弧(arc)、圓或者其他複雜的曲線形狀。


87FE4D73-A87A-4B8C-9A0E-73941FA532EC.png
+ (instancetype)bezierPath;

這個使用比較多,因爲這個工廠方法創建的對象,我們可以根據我們的需要任意定製樣式,可以畫任何我們想畫的圖形。

+ (instancetype)bezierPathWithRect:(CGRect)rect;

這個工廠方法根據一個矩形畫貝塞爾曲線。

+ (instancetype)bezierPathWithOvalInRect:(CGRect)rect;

這個工廠方法根據一個矩形畫內切曲線。通常用它來畫圓或者橢圓。

+ (instancetype)bezierPathWithRoundedRect:(CGRect)rect cornerRadius:(CGFloat)cornerRadius;
+ (instancetype)bezierPathWithRoundedRect:(CGRect)rect byRoundingCorners:(UIRectCorner)corners cornerRadii:(CGSize)cornerRadii;

第一個工廠方法是畫矩形,但是這個矩形是可以畫圓角的。第一個參數是矩形,第二個參數是圓角大小。
第二個工廠方法功能是一樣的,但是可以指定某一個角畫成圓角。像這種我們就可以很容易地給UIView擴展添加圓角的方法了。

+ (instancetype)bezierPathWithArcCenter:(CGPoint)center
 radius:(CGFloat)radius
 startAngle:(CGFloat)startAngle
 endAngle:(CGFloat)endAngle
 clockwise:(BOOL)clockwise;

這個工廠方法用於畫弧,參數說明如下:
center: 弧線中心點的座標
radius: 弧線所在圓的半徑
startAngle: 弧線開始的角度值
endAngle: 弧線結束的角度值
clockwise: 是否順時針畫弧線

- (void)closePath;//閉合弧線
 // 畫三角形
- (void)drawTrianglePath { UIBezierPath *path = [UIBezierPath bezierPath];
 [path moveToPoint:CGPointMake(20, 20)];
 [path addLineToPoint:CGPointMake(self.frame.size.width - 40, 20)]; 
[path addLineToPoint:CGPointMake(self.frame.size.width / 2, self.frame.size.height - 20)];

 // 最後的閉合線是可以通過調用closePath方法來自動生成的,也可以調用-addLineToPoint:方法來添加
 // [path addLineToPoint:CGPointMake(20, 20)];
 [path closePath];
 // 設置線寬
 path.lineWidth = 1.5; 
// 設置填充顏色
 UIColor *fillColor = [UIColor greenColor];
 [fillColor set]; [path fill];
 // 設置畫筆顏色
 UIColor *strokeColor = [UIColor blueColor];
 [strokeColor set];
 // 根據我們設置的各個點連線
 [path stroke];
}

我們設置畫筆顏色通過set方法:

UIColor *strokeColor = [UIColor blueColor];[strokeColor set];

如果我們需要設置填充顏色,比如這裏設置爲綠色,那麼我們需要在設置畫筆顏色之前先設置填充顏色,否則畫筆顏色就被填充顏色替代了。也就是說,如果要讓填充顏色與畫筆顏色不一樣,那麼我們的順序必須是先設置填充顏色再設置畫筆顏色。如下,這兩者順序不能改變。因爲我們設置填充顏色也是跟設置畫筆顏色一樣調用UIColor的-set方法。

// 設置填充顏色UIColor *fillColor = [UIColor greenColor];
[fillColor set];[path fill];
// 設置畫筆顏色
UIColor *strokeColor = [UIColor blueColor];[strokeColor set];

CAShapeLayer

CAShapeLayer是在其座標系統內繪製貝塞爾曲線(UIBezierPath)的。因此,使用CAShapeLayer需要與UIBezierPath一起使用。

它有一個path屬性,而UIBezierPath就是對CGPathRef類型的封裝,因此這兩者配合起來使用纔可以的哦!
CAShapeLayer與UIBezierPath的關係:

CAShapeLayer中shape代表形狀的意思,所以需要形狀才能生效
貝塞爾曲線可以創建基於矢量的路徑,而UIBezierPath類是對CGPathRef的封裝
貝塞爾曲線給CAShapeLayer提供路徑,CAShapeLayer在提供的路徑中進行渲染。路徑會閉環,所以繪製出了Shape
用於CAShapeLayer的貝塞爾曲線作爲path,其path是一個首尾相接的閉環的曲線,即使該貝塞爾曲線不是一個閉環的曲線

CAShapeLayer與UIBezierPath畫圓

- (CAShapeLayer *)drawCircle {
CAShapeLayer *circleLayer = [CAShapeLayer layer];
 // 指定frame,只是爲了設置寬度和高度
 circleLayer.frame = CGRectMake(0, 0, 200, 200);
 // 設置居中顯示
 circleLayer.position = self.view.center; 
 // 設置填充顏色
 circleLayer.fillColor = [UIColor clearColor].CGColor;
 // 設置線寬
 circleLayer.lineWidth = 2.0;
 // 設置線的顏色
 circleLayer.strokeColor = [UIColor redColor].CGColor;
 // 使用UIBezierPath創建路徑
 CGRect frame = CGRectMake(0, 0, 200, 200);
 UIBezierPath *circlePath = [UIBezierPath bezierPathWithOvalInRect:frame]; 
// 設置CAShapeLayer與UIBezierPath關聯
 circleLayer.path = circlePath.CGPath;
 // 將CAShaperLayer放到某個層上顯示
 [self.view.layer addSublayer:circleLayer]; return circleLayer;}

登錄例子下載地址:
demo下載地址

如果你都看到這裏了,請給我點個贊吧,你的喜歡是我堅持原創的不竭動力。
參考資料:
iOS 動畫效果:Core Animation & Facebook
拍電影與CABasicAnimation
標哥的技術博客
CABasicAnimation使用總結
蘋果文檔
放肆的使用UIBezierPath和CAShapeLaye

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章