1. iOS動畫
總的來說,從涉及類的形式來看,iOS動畫有:基於UIView的仿射形變動畫,基於CAAnimation及其子類的動畫,基於CG的動畫。這篇文章著重總結前兩種動畫。
2. UIView動畫
設定UIView形變動畫有兩種常見用到的屬性,.frame
,.transform
,所以有的人也可以分別稱之為:
- ① frame動畫
- ② transform動畫
這兩種動畫只需要在動畫語法中適當的位置,基於UIView和CALayer的屬性設定變化值即可。這種動畫,不需要 呼叫核心動畫CAAnimation裡面的專用類和API。
其中,frame動畫設定方式有限,必須確切地制定形變前後的frame,平移還好,特別是 旋轉 的時候,只能通過數學知識計算出新的frame。這就得不償失了。所以,更多的時候,當涉及一些frame,bounds,center的改變或是形變的時候可以用transform來取代frame。
2.1 設定UIView動畫的兩種語法形式
- begin --- commit
//偏移動畫
[UIView beginAnimations:@"move" context:nil];
[UIView setAnimationDuration:2];
[UIView setAnimationDelegate:self];
imageContainView.frame = CGRectMake(80, 80, 200, 200);
[label1 setBackgroundColor:[UIColor yellowColor]];
[label1 setTextColor:[UIColor redColor]];
[UIView commitAnimations];
複製程式碼
- animations block
//縮放動畫
view.transform = CGAffineTransformIdentity;
[UIView animateWithDuration:1.0f animations:^{
view.transform = CGAffineTransformMakeScale(2.0f, 2.0f);
}];
複製程式碼
2.2 設定屬性形變動畫的兩種型別
- UIView的 CGAffineTransform 型別屬性:animatedView**.transform** 一般是View的旋轉,拉伸移動等屬性,是二維的,通常使用都是字首CGAffineTransform的類。
CGAffineTransform transform = CGAffineTransformScale(imageContainView.transform, 1.2, 1.2);
[UIView beginAnimations: @"scale"context: nil];
[UIView setAnimationDuration: 2];
[UIView setAnimationDelegate: self];
[imageView setTransform: transform];
[UIView commitAnimations];
複製程式碼
- CALayer的CATransform3D 型別屬性:animaView**.layer.transform**
通過
.layer.transform
可以在3D模式下面的變化,通常使用的都是字首為CATransform3D的類。
imageView.layer.transform = CATransform3DIdentity;
[UIView animateWithDuration:1.0f animations:^{
imageView.layer.transform = CATransform3DMakeScale(2.0, 2.0, 1.0);
}];
複製程式碼
2.3 與動畫相關的屬性
2.3.1 UIView與動畫相關的屬性--與CGAffineTransform對應
下面是UIView的一些屬性介紹
@property(nonatomic) CGRect frame;
@property(nonatomic) CGRect bounds; // default bounds is zero origin, frame size. animatable
@property(nonatomic) CGPoint center; // center is center of frame. animatable
@property(nonatomic) CGAffineTransform transform; // default is CGAffineTransformIdentity. animatable
@property(nonatomic) CGFloat contentScaleFactor NS_AVAILABLE_IOS(4_0);
@property(nonatomic,getter=isMultipleTouchEnabled) BOOL multipleTouchEnabled __TVOS_PROHIBITED; // default is NO
@property(nonatomic,getter=isExclusiveTouch) BOOL exclusiveTouch __TVOS_PROHIBITED; // default is NO
複製程式碼
在實際開發中,使用場景:
-
(1) 當涉及一些frame, bounds, center的改變或是形變的時候可以用 transform 來取代 frame。
-
(2) 一般在實際開發中都是平移,旋轉,縮放組合使用。
2.3.2 CALayer與動畫相關的屬性--與CATransform3D對應
下面是CALayer的一些屬性介紹
//寬度和高度
@property CGRect bounds;
//位置(預設指中點,具體由anchorPoint決定)
@property CGPoint position;
//錨點(x,y的範圍都是0-1),決定了position的含義
@property CGPoint anchorPoint;
//背景顏色(CGColorRef型別)
@property CGColorRef backgroundColor;
//形變屬性
@property CATransform3D transform;
//邊框顏色(CGColorRef型別)
@property CGColorRef borderColor;
//邊框寬度
@property CGFloat borderWidth;
//圓角半徑
@property CGFloat cornerRadius;
//內容(比如設定為圖片CGImageRef)
@property(retain) id contents;
複製程式碼
2.4 管理二維形變和三維形變的封裝類:CGAffineTransform與CATransform3D
2.4.1 CGAffineTransform操作API
- CGAffineTransform結構體定義
struct CGAffineTransform {
CGFloat a, b, c, d;
CGFloat tx, ty;
};
複製程式碼
它其實表示的是一個矩陣:
因為最後一列總是是(0,0,1),所以有用的資訊就是前面兩列。對一個view進行仿射變化就相當於對view上的每個點做一個乘法,結果就是:
a表示x水平方向的縮放,tx表示x水平方向的偏移 d表示y垂直方向的縮放,ty表示y垂直方向的偏移 如果b和c不為零的話,那麼檢視肯定發生了旋轉,旋轉角度這樣計算:tan(angle) = b / a
如果這樣:
這個就是沒有變化的最初的樣子。
- CGAffineTransform操作API
//還原
CGAffineTransformIdentity
//位移仿射 ---- 理解為平移 (CGFloat tx,CGFloat ty)
CGAffineTransformMakeTranslation
CGAffineTransformTranslate
//旋轉仿射 ---- 理解為旋轉 (CGFloat angle)
CGAffineTransformMakeRotation
CGAffineTransformRotate
//縮放仿射 --- 理解縮放大小 (CGFloat sx, CGFloat sy)
CGAffineTransformMakeScale
CGAffineTransformScale
複製程式碼
CGAffineTransform操作的數學本質
- CGPoint轉換公式
- 矩陣乘法運算原理演示
2.4.2 CATransform3D操作API
//還原
CATransform3DIdentity
//位移3D仿射 ==> (CGFloat tx, CGFloat ty, CGFloat tz)
CATransform3DMakeTranslation
CATransform3DTranslation
//旋轉3D仿射 ==> (CGFloat angle, CGFloat x, CGFloat y, CGFloat z)
CATransform3DMakeRotation
CATransform3DRotation
//縮放3D仿射 ==> (CGFloat angle, CGFloat x, CGFloat y, CGFloat z)
CATransform3DMakeScale
CATransform3DScale
//疊加3D仿射效果
CATransform3DConcat
//仿射基礎3D方法,可以直接做效果疊加
CGAffineTransformMake (sx,shx,shy,sy,tx,ty)
//檢查是否有做過仿射3D效果 == ((CATransform3D t))
CATransform3DIsIdentity(transform)
//檢查2個3D仿射效果是否相同
CATransform3DEqualToTransform(transform1,transform2)
//3D仿射效果反轉(反效果,比如原來擴大,就變成縮小)
CATransform3DInvert(transform)
複製程式碼
2.4.3 CATransform3D與CGAffineTransform相互轉換API
//將一個CGAffinrTransform轉化為CATransform3D
CATransform3D CATransform3DMakeAffineTransform (CGAffineTransform m);
//判斷一個CATransform3D是否可以轉換為CAAffineTransformbool
CATransform3DIsAffine (CATransform3D t);
//將CATransform3D轉換為CGAffineTransform
CGAffineTransform CATransform3DGetAffineTransform (CATransform3D t);
複製程式碼
2.5 “組合動畫” 與 CGAffineTransformConcat
2.5.1 連線設定多個屬性組合成一個動畫
連線設定兩個以上屬性的動畫,可以先呼叫含有 formMake
的API,然後再呼叫只含 form
的API。例如,這樣:
alertView.transform = CGAffineTransformMakeScale(.25, .25);
alertView.transform = CGAffineTransformTranslate(alertView.transform, 0, 600);
複製程式碼
2.5.2 利用CGAffineTransformConcat設定組合動畫
另外,可以直接利用 CGAffineTransformConcat 來組合多種含有 formMake
的形變API。
CGAffineTransform viewTransform = CGAffineTransformConcat(CGAffineTransformMakeScale(.25, .25), CGAffineTransformMakeTranslation(0, 600));
alertView.transform = viewTransform;
複製程式碼
關於組合3D形變也有相應的API --- CATransform3DConcat,關於3D形變下一篇會專門介紹。
- CGAffineTransformConcat 組合多種形變動畫小例子
- (void)viewDidAppear: (BOOL)animated {
/// 初始化動畫開始前label的位置
CGFloat offset = label1.frame.size.height * 0.5;
label1.transform = CGAffineTransformConcat(
CGAffineTransformMakeScale(0, 0),
CGAffineTransformTranslate(0, -offset)
);
label1.alpha = 0;
[UIView animateWithDuration: 3. animations: ^ {
/// 還原label1的變換狀態並形變和偏移label2
label1.transform = CGAffineTransformIdentifier;
label1.transform = CGAffineTransformConcat(
CGAffineTransformMakeScale(0, 0),
CGAffineTransformTranslate(0, offset)
);
label1.alpha = 1;
label2.alpha = 0;
}];
}
複製程式碼
-
組合變換的本質 CGAffineTransformConcat的數學本質是將括號內代表的若干變換的係數矩陣進行相乘。
-
另一種組合變換 基於已有的CGAffineTransform連續追加新的CGAffineTransform:
CGAffineTransform transform = CGAffineTransformIdentity;
transform = CGAffineTransformScale(transform, 0.5f, 0.5f);
transform = CGAffineTransformRotate(transform, 30.0f/180.0f*M_PI);
transform = CGAffineTransformTranslate(transform, 200.0f, 0.0f);
layerView.layer.affineTransform = transform;
複製程式碼
2.6 動畫後將屬性還原
當我們改變過一個view.transform屬性或者view.layer.transform的時候需要恢復預設狀態的話,記得先把他 們重置為:
view.transform = CGAffineTransformIdentity;
view.layer.transform = CATransform3DIdentity;
複製程式碼
2.7 注意點: transform對frame的影響
官方文件上關於transform屬性的說明:
Changes to this property can be animated. However, if the transform property contains a non-identity transform, the value of the frame property is undefined and should not be modified. In that case, you can reposition the view using the center property and adjust the size using the bounds property instead.
如果在程式中改變了某個控制元件的transform,那麼請不要使用這個控制元件的frame計算 子控制元件 的佈局,應該使用bounds+center代替。
3. CAAnimation核心動畫
CAAnimation——所有動畫物件的父類
3.1 設定動畫的一種語法形式
- addAnimation
/**
* 抖動效果
*/
-(void)shakeAnimation{
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.repeatCount = MAXFLOAT;
[_demoView.layer addAnimation:anima forKey:@"shakeAnimation"];
}
複製程式碼
3.2 CAAnimation繼承結構
CAAnimation{
CAPropertyAnimation{
CABasicAnimation{
CASpringAnimation
}
CAKeyframeAnimation
}
CATransition
CAAnimationGroup
}
複製程式碼
是所有動畫物件的父類,負責控制動畫的持續時間和速度,是個抽象類,不能直接使用,應該使用它具體的子類
3.3 CAAnimation類的屬性
帶*號代表來自CAMediaTiming協議的屬性)
- *duration:動畫的持續時間
- *repeatCount:重複次數,無限迴圈可以設定HUGE_VALF或者MAXFLOAT
- *repeatDuration:重複時間
- removedOnCompletion:預設為YES,代表動畫執行完畢後就從圖層上移除,圖形會恢復到動畫執行前的狀態。如果想讓圖層保持顯示動畫執行後的狀態,那就設定為NO,不過還要設定fillMode為kCAFillModeForwards
- *fillMode:決定當前物件在非active時間段的行為。比如動畫開始之前或者動畫結束之後
- *beginTime:可以用來設定動畫延遲執行時間,若想延遲2s,就設定為CACurrentMediaTime()+2,CACurrentMediaTime()為圖層的當前時間
- timingFunction:速度控制函式,控制動畫執行的節奏
- delegate:動畫代理
3.4 幾個重要屬性值
removedOnCompletion屬性值
CAAnimation——動畫填充模式
預設為YES,代表動畫執行完畢後就從圖層上移除,圖形會恢復到動畫執行前的狀態。如果想讓圖層保持顯示動畫執行後的狀態,那就設定為NO,不過還要設定fillMode為kCAFillModeForwards
fillMode屬性值
CAAnimation——控制恢復到動畫執行前
要想fillMode有效,最好設定removedOnCompletion = NO
- kCAFillModeRemoved 這個是預設值,也就是說當動畫開始前和動畫結束後,動畫對layer都沒有影響,動畫結束後,layer會恢復到之前的狀態
- kCAFillModeForwards 當動畫結束後,layer會一直保持著動畫最後的狀態
- kCAFillModeBackwards 在動畫開始前,只需要將動畫加入了一個layer,layer便立即進入動畫的初始狀態並等待動畫開始。
- kCAFillModeBoth 這個其實就是上面兩個的合成.動畫加入後開始之前,layer便處於動畫初始狀態,動畫結束後layer保持動畫最後的狀態
如果 fillMode = kCAFillModeForwards
同時 removedOnComletion = NO
,那麼在動畫執行完畢後,圖層會保持顯示動畫執行後的狀態。但在實質上,圖層的屬性值還是動畫執行前的初始值,並沒有真正被改變。
timingFunction屬性值
CAAnimation——動畫速度控制函式
- kCAMediaTimingFunctionLinear(線性):勻速,給你一個相對靜態的感覺
- kCAMediaTimingFunctionEaseIn(漸進):動畫緩慢進入,然後加速離開
- kCAMediaTimingFunctionEaseOut(漸出):動畫全速進入,然後減速的到達目的地
- kCAMediaTimingFunctionEaseInEaseOut(漸進漸出):動畫緩慢的進入,中間加速,然後減速的到達目的地。這個是預設的動畫行為。
4. CABasicAnimation
基本動畫,是CAPropertyAnimation的子類
4.1 特別屬性說明:
- keyPath: 要改變的屬性名稱(傳字串)
- fromValue: keyPath相應屬性的初始值
- toValue: keyPath相應屬性的結束值
4.2 keyPath可以是哪些值
CATransform3D{
//rotation旋轉
transform.rotation.x
transform.rotation.y
transform.rotation.z
//scale縮放
transform.scale.x
transform.scale.y
transform.scale.z
//translation平移
transform.translation.x
transform.translation.y
transform.translation.z
}
CGPoint{
position
position.x
position.y
}
CGRect{
bounds
bounds.size
bounds.size.width
bounds.size.height
bounds.origin
bounds.origin.x
bounds.origin.y
}
property{
opacity
backgroundColor
cornerRadius
borderWidth
contents
Shadow{
shadowColor
shadowOffset
shadowOpacity
shadowRadius
}
}
複製程式碼
4.3 舉例
- 縮放動畫 -- transform.scale
//心臟縮放動畫
CABasicAnimation *scaleAnimation = [CABasicAnimation animationWithKeyPath:@"transform.scale"]; //選中的這個keyPath就是縮放
scaleAnimation.fromValue = [NSNumber numberWithDouble:0.5]; //一開始時是0.5的大小
scaleAnimation.toValue = [NSNumber numberWithDouble:1.5]; //結束時是1.5的大小
scaleAnimation.duration = 1; //設定時間
scaleAnimation.repeatCount = MAXFLOAT; //重複次數
[_heartImageView.layer addAnimation:scaleAnimation forKey:@"CQScale"]; //新增動畫
複製程式碼
- 旋轉動畫 -- transform.rotation.z
//風車旋轉動畫
CABasicAnimation *rotationAnimation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
rotationAnimation.fromValue = [NSNumber numberWithDouble:0.f];
rotationAnimation.toValue = [NSNumber numberWithDouble:2 * M_PI];
rotationAnimation.duration = 2.f;
rotationAnimation.repeatCount = MAXFLOAT;
[_fengcheImageView.layer addAnimation:rotationAnimation forKey:@"CQRotation"];
複製程式碼
- 平移動畫 -- position.x/position.y
//平移動畫
CABasicAnimation *positionAnimation = [CABasicAnimation animationWithKeyPath:@"position.x"];
positionAnimation.fromValue = [NSNumber numberWithDouble:0.f];
positionAnimation.toValue = [NSNumber numberWithDouble:SCREEN_WIDTH];
positionAnimation.duration = 2;
positionAnimation.repeatCount = MAXFLOAT;
[_arrowImageView.layer addAnimation:positionAnimation forKey:@"CQPosition"];
複製程式碼
5. CAKeyframeAnimation關鍵幀動畫
5.1 引數陣列形式
//根據values移動的動畫
CAKeyframeAnimation *catKeyAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
CGPoint originalPoint = self.catImageView.layer.frame.origin;
CGFloat distance = 50;
NSValue *value1 = [NSValue valueWithCGPoint:CGPointMake(originalPoint.x + distance, originalPoint.y + distance)];
NSValue *value2 = [NSValue valueWithCGPoint:CGPointMake(originalPoint.x + 2 * distance, originalPoint.y + distance)];
NSValue *value3 = [NSValue valueWithCGPoint:CGPointMake(originalPoint.x + 2 * distance, originalPoint.y + 2 * distance)];
NSValue *value4 = [NSValue valueWithCGPoint:originalPoint];
catKeyAnimation.values = @[value4, value1, value2, value3, value4];
catKeyAnimation.duration = 2;
catKeyAnimation.repeatCount = MAXFLOAT;
catKeyAnimation.removedOnCompletion = NO;
[self.catImageView.layer addAnimation:catKeyAnimation forKey:nil];
複製程式碼
5.2 path形式
//指定path
UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0, 200, 200, 200)];
//指定path的動畫
UIBezierPath *path2 = [UIBezierPath bezierPath];
[path2 moveToPoint:CGPointMake(100, 100)];
[path2 addLineToPoint:CGPointMake(100, 200)];
[path2 addLineToPoint:CGPointMake(200, 200)];
[path2 addLineToPoint:CGPointMake(200, 100)];
[path2 addLineToPoint:CGPointMake(100, 100)];
CAKeyframeAnimation *penguinAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
penguinAnimation.path = path2.CGPath;
penguinAnimation.duration = 2;
penguinAnimation.repeatCount = MAXFLOAT;
penguinAnimation.removedOnCompletion = NO;
[self.penguinImageView.layer addAnimation:penguinAnimation forKey:nil];
複製程式碼
6. 組動畫
6.1 組動畫
上面單一動畫的情況在實際開發中實際比較少,更多的時候是組合這些動畫:建立不同型別的動畫物件,設定好它們的引數,然後把這些動畫物件存進陣列,傳進組動畫物件的animations屬性中去。
6.2 示例
//建立組動畫
CAAnimationGroup *animationGroup = [CAAnimationGroup animation];
animationGroup.duration = 3;
animationGroup.repeatCount = MAXFLOAT;
animationGroup.removedOnCompletion = NO;
/* beginTime 可以分別設定每個動畫的beginTime來控制組動畫中每個動畫的觸發時間,時間不能夠超過動畫的時間,預設都為0.f */
//縮放動畫
CAKeyframeAnimation *animation1 = [CAKeyframeAnimation animationWithKeyPath:@"transform.scale"];
animation1.values = @[[NSNumber numberWithFloat:1.0],[NSNumber numberWithFloat:0.5],[NSNumber numberWithFloat:1.5],[NSNumber numberWithFloat:1.0]];
animation1.beginTime = 0.f;
//按照圓弧移動動畫
CAKeyframeAnimation *animation2 = [CAKeyframeAnimation animationWithKeyPath:@"position"];
UIBezierPath *bezierPath = [UIBezierPath bezierPath];
[bezierPath moveToPoint:CGPointMake(300, 200)];
[bezierPath addQuadCurveToPoint:CGPointMake(200, 300) controlPoint:CGPointMake(300, 300)];
[bezierPath addQuadCurveToPoint:CGPointMake(100, 200) controlPoint:CGPointMake(100, 300)];
[bezierPath addQuadCurveToPoint:CGPointMake(200, 100) controlPoint:CGPointMake(100, 100)];
[bezierPath addQuadCurveToPoint:CGPointMake(300, 200) controlPoint:CGPointMake(300, 100)];
animation2.path = bezierPath.CGPath;
animation2.beginTime = 0.f;
//透明度動畫
CABasicAnimation *animation3 = [CABasicAnimation animationWithKeyPath:@"opacity"];
animation3.fromValue = [NSNumber numberWithDouble:0.0];
animation3.toValue = [NSNumber numberWithDouble:1.0];
animation3.beginTime = 0.f;
//新增組動畫
animationGroup.animations = @[animation1, animation2,animation3];
[_penguinImageView.layer addAnimation:animationGroup forKey:nil];
複製程式碼
7. 貝塞爾曲線
前面關鍵幀動畫章節提到了貝塞爾曲線,這個曲線很有用,在iOS開發中有兩種形式可用:CGMutablePathRef和UIBezierPath,均可以通過制定控制點陣列的形式唯一確定曲線,也可以通過矩形內切橢圓唯一確定曲線。下面是兩者的例子:
7.1 CGMutablePathRef
- 通過 關鍵點曲線連線 唯一確定
// 貝塞爾曲線關鍵幀
// 設定路徑, 繪製貝塞爾曲線
CGMutablePathRef path = CGPathCreateMutable();
CGPathMoveToPoint(path, NULL, 200, 200); // 起始點
CGPathAddCurveToPoint(path, NULL, 100, 300, 300, 500, 200, 600);
// CGPathAddCurveToPoint(path, NULL, 控制點1.x, 控制點1.y, 控制點2.x, 控制點2.y, 終點.x, 終點.y);
// 設定path屬性
keyframeAnimation.path = path;
CGPathRelease(path);
// 設定其他屬性
keyframeAnimation.duration = 4;
keyframeAnimation.beginTime = CACurrentMediaTime() + 1; // 設定延遲2秒執行, 不設定這個屬性, 預設直接執行
// 3. 新增動畫到圖層, 會自動執行
[_layer addAnimation:keyframeAnimation forKey:@"GGKeyframeAnimation"];
複製程式碼
7.2 UIBezierPath
- 通過 矩形內切橢圓 唯一確定
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"];
複製程式碼
- 通過 關鍵點直線連線 唯一確定
-(void)pathAnimation2{
//建立path
UIBezierPath *path = [UIBezierPath bezierPath];
//設定線寬
path.lineWidth = 3;
//線條拐角
path.lineCapStyle = kCGLineCapRound;
//終點處理
path.lineJoinStyle = kCGLineJoinRound;
//多條直線
[path moveToPoint:(CGPoint){0, SCREEN_HEIGHT/2-50}];
[path addLineToPoint:(CGPoint){SCREEN_WIDTH/3, SCREEN_HEIGHT/2-50}];
[path addLineToPoint:(CGPoint){SCREEN_WIDTH/3, SCREEN_HEIGHT/2+50}];
[path addLineToPoint:(CGPoint){SCREEN_WIDTH*2/3, SCREEN_HEIGHT/2+50}];
[path addLineToPoint:(CGPoint){SCREEN_WIDTH*2/3, SCREEN_HEIGHT/2-50}];
[path addLineToPoint:(CGPoint){SCREEN_WIDTH, SCREEN_HEIGHT/2-50}];
// [path closePath];
CAKeyframeAnimation *anima = [CAKeyframeAnimation animationWithKeyPath:@"position"];
anima.path = path.CGPath;
anima.duration = 2.0f;
anima.fillMode = kCAFillModeForwards;
anima.removedOnCompletion = NO;
anima.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
[_demoView.layer addAnimation:anima forKey:@"pathAnimation"];
}
複製程式碼