CALayer 和 Core Animation

CALayer

一、簡介

1.CALayer

簡單的說是層(圖層)的概念,類似與PS中的圖層。一個用來完成繪制、渲染、動畫等效果的可見的容器。

2.CALayer 與 UIView 的聯系與區別

UIView中有一個layer屬性,為根圖層。UIView之所以能顯示在屏幕上,完全是因為它內部的CALayer對象。在創建UIView對象時,UIView內部會自動創建一個層(即CALayer對象),通過UIView的layer屬性可以訪問這個層。當UIView需要顯示到屏幕上時,會調用drawRect:方法進行繪圖,并且會將所有內容繪制在自己的層上,繪圖完畢后,系統會將層拷貝到屏幕上,于是就完成了UIView的顯示。UIView本身不具備顯示的功能,顯示的是UIView的CALayer。
UIView可以通過subviews屬性訪問所有的子視圖,同樣地,CALayer也可以通過sublayers屬性訪問所有的子層;UIView可以通過superview屬性訪問父視圖,同樣地,CALayer也可以通過superlayer屬性訪問父層。
UIView可以通過addSubview:方法添加子視圖,同樣地,CALayer可以通過addSublayer:方法添加子層。

在使用時,往往CALayer和UIView都能實現相同的顯示效果,但是UIView具有交互的能力,可以處理事件。如果顯示出來的東西需要跟用戶進行交互的話,那么就要使用用UIView。

3.常用屬性

屬性 說明 是否支持隱式動畫
anchorPoint 和中心點position重合的一個點,稱為“錨點”,錨點的描述是相對于x、y位置比例而言的默認在圖像中心點(0.5,0.5)的位置
backgroundColor 圖層背景顏色
borderColor 邊框顏色
borderWidth 邊框寬度
bounds 圖層大小
contents 圖層顯示的內容
contentsRect 圖層顯示內容的大小和位置
cornerRadius 圓角半徑
doubleSided 圖層背面是否顯示,默認為YES
frame 圖層大小和位置
hidden 是否隱藏
mask 圖層蒙版
maskToBounds 子圖層是否剪切圖層邊界,默認為NO
opacity 透明度
position 圖層中心點位置
shadowColor 陰影顏色
shadowOffset 陰影偏移量
shadowOpacity 陰影透明度,默認為0,設置陰影必須設置此屬性
shadowPath 陰影的形狀
shadowRadius 陰影模糊半徑
sublayers 子圖層
sublayerTransform 子圖層形變
transform 圖層形變

注:

  • 在CALayer中很少使用frame屬性,因為frame本身不支持隱式動畫,通常使用bounds和position代替
  • anchorPoint屬性是圖層的錨點,范圍在(01,01)表示在x、y軸的比例,這個點決定著CALayer身上的哪個點會與position所指定的位置重合,當圖層中心點固定后,調整anchorPoint即可達到調整圖層顯示位置的作用(因為它永遠和position重合)

4.隱式動畫屬性

每一個UIView內部都默認關聯著一個CALayer,這個CALayer為Root Layer(根層)。對于所有的非Root Layer,即手動創建的CALayer對象,當對非Root Layer的部分屬性進行相應的修改時,默認會自動產生一些動畫效果,這些屬性稱為Animatable Properties(可動畫屬性)。隱式屬性動畫的本質是這些屬性的變動默認隱含了CABasicAnimation動畫實現,例如修改bounds屬性會產生縮放動畫,修改backgroundColor屬性會產生背景色的漸變動畫,修改position屬性會產生平移動畫。

可以通過動畫事務(CATransaction)關閉默認的隱式動畫效果
[CATransactionbegin];
[CATransactionsetDisableActions:YES];
self.myview.layer.position= CGPointMake(100, 100);
[CATransactioncommit];

二、簡單使用

1. 設置陰影

  imageView.layer.shadowColor = [UIColor grayColor].CGColor;  //陰影顏色
  imageView.layer.shadowOffset = CGSizeMake(10, 10); //陰影偏移量
  imageView.layer.shadowOpacity = 0.5;  //陰影透明度

2.設置圓角

 imageView.layer.masksToBounds = YES;   
 imageView.layer.cornerRadius = 10;     //當cornerRadius等于邊長的一半時,就是圓

3.設置邊框

 imageView.layer.borderWidth = 5;
 imageView.layer.borderColor = [UIColor redColor].CGColor;

4.設置旋轉

 imageView.layer.transform = CATransform3DMakeRotation(M_PI_4, 0, 0, 1);// 順時針旋轉45

5.繪制圖層

繪制圖層有兩種方法

1.通過圖層代理drawLayer: inContext:方法繪制
2.通過自定義圖層drawInContext:方法繪制
不管使用哪種方法繪制完必須調用圖層的setNeedDisplay方法.

① 使用代理方法繪制圖層
通過代理方法進行圖層繪圖只要指定圖層的代理,然后在代理對象中重寫-(void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx方法即可。需要注意這個方法雖然是代理方法但是不用手動實現CALayerDelegate,因為CALayer定義中給NSObject做了分類擴展,所有的NSObject都包含這個方法。另外設置完代理后必須要調用圖層的setNeedDisplay方法,否則繪制的內容無法顯示。

- (void)viewDidLoad {
    [super viewDidLoad];

    CALayer * layer = [[CALayer alloc]init];
    layer.bounds = CGRectMake(0, 0, 100, 100);
    layer.position = CGPointMake(50, 50);
    layer.masksToBounds = YES;
    layer.cornerRadius = 50;
    layer.borderWidth = 2;
    layer.borderColor = [UIColor yellowColor].CGColor;

    layer.delegate = self; //設置代理
    [self.view.layer addSublayer:layer]; //添加圖層
    [layer setNeedsDisplay]; //重繪
}

- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx{
    CGContextSaveGState(ctx);
    CGContextScaleCTM(ctx, 1, -1); //轉換坐標系
    CGContextTranslateCTM(ctx, 0, -100);
    UIImage * image = [UIImage imageNamed:@"me"];
    CGContextDrawImage(ctx, CGRectMake(0, 0, 100, 100), image.CGImage);
    CGContextRestoreGState(ctx);
}

② 使用自定義圖層繪制圖層
創建一個CALayer的子類,然后重寫drawInContext:方法,在方法中使用Quartz2D API進行繪圖;
同樣,使用時,必須要調用圖層的setNeedDisplay方法,才會觸發drawInContext:方法的調用。

Core Animation

一、簡介

1.概念

Core Animation 是作用在CALayer上的,使用Core Animation 創建動畫不僅簡單,而且還有更好的性能,因為Core Animation 是在單獨的線程中完成的,不會阻塞主線程,而且只會重繪界面上變化的部分(局部刷新)。

2.分類

類別 說明
CAAnimation 所有動畫類的基類,不能直接使用,實現了CAMediaTiming協議,提供了動畫的持續時間、速度和重復計數等,還實現了CAAction協議,該協議為CALayer動畫觸發的動作提供標準化響應。
CATransition CAAnimation的子類,轉場動畫,能夠為層提供移出屏幕和移入屏幕的動畫效果
CAAnimationGroup CAAnimation的子類,動畫組,是一種組合模式設計,可以通過動畫組來進行所有動畫行為的統一控制,組中所有動畫效果可以并發執行。
CAPropertyAnimation CAAnimation的子類,屬性動畫的基類,通過控制可動畫屬性慢慢地變化,不能直接使用。有兩個子類CABasicAnimation 和 CAKeyframeAnimation。
CABasicAnimation CAPropertyAnimation 的子類,基礎動畫,簡單地控制CALayer層的屬性慢慢改變,從而實現動畫。
CAKeyframeAnimation CAPropertyAnimation 的子類,關鍵幀動畫,同樣是通過屬性進行動畫參數控制,通過values屬性指定多個關鍵幀,通過多個關鍵幀可以指定動畫的各階段的關鍵值。

基礎動畫、關鍵幀動畫都屬于屬性動畫,是通過修改屬性值產生動畫效果,只需要設置初始值和結束值,中間的過程動畫(“補間動畫”)是由系統自動計算產生。和基礎動畫不同的是關鍵幀動畫可以設置多個屬性值,每兩個屬性中間的補間動畫由系統自動完成,基礎動畫也可以看成是有兩個關鍵幀的關鍵幀動畫。

二、使用

1. CAAnimation

CAAnimation 是基類,其屬性它的子類也擁有。它不能直接使用。

屬性 說明
beginTime 可以用來設置動畫延遲執行時間,若想延遲2s,就設置為CACurrentMediaTime()+2,CACurrentMediaTime()為圖層的當前時間
duration 動畫的持續時間
delegate 動畫代理
repeatCount 重復次數,無限循環可以設置HUGE_VALF或者MAXFLOAT
repeatDuration 重復時間
removedOnCompletion 默認為YES,代表動畫執行完畢后就從圖層上移除,圖形會恢復到動畫執行前的狀態。如果想讓圖層保持顯示動畫執行后的狀態,那就設置為NO,同時還要設置fillMode為kCAFillModeForwards
fillMode 決定當前對象在非active時間段的行為。(要想fillMode有效,最好設置removedOnCompletion = NO),kCAFillModeRemoved 這個是默認值,也就是說當動畫開始前和動畫結束后,動畫對layer都沒有影響,動畫結束后,layer會恢復到之前的狀態;kCAFillModeForwards 當動畫結束后,layer會一直保持著動畫最后的狀態;kCAFillModeBackwards 在動畫開始前,只需要將動畫加入了一個layer,layer便立即進入動畫的初始狀態并等待動畫開始;kCAFillModeBoth 這個其實就是上面兩個的合成.動畫加入后開始之前,layer便處于動畫初始狀態,動畫結束后layer保持動畫最后的狀態
timingFunction 速度控制函數,控制動畫運行的節奏,為 CAMediaTimingFunction 類型。kCAMediaTimingFunctionLinear(線性):勻速動畫;kCAMediaTimingFunctionEaseIn(漸進):動畫緩慢進入,然后加速離開;kCAMediaTimingFunctionEaseOut(漸出):動畫全速進入,然后減速的到達目的地;kCAMediaTimingFunctionEaseInEaseOut(漸進漸出):動畫緩慢的進入,中間加速,然后減速的到達目的地。

代理方法 : 給NSObject添加了分類,所以任何對象,都可以成為CAAnimation的代理。

動畫開始的時候調用 - (void)animationDidStart:(CAAnimation *)anim;
動畫停止的時候調用 - (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag;

2.CAPropertyAnimation

屬性或方法 說明
+ (instancetype)animationWithKeyPath:(nullable NSString *)path; 類方法,創建一個新的動畫對象,同時設置該對象的keyPath屬性
keyPath 屬性,用來描述動畫是作用在哪一個屬性上
additive 屬性,指定該屬性動畫是否以當前動畫效果為基礎
cumulative 屬性,指定動畫是否為累加效果
valueFunction 該屬性值是一個 CAValueFunction 對象,負責對屬性改變進行插值計算,系統已經提供了默認的插值計算方式,一般無須指定該屬性。

位移動畫通常使用屬性動畫控制 CALayer 的 position 屬性持續改變,如果要實現 CALayer 的旋轉、縮放等動畫效果,若在平面上(二維空間)上,則修改 CALayer 的 affineTransform 屬性,該屬性值為一個CGAffineTransform 對象;若在三維空間上,則修改 CALayer 的 transform 屬性,該屬性值為一個 CATransform3D 對象。

CATransform3D : 三維變換矩陣

方法 說明
CATransform3DIsIdentity (CATransform3D t) 返回bool值,判斷 t 矩陣是否為單位矩陣
CATransform3DEqualToTransform (CATransform3D a, CATransform3D b) 返回bool值,判斷 a , b 兩個矩陣是否相等
CATransform3DMakeTranslation (CGFloat tx, CGFloat ty, CGFloat tz) 創建一個在X方向上移動 tx、在Y方向上移動 ty、在Z方向上移動 tz 的變化矩陣
CATransform3DMakeScale (CGFloat sx, CGFloat sy, CGFloat sz) 創建一個在X方向上縮放 sx、在Y方向上縮放 sy、在Z方向上縮放 sz 的變化矩陣
CATransform3DMakeRotation (CGFloat angle, CGFloat x, CGFloat y, CGFloat z) 創建基于指定旋轉軸旋轉 angle 弧度的變換,x、y、z 用于確定旋轉軸,(1,0,0)指定旋轉軸為X軸,(0,1,0)指定旋轉軸為Y軸,(0,0,1)指定旋轉軸為Z軸
CATransform3DTranslate (CATransform3D t, CGFloat tx, CGFloat ty, CGFloat tz) 在 t 變化矩陣的基礎上進行位移變換
CATransform3DScale (CATransform3D t, CGFloat sx, CGFloat sy, CGFloat sz) 在 t 變化矩陣的基礎上進行縮放變換
CATransform3DRotate (CATransform3D t, CGFloat angle, CGFloat x, CGFloat y, CGFloat z) 在 t 變化矩陣的基礎上進行旋轉變換
CATransform3DConcat (CATransform3D a, CATransform3D b) 將a,b兩個矩陣相乘
CATransform3DInvert (CATransform3D t) 對 t 矩形進行反轉
CATransform3DMakeAffineTransform (CGAffineTransform m) 將 CGAffineTransform 矩陣轉化成 CATransform3D 矩陣,轉化后也只有X,Y維度上的變換
CATransform3DIsAffine (CATransform3D t) 返回bool值,判斷 t 矩陣是否是 CGAffineTransform 變換矩陣
CATransform3DGetAffineTransform (CATransform3D t) 獲取 t 矩陣所包含的 CGAffineTransform 變換矩陣

3.CABasicAnimation 和 CAKeyframeAnimation

CAPropertyAnimation的子類,首先創建初始化一個CALayer,初始化動畫CABasicAnimation是設置 fromValue 和 toValue ,CAKeyframeAnimation是設置 values ,再設置其他動畫屬性,將動畫添加到圖層上。

CALayer 為動畫提供的方法

方法 說明
- (void)addAnimation:(CAAnimation *)anim forKey:(nullable NSString *)key 為 CALayer 添加一個動畫,并為動畫指定一個唯一標識
- (nullable CAAnimation *)animationForKey:(NSString *)key 讓 CALayer 執行 key 所對應的動畫
- (void)removeAllAnimations 刪除 CALayer 上添加的所有動畫
- (void)removeAnimationForKey:(NSString *)key 刪除 key 所對應的動畫
- (nullable NSArray<NSString *> *)animationKeys 獲取 CALayer 上所有動畫的 key 組成的數組

① 位移動畫
CABasicAnimation

CABasicAnimation * anim = [CABasicAnimation animationWithKeyPath:@"position"];
anim.fromValue = [NSValue valueWithCGPoint:self.layer.position];
CGPoint toPoint = CGPointMake(300, 300);
anim.toValue = [NSValue valueWithCGPoint:toPoint];
anim.duration = 1.0;
self.layer.position = toPoint;
anim.removedOnCompletion = YES;
[self.layer addAnimation:anim forKey:@"KCBasicAnimation_Position"];

CAKeyframeAnimation

CAKeyframeAnimation * keyframeAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
NSValue * value1 = [NSValue valueWithCGPoint:self.layer.position];
NSValue * value2 = [NSValue valueWithCGPoint:CGPointMake(80, 220)];
NSValue * value3 = [NSValue valueWithCGPoint:CGPointMake(45, 300)];
NSValue * value4 = [NSValue valueWithCGPoint:CGPointMake(55, 400)];
NSArray * values=@[value1,value2,value3,value4];
keyframeAnimation.values = values;
keyframeAnimation.duration = 2.0;
[self.layer addAnimation:keyframeAnimation forKey:@"KCKeyframeAnimation_Position"];    

通過描繪路徑進行關鍵幀動畫控制

CAKeyframeAnimation * keyframeAnimation=[CAKeyframeAnimation animationWithKeyPath:@"position"];
CGMutablePathRef path = CGPathCreateMutable();
CGPathMoveToPoint(path, NULL, self.layer.position.x, self.layer.position.y);
CGPathAddCurveToPoint(path, NULL, 160, 280, -30, 300, 55, 400);
keyframeAnimation.path = path;
CGPathRelease(path);
keyframeAnimation.duration = 4.0;
keyframeAnimation.removedOnCompletion = YES;
[self.layer addAnimation:keyframeAnimation forKey:@"KCKeyframeAnimation_Position"];

② 旋轉動畫
CABasicAnimation

CABasicAnimation * anim = [CABasicAnimation animationWithKeyPath:@"transform"];
CATransform3D fromValue = self.layer.transform;
anim.fromValue = [NSValue valueWithCATransform3D:fromValue];
CATransform3D toValue = CATransform3DRotate(fromValue, M_PI, 1, 0, 0);
anim.toValue = [NSValue valueWithCATransform3D:toValue];
anim.duration = 0.5;
self.layer.transform = toValue;
anim.removedOnCompletion = YES;
[self.layer addAnimation:anim forKey:@"KCBasicAnimation_Rotate"];

CAKeyframeAnimation

CAKeyframeAnimation * keyframeAnimation = [CAKeyframeAnimation animationWithKeyPath:@"transform"];
NSValue * value1 = [NSValue valueWithCATransform3D:self.layer.transform];
NSValue * value2 = [NSValue valueWithCATransform3D:CATransform3DMakeRotation(M_PI, 1, 0, 0)];
NSValue * value3 = [NSValue valueWithCATransform3D:CATransform3DMakeRotation(M_PI, 0, 1, 0)];
NSValue * value4 = [NSValue valueWithCATransform3D:CATransform3DMakeRotation(M_PI, 0, 0, 1)];
NSArray * values = @[value1,value2,value3,value4];
keyframeAnimation.values = values;
keyframeAnimation.duration = 2.0;
[self.layer addAnimation:keyframeAnimation forKey:@"KCKeyframeAnimation_Rotate"];

③ 縮放動畫
CABasicAnimation

CABasicAnimation * anim = [CABasicAnimation animationWithKeyPath:@"transform"];
CATransform3D fromValue = self.layer.transform;
anim.fromValue = [NSValue valueWithCATransform3D:fromValue];
CATransform3D toValue = CATransform3DScale(fromValue, 0.5, 0.5, 1);
anim.toValue = [NSValue valueWithCATransform3D:toValue];
anim.duration = 0.5;
self.layer.transform = toValue;
anim.removedOnCompletion = YES;
[self.layer addAnimation:anim forKey:@"KCBasicAnimation_Scale"];

CAKeyframeAnimation

CAKeyframeAnimation * keyframeAnimation = [CAKeyframeAnimation animationWithKeyPath:@"transform"];
NSValue * value1 = [NSValue valueWithCATransform3D:self.layer.transform];
NSValue * value2 = [NSValue valueWithCATransform3D:CATransform3DMakeScale(0.5, 0.5, 1)];
NSValue * value3 = [NSValue valueWithCATransform3D:CATransform3DMakeScale(1, 1, 1)];
NSArray * values = @[value1,value2,value3];
keyframeAnimation.values = values;
keyframeAnimation.duration = 2.0;
[self.layer addAnimation:keyframeAnimation forKey:@"KCKeyframeAnimation_Scale"];

④ 動畫組
CABasicAnimation

CGPoint fromPoint = self.layer.position;
CGPoint toPoint = CGPointMake(280, fromPoint.y + 300);

CABasicAnimation * moveAnim = [CABasicAnimation animationWithKeyPath:@"position"];
moveAnim.fromValue = [NSValue valueWithCGPoint:fromPoint];
moveAnim.toValue = [NSValue valueWithCGPoint:toPoint];
moveAnim.removedOnCompletion = YES;

CABasicAnimation * transformAnim = [CABasicAnimation animationWithKeyPath:@"transform"];
CATransform3D fromValue = self.layer.transform;
transformAnim.fromValue = [NSValue valueWithCATransform3D:fromValue];
CATransform3D scaleValue = CATransform3DScale(fromValue, 0.5, 0.5, 1);
CATransform3D rotateValue = CATransform3DRotate(fromValue, M_PI, 0, 0, 1);
CATransform3D toValue = CATransform3DConcat(scaleValue, rotateValue);
transformAnim.toValue = [NSValue valueWithCATransform3D:toValue];
transformAnim.cumulative = YES;
transformAnim.repeatCount = 2;
transformAnim.duration = 3;

CAAnimationGroup * animGroup = [CAAnimationGroup animation];
animGroup.animations = @[moveAnim,transformAnim];
animGroup.duration = 6;
[self.layer addAnimation:animGroup forKey:@"KCBasicAnimation_Group"];

CAKeyframeAnimation

CAKeyframeAnimation * moveAnim = [CAKeyframeAnimation animationWithKeyPath:@"position"];
NSValue * move1 = [NSValue valueWithCGPoint:self.layer.position];
NSValue * move2 = [NSValue valueWithCGPoint:CGPointMake(300, 100)];
NSValue * move3 = [NSValue valueWithCGPoint:CGPointMake(300, 300)];
NSValue * move4 = [NSValue valueWithCGPoint:CGPointMake(100, 300)];
NSValue * move5 = [NSValue valueWithCGPoint:CGPointMake(100, 100)];
NSArray * values = @[move1,move2,move3,move4,move5];
moveAnim.values = values;
moveAnim.removedOnCompletion = YES;

CAKeyframeAnimation * transformAnim = [CAKeyframeAnimation animationWithKeyPath:@"transform"];
CATransform3D rotate1 = self.layer.transform;
CATransform3D rotate2 = CATransform3DMakeRotation(M_PI, 1, 0, 0);
CATransform3D rotate3 = CATransform3DMakeRotation(M_PI, 0, 1, 0);
CATransform3D rotate4 = CATransform3DMakeRotation(M_PI, 0, 0, 1);
CATransform3D rotate5 = CATransform3DMakeRotation(M_PI, 1, 1, 1);

CATransform3D scale1 = self.layer.transform;
CATransform3D scale2 = CATransform3DMakeScale(0.5, 0.5, 1);
CATransform3D scale3 = CATransform3DMakeScale(1, 1, 1);
CATransform3D scale4 = CATransform3DMakeScale(0.5, 0.5, 1);
CATransform3D scale5 = CATransform3DMakeScale(1, 1, 1);

NSValue * transform1 = [NSValue valueWithCATransform3D:CATransform3DConcat(rotate1, scale1)];
NSValue * transform2 = [NSValue valueWithCATransform3D:CATransform3DConcat(rotate2, scale2)];
NSValue * transform3 = [NSValue valueWithCATransform3D:CATransform3DConcat(rotate3, scale3)];
NSValue * transform4 = [NSValue valueWithCATransform3D:CATransform3DConcat(rotate4, scale4)];
NSValue * transform5 = [NSValue valueWithCATransform3D:CATransform3DConcat(rotate5, scale5)];
transformAnim.values = @[transform1,transform2,transform3,transform4,transform5];
transformAnim.removedOnCompletion = YES;

CAAnimationGroup * animGroup = [CAAnimationGroup animation];
animGroup.animations = @[moveAnim,transformAnim];
animGroup.duration = 8;
self.layer.position = CGPointMake(100, 100);
[self.layer addAnimation:animGroup forKey:@"KCBasicAnimation_Group"];

④ 動畫暫停

-(void)pauseLayer{
    CFTimeInterval interval=[self.layer convertTime:CACurrentMediaTime() fromLayer:nil];
    [self.layer setTimeOffset:interval];
    self.layer.speed=0;
}

⑤ 動畫恢復

-(void)resumeLayer{
    CFTimeInterval beginTime= CACurrentMediaTime() - self.layer.timeOffset;
    self.layer.timeOffset = 0;
    self.layer.beginTime = beginTime;
    self.layer.speed=1.0;
}

4.CATransition

屬性 說明
type 動畫過渡效果
subtype 動畫過渡方向
startProgress 動畫起點(在整體動畫的百分比)
endProgress 動畫終點(在整體動畫的百分比)

CATransition的type屬性用于控制動畫類型,還有私有動畫。

值| 說明 |是否支持方向設置
-----|-----
kCATransitionFade|通過漸隱效果過渡,默認屬性值|是
kCATransitionMoveIn|通過移入動畫過渡,新視圖移到舊視圖上面|是
kCATransitionPush|通過推入動畫過渡,新視圖把舊視圖推出去|是
kCATransitionReveal|通過揭開動畫過渡,將舊視圖移開,同時顯示下面的新視圖|是
@"cube"|通過立方體旋轉動畫過渡|是
@"oglFlip"|通過翻轉動畫過渡|是
@"suckEffect"|通過收縮(吸入)動畫過渡|否
@"rippleEffect"|通過水波動畫過渡|否
@"pageCurl"|通過向后翻頁動畫過渡|是
@"pageUnCurl"|通過向前翻頁動畫過渡|是
@"cameraIrisHollowOpen"|通過相機鏡頭打開效果動畫過渡|否
@"cameraIrisHollowClose"|通過相機鏡頭關閉效果動畫過渡|否

CATransition的subtype屬性用于控制動畫方向

說明
kCATransitionFromRight 從右側轉場
kCATransitionFromLeft 從左側轉場
kCATransitionFromTop 從頂部轉場
kCATransitionFromBottom 從底部轉場
#import "ViewController.h"
#define IMAGE_COUNT 5

typedef enum {
    GestureDirectionUp = 1,
    GestureDirectionDown,
    GestureDirectionLeft,
    GestureDirectionRight
}GestureDirection;

@interface ViewController () <UIPickerViewDelegate,UIPickerViewDataSource>

@property (nonatomic,strong) UIImageView * imageView;
@property (nonatomic,assign) int currentIndex;
@property (nonatomic,strong) CATransition * transition;
@property (nonatomic,strong) UIPickerView * pickerView;
@property (nonatomic,copy) NSArray * transitions;
@property (nonatomic,assign) BOOL isPop;

@end

@implementation ViewController

- (NSArray *)transitions{
    if (_transitions == nil) {
        _transitions = @[kCATransitionFade,kCATransitionMoveIn,kCATransitionPush,kCATransitionReveal,@"cube",@"oglFlip",@"suckEffect",@"rippleEffect",@"pageCurl",@"pageUnCurl",@"cameralIrisHollowOpen",@"cameraIrisHollowClose"];
    }
    return _transitions;
}

- (void)viewDidLoad {
    [super viewDidLoad];

    self.view.backgroundColor = [UIColor whiteColor];

    CGFloat screenWidth = [UIScreen mainScreen].bounds.size.width;
    CGFloat screenHeight = [UIScreen mainScreen].bounds.size.height;

    self.navigationItem.title = @"轉場";
    UIBarButtonItem * rightBarButtonItem = [[UIBarButtonItem alloc]initWithTitle:@"效果" style:UIBarButtonItemStyleDone target:self action:@selector(selectTransition:)];
    self.navigationItem.rightBarButtonItem = rightBarButtonItem;

    self.imageView = [[UIImageView alloc]initWithFrame:self.view.frame];
    self.imageView.backgroundColor = [UIColor whiteColor];
    self.imageView.contentMode = UIViewContentModeScaleAspectFit;
    self.imageView.image = [UIImage imageNamed:@"0.jpg"];
    [self.view addSubview:self.imageView];

    self.pickerView = [[UIPickerView alloc]initWithFrame:CGRectMake(0, screenHeight, screenWidth, 300)];
    self.pickerView.dataSource = self;
    self.pickerView.delegate = self;
    [self.view addSubview:self.pickerView];

    UISwipeGestureRecognizer * upSwipeGesture = [[UISwipeGestureRecognizer alloc]initWithTarget:self action:@selector(swipe:)];
    [upSwipeGesture setDirection:UISwipeGestureRecognizerDirectionUp];
    [self.view addGestureRecognizer:upSwipeGesture];

    UISwipeGestureRecognizer * downSwipeGesture = [[UISwipeGestureRecognizer alloc]initWithTarget:self action:@selector(swipe:)];
    [downSwipeGesture setDirection:UISwipeGestureRecognizerDirectionDown];
    [self.view addGestureRecognizer:downSwipeGesture];

    UISwipeGestureRecognizer * leftSwipeGesture = [[UISwipeGestureRecognizer alloc]initWithTarget:self action:@selector(swipe:)];
    [leftSwipeGesture setDirection:UISwipeGestureRecognizerDirectionLeft];
    [self.view addGestureRecognizer:leftSwipeGesture];

    UISwipeGestureRecognizer * rightSwipeGesture = [[UISwipeGestureRecognizer alloc]initWithTarget:self action:@selector(swipe:)];
    [rightSwipeGesture setDirection:UISwipeGestureRecognizerDirectionRight];
    [self.view addGestureRecognizer:rightSwipeGesture];

    self.transition = [[CATransition alloc]init];
    self.transition.type = kCATransitionFade;
    self.isPop = NO;
}

- (void)selectTransition:(UIBarButtonItem *)btn{
    self.isPop = !self.isPop;
    if (self.isPop) {
        CGContextRef context = UIGraphicsGetCurrentContext();
        [UIView beginAnimations:nil context:context];
        [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
        [UIView setAnimationDuration:0.6];
        [self.view bringSubviewToFront:self.pickerView];
        CGFloat screenHeight = [UIScreen mainScreen].bounds.size.height;
        CGFloat pickerViewWidth = [UIScreen mainScreen].bounds.size.width;
        CGFloat pickerViewHeight = 300;
        self.pickerView.frame = CGRectMake(0, screenHeight - pickerViewHeight, pickerViewWidth, pickerViewHeight);
        [UIView commitAnimations];
}
else {
        CGContextRef context = UIGraphicsGetCurrentContext();
        [UIView beginAnimations:nil context:context];
        [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
        [UIView setAnimationDuration:0.6];
        CGFloat screenHeight = [UIScreen mainScreen].bounds.size.height;
        CGFloat pickerViewWidth = [UIScreen mainScreen].bounds.size.width;
        CGFloat pickerViewHeight = 300;
        self.pickerView.frame = CGRectMake(0, screenHeight, pickerViewWidth, pickerViewHeight);
        [UIView commitAnimations];
    }
}

-(void)swipe:(UISwipeGestureRecognizer *)gesture{
    if (gesture.direction == UISwipeGestureRecognizerDirectionUp){
        [self transitionAnimation:GestureDirectionUp];
    }
    else if (gesture.direction == UISwipeGestureRecognizerDirectionDown){
        [self transitionAnimation:GestureDirectionDown];
    }
    else if (gesture.direction == UISwipeGestureRecognizerDirectionLeft){
        [self transitionAnimation:GestureDirectionLeft];
    }
    else {
        [self transitionAnimation:GestureDirectionRight];
    }
}

- (void)transitionAnimation:(GestureDirection)gestureDirection{
    switch (gestureDirection) {
        case GestureDirectionUp:
            self.transition.subtype = kCATransitionFromTop;
            break;
        case GestureDirectionDown:
            self.transition.subtype = kCATransitionFromBottom;
           break;
        case GestureDirectionLeft:
            self.transition.subtype = kCATransitionFromRight;
            break;
        case GestureDirectionRight:
            self.transition.subtype = kCATransitionFromLeft;
            break;
        default:
            break;
    }
    self.transition.duration = 1.0f;
    self.imageView.image = [self getImage];
    [self.view.layer addAnimation:self.transition forKey:@"KCTransitionAnimation"];
}

- (UIImage *)getImage{
    self.currentIndex = (self.currentIndex + 1) % IMAGE_COUNT;
    NSString *imageName=[NSString stringWithFormat:@"%i.jpg",_currentIndex];
    return [UIImage imageNamed:imageName];
}

- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView{
    return 1;
}

- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component{
    return self.transitions.count;
}

- (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component{
    return self.transitions[row];
}

- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component{
    self.transition.type = self.transitions[row];
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

@end

5.逐幀動畫

通過設置UIImageView的animationImages屬性,然后調用它的startAnimating方法去播放一組逐幀圖片,但是它存在著很大的性能問題,并且這種方法一旦設置完圖片中間的過程就無法控制了。使用CADisplayLink加入到主循環隊列中,循環調用目標方法,在這個方法中更新視圖內容就可以完成逐幀動畫。

- (void)runAnimationWithImageCount:(NSInteger)count AndImageName:(NSString *)name
{
    if (self.myImageView.isAnimating)
    {
        return;
    }
    NSMutableArray * pictArray = [NSMutableArray array];
    for (int i = 0; i < count; i++)
    {
        NSString * pictNum = [NSString stringWithFormat:@"%@_%02d.jpg",name,i];
        // 使用imageNamed的方式加載圖片會有緩存,使得占用的內存很大
        //[pictArray addObject:[UIImage imageNamed:pictNum]];
    
        // 直接從文件中讀取圖片就不會占用太大的內存
        NSBundle * boudle = [NSBundle mainBundle];
        NSString * path = [boudle pathForResource:pictNum ofType:nil];
        [pictArray addObject:[UIImage imageWithContentsOfFile:path]];
    }
    self.myImageView.animationImages = pictArray;
    self.myImageView.animationDuration = (self.myImageView.animationImages.count * 0.07);
    CGFloat delayTime = self.myImageView.animationDuration + 0.5;
    self.myImageView.animationRepeatCount = 1;
    [self.myImageView startAnimating];

    [self.myImageView performSelector:@selector(setAnimationImages:) withObject:nil afterDelay:delayTime];
}

6.UIView動畫封裝

UIView本身對于基本動畫和關鍵幀動畫、轉場動畫都有相應的封裝。

方法 說明
+ (void)beginAnimations:(nullable NSString *)animationID context:(nullable void *)context 開始動畫,animationID :動畫塊內部應用程序標識,context :附加的信息,都傳遞給動畫代理
+ (void)commitAnimations 提交動畫,結束動畫
+ (void)setAnimationDelegate:(nullable id)delegate 設置動畫的代理
+ (void)setAnimationWillStartSelector:(nullable SEL)selector 設置動畫開始時的執行方法
+ (void)setAnimationDidStopSelector:(nullable SEL)selector 設置動畫結束時的執行方法
+ (void)setAnimationDuration:(NSTimeInterval)duration 設置動畫執行時間
+ (void)setAnimationDelay:(NSTimeInterval)delay 設置動畫延遲執行的時間
+ (void)setAnimationStartDate:(NSDate *)startDate 設置動畫開始執行的時間
+ (void)setAnimationCurve:(UIViewAnimationCurve)curve 設置動畫運行過程中相對的速度
+ (void)setAnimationRepeatCount:(float)repeatCount 設置動畫重復次數
+ (void)setAnimationRepeatAutoreverses:(BOOL)repeatAutoreverses 設置是否自動逆向動畫
+ (void)setAnimationBeginsFromCurrentState:(BOOL)fromCurrentState 設置動畫是否從當前狀態開始執行,設置為YES,則當上一次動畫正在執行中,那么當下一個動畫開始時,上一次動畫的當前狀態將成為下一次動畫的開始狀態。設置為NO,則當上一個動畫正在執行中,那么當下一個動畫開始時,上一次動畫需要先恢復到完成時的狀態,然后在開始執行下一次動畫
+ (void)setAnimationsEnabled:(BOOL)enabled 設置動畫是否可用
+ (BOOL)areAnimationsEnabled 獲取動畫是否可用

動畫使用block方式

方法 說明
+ (void)animateWithDuration:(NSTimeInterval)duration animations:(void (^)(void))animations 使用block方式
+ (void)animateWithDuration:(NSTimeInterval)duration animations:(void (^)(void))animations completion:(void (^ __nullable)(BOOL finished))completion 使用block方式,有完成后的block
+ (void)animateWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay options:(UIViewAnimationOptions)options animations:(void (^)(void))animations completion:(void (^ __nullable)(BOOL finished))completion 使用block方式,可以設置動畫延遲時間和動畫參數
+ (void)transitionWithView:(UIView *)view duration:(NSTimeInterval)duration options:(UIViewAnimationOptions)options animations:(void (^ __nullable)(void))animations completion:(void (^ __nullable)(BOOL finished))completion 轉場動畫
+ (void)transitionFromView:(UIView *)fromView toView:(UIView *)toView duration:(NSTimeInterval)duration options:(UIViewAnimationOptions)options completion:(void (^ __nullable)(BOOL finished))completion 轉場動畫,toView添加到fromView的父視圖中,fromView從它的福視圖中移除
+ (void)animateKeyframesWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay options:(UIViewKeyframeAnimationOptions)options animations:(void (^)(void))animations completion:(void (^ __nullable)(BOOL finished))completion 使用block方式實現關鍵幀動畫
+ (void)addKeyframeWithRelativeStartTime:(double)frameStartTime relativeDuration:(double)frameDuration animations:(void (^)(void))animations 從開始時間添加一個持續時間的動畫

UIViewAnimationCurve

說明
UIViewAnimationCurveEaseIn 先慢后快,在動畫剛開始的時候執行速度慢
UIViewAnimationCurveEaseOut 先快后慢,在動畫快結束的時候執行速度慢
UIViewAnimationCurveEaseInOut 先由慢變快,然后再變慢,在動畫剛開始和快結束的時候速度慢
UIViewAnimationCurveLinear 勻速運行

UIViewAnimationOptions

說明
UIViewAnimationOptionLayoutSubviews 動畫過程中保證子視圖跟隨運動
UIViewAnimationOptionAllowUserInteraction 動畫過程中允許用戶交互
UIViewAnimationOptionBeginFromCurrentState 所有視圖從當前狀態開始運行
UIViewAnimationOptionRepeat 重復執行動畫
UIViewAnimationOptionAutoreverse 逆向動畫,動畫運行到結束點后仍然以動畫方式回到初始點
UIViewAnimationOptionOverrideInheritedDuration 忽略嵌套動畫時間設置
UIViewAnimationOptionOverrideInheritedCurve 忽略嵌套動畫速度設置
UIViewAnimationOptionAllowAnimatedContent 動畫過程允許重繪視圖(僅適用在轉場動畫中)
UIViewAnimationOptionShowHideTransitionViews 視圖切換時翻轉隱藏舊視圖、顯示新視圖,而不再是直接刪除舊視圖、添加新視圖
UIViewAnimationOptionOverrideInheritedOptions 不繼承任何參數或動畫類型
UIViewAnimationOptionCurveEaseInOut 默認值,和UIViewAnimationCurveEaseInOut效果一樣,只是這個值用于block動畫參數,先由慢變快,然后再變慢,在動畫剛開始和快結束的時候速度慢
UIViewAnimationOptionCurveEaseIn 和UIViewAnimationCurveEaseIn效果一樣,先慢后快,在動畫剛開始的時候執行速度慢
UIViewAnimationOptionCurveEaseOut 和UIViewAnimationCurveEaseOut效果一樣,先快后慢,在動畫快結束的時候執行速度慢
UIViewAnimationOptionCurveLinear 和UIViewAnimationCurveLinear效果一樣,勻速運行
UIViewAnimationOptionTransitionNone 下面的值僅適用于轉場動畫設置,沒有轉場動畫效果
UIViewAnimationOptionTransitionFlipFromLeft 從左側翻轉
UIViewAnimationOptionTransitionFlipFromRight 從右側翻轉
UIViewAnimationOptionTransitionCurlUp 向后翻頁的動畫過渡
UIViewAnimationOptionTransitionCrossDissolve 水波動畫過渡
UIViewAnimationOptionTransitionFlipFromTop 從頂部翻轉
UIViewAnimationOptionTransitionFlipFromBottom 從底部翻轉

UIViewKeyframeAnimationOptions
其中一部分值和 UIViewAnimationOptions 是一樣的,不一樣的如下:

說明
UIViewKeyframeAnimationOptionCalculationModeLinear 連續運算模式
UIViewKeyframeAnimationOptionCalculationModeDiscrete 離散運算模式
UIViewKeyframeAnimationOptionCalculationModePaced 均勻執行運算模式
UIViewKeyframeAnimationOptionCalculationModeCubic 平滑運算模式
UIViewKeyframeAnimationOptionCalculationModeCubicPaced 平滑均勻運算模式

7.彈簧動畫

方法 說明
+ (void)animateWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay usingSpringWithDamping:(CGFloat)dampingRatio initialSpringVelocity:(CGFloat)velocity options:(UIViewAnimationOptions)options animations:(void (^)(void))animations completion:(void (^ __nullable)(BOOL finished))completion UIViewAnimationWithBlocks中的方法,用來創建具有彈簧效果的動畫。dampingRatio為阻尼,范圍0~1,阻尼越接近于0,彈性效果越明顯。velocity為彈性復位的速度。
[UIView animateWithDuration:5.0 delay:0 usingSpringWithDamping:0.1 initialSpringVelocity:1.0 options:UIViewAnimationOptionCurveLinear animations:^{
    self.imageView.center = CGPointMake(300, 300);
} completion:nil];
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容