CABaseAnimation收錄:二、CAAnimation動畫詳解
先看看CAAnimation動畫的繼承結構
CAAnimation{
CAPropertyAnimation {
CABasicAnimation {
CASpringAnimation
}
CAKeyframeAnimation
}
CATransition
CAAnimationGroup
}
CAAnimation基本屬性詳解
//動畫的動作規則,包含以下值 //kCAMediaTimingFunctionLinear 勻速 //kCAMediaTimingFunctionEaseIn 慢進快出 //kCAMediaTimingFunctionEaseOut 快進慢出 //kCAMediaTimingFunctionEaseInEaseOut 慢進慢出 中間加速 //kCAMediaTimingFunctionDefault 預設 @property(nullable, strong) CAMediaTimingFunction *timingFunction; //動畫的代理回撥 @property(nullable, strong) id <CAAnimationDelegate> delegate; //動畫執行完以後是否移除動畫,預設YES @property(getter=isRemovedOnCompletion) BOOL removedOnCompletion;
以上屬性詳解:
- delegate:動畫執行的代理,在動畫開始前設定,不用顯式的寫在程式碼裡,它包含兩個方法:
動畫開始回撥- (void)animationDidStart:(CAAnimation *)aim;
動畫結束回撥- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag;
- removedOnCompletion:動畫完成後是否移除動畫.預設為YES.此屬性為YES時, fillMode不可用,具體為什麼不可用,可以自己結合兩個屬性分析一下,這裡不再贅述.
- timingFunction 設定動畫速度曲線,預設值上面已經給出.下面說它的幾個方法:
這兩個方法是一樣的.如果我們對系統自帶的速度函式不滿意,可以通過這兩個函式建立一個自己喜歡的速度曲線函式,具體用法可以參考這篇文章CAMediaTimingFunction的使用
+ (instancetype)functionWithControlPoints:(float)c1x :(float)c1y :(float)c2x :(float)c2y;
- (instancetype)initWithControlPoints:(float)c1x :(float)c1y :(float)c2x :(float)c2y;
獲取曲線函式的緩衝點,具體用法可以參考這篇文章:iOS-核心動畫高階程式設計/10-緩衝
- (void)getControlPointAtIndex:(size_t)idx values:(float[2])ptr;
CAAnimation協議的屬性
//開始時間.這個屬性比較複雜,傻瓜用法為:CACurrentMediaTime() + x,
//其中x為延遲時間.如果設定 beginTime = CACurrentMediaTime() + 1.0,產生的效果為延遲一秒執行動畫,下面詳解原理
@property CFTimeInterval beginTime;
//動畫執行時間,此屬性和speed有關係speed預設為1.0,如果speed設定為2.0,那麼動畫執行時間則為duration*(1.0/2.0).
@property CFTimeInterval duration;
//動畫執行速度,它duration的關係參考上面解釋
@property float speed;
//動畫的時間延遲,這個屬性比較複雜,下面詳解
@property CFTimeInterval timeOffset;
//重複執行次數
@property float repeatCount;
//重複執行時間,此屬性優先順序大於repeatCount.也就是說如果repeatDuration設定為1秒重複10次,那麼它會在1秒內執行完動畫.
@property CFTimeInterval repeatDuration;
//是否自動翻轉動畫,預設NO.如果設定YES,那麼整個動畫的執行效果為A->B->A.
@property BOOL autoreverses;
//動畫的填充方式,預設為: kCAFillModeRemoved,包含以下值
//kCAFillModeForwards//動畫結束後回到準備狀態
//kCAFillModeBackwards//動畫結束後保持最後狀態
//kCAFillModeBoth//動畫結束後回到準備狀態,並保持最後狀態
//kCAFillModeRemoved//執行完成移除動畫
@property(copy) NSString *fillMode;
以上屬性的詳解:
- beginTime:剛才上面簡單解釋了下這個屬性的用法:CACurrentMediaTime()+ x 會使動畫延遲執行x秒.不知道到這裡有沒有人想過如果-x會出現怎麼樣效果?假設我們有執行一個3秒的動畫,然後設定beginTime = CACurrentMediaTime()- 1.5那麼執行動畫你會發現動畫只會執行後半段,也就是隻執行後面的3-1.5s的動畫.為什麼會這樣?其實動畫都有一個timeline(時間線)的概念.動畫開始執行都是基於這個時間線的絕對時間,這個時間和它的父類有關(系統的屬性註釋可以看到).預設的CALayer的beginTime為零,如果這個值為零的話,系統會把它設定為CACurrentMediaTime(),那麼這個時間就是正常執行動畫的時間:立即執行.所以如果你設定beginTime=CACurrentMediaTime()+x;它會把它的執行時間線推遲x秒,也就是晚執行x秒,如果你beginTime=CACurrentMediaTime()-x;那它開始的時候會從你動畫對應的絕對時間開始執行.
- timeOffset:時間偏移量,預設為0;既然它是時間偏移量,那麼它即和動畫時間相關.這麼解釋:假設我們設定一個動畫時間為5s,動畫執行的過程為1->2->3->4->5,這時候如果你設定timeOffset = 2s那麼它的執行過程就會變成3->4->5->1->2如果你設定timeOffset = 4s那麼它的執行過程就會變成5->1->2->3->4,這麼說應該很明白了吧?
CAPropertyAnimation的基本屬性
//需要動畫的屬性值
@property(nullable, copy) NSString *keyPath;
//屬性動畫是否以當前動畫效果為基礎,預設為NO
@property(getter=isAdditive) BOOL additive;
//指定動畫是否為累加效果,預設為NO
@property(getter=isCumulative) BOOL cumulative;
//此屬性相當於CALayer中的transform屬性,下面會詳解
@property(nullable, strong) CAValueFunction *valueFunction;
以上屬性的詳解:
CAPropertyAnimation是屬性動畫.顧名思義也就是針對屬性才可以做的動畫.那它可以對誰的屬性可以做動畫?是CALayer的屬性,比如:bounds,position等.那麼問題來了,我們改變CALayer的position可以直接設定[CAPropertyAnimation animationWithKeyPath:@"position"]如果我們設定它的transform(CATransform3D)呢?CATransform3D是一個矩陣,如果我們想為它做動畫怎麼辦?下面這個屬性就是用來解決這個問題的.
- valueFunction:我們來看它可以設定的值:
kCAValueFunctionRotateX kCAValueFunctionRotateY kCAValueFunctionRotateZ kCAValueFunctionScale kCAValueFunctionScaleX kCAValueFunctionScaleY kCAValueFunctionScaleZ kCAValueFunctionTranslate kCAValueFunctionTranslateX kCAValueFunctionTranslateY kCAValueFunctionTranslateZ
CAPropertyAnimation的方法
//通過key建立一個CAPropertyAnimation物件
+ (instancetype)animationWithKeyPath:(nullable NSString *)path;
下面我們來看一下可以設定屬性動畫的屬性歸總:
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
}
}
總結: CAAnimation是基類,CAPropertyAnimation**是抽象類,兩者都不可以直接使用, 那我們只有使用它的子類了.
CABasicAnimation基本動畫
CABasicAnimation的屬性
//開始值
@property(nullable, strong) id fromValue;
//結束值
@property(nullable, strong) id toValue;
//結束值
@property(nullable, strong) id byValue;
這三個屬性之間的規則
- fromValue和toValue不為空,動畫的效果會從fromValue的值變化到toValue.
- fromValue和byValue都不為空,動畫的效果將會從fromValue變化到fromValue+byValue
- toValue和byValue都不為空,動畫的效果將會從toValue-byValue變化到toValue
- 只有fromValue的值不為空,動畫的效果將會從fromValue的值變化到當前的狀態.
- 只有toValue的值不為空,動畫的效果將會從當前狀態的值變化到toValue的值.
- 只有byValue的值不為空,動畫的效果將會從當前的值變化到(當前狀態的值+byValue)的值.
CABasicAnimation看起來不太複雜,但實際只用這個就足以可以做很多種動畫了,下面簡單用一下,先看效果:
CABasicAnimation.gif
實現程式碼:
- (IBAction)animation:(id)sender
{
UIButton *btn = (UIButton *)sender;
CABasicAnimation *animation = nil;
switch (btn.tag)
{
//淡如淡出
case 0:
animation = [CABasicAnimation animationWithKeyPath:@"opacity"];
[animation setFromValue:@1];
[animation setToValue:@0.1];
break;
//縮放
case 1:
animation = [CABasicAnimation animationWithKeyPath:@"transform.scale"];
[animation setFromValue:@1];
[animation setToValue:@0.1];
break;
//旋轉
case 2:
animation = [CABasicAnimation animationWithKeyPath:@"transform.rotation"];
[animation setToValue:@(M_PI)];
break;
//平移
case 3:
animation = [CABasicAnimation animationWithKeyPath:@"position"];
[animation setToValue:[NSValue valueWithCGPoint:CGPointMake(self.opView.center.x, self.opView.center.y+200)]];
break;
default:
break;
}
animation.delegate = self;
animation.duration = 0.3;//設定動畫時間,單次動畫時間
animation.removedOnCompletion = NO;//預設為YES,設定為NO時setFillMode有效
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
//設定自動翻轉
//設定自動翻轉以後單次動畫時間不變,總動畫時間增加一倍,它會讓你前半部分的動畫以相反的方式動畫過來
//比如說你設定執行一次動畫,從a到b時間為1秒,設定自動翻轉以後動畫的執行方式為,先從a到b執行一秒,然後從b到a再執行一下動畫結束
[animation setAutoreverses:YES];
//kCAFillModeForwards//動畫結束後回到準備狀態
//kCAFillModeBackwards//動畫結束後保持最後狀態
//kCAFillModeBoth//動畫結束後回到準備狀態,並保持最後狀態
//kCAFillModeRemoved//執行完成移除動畫
[animation setFillMode:kCAFillModeBoth];
//將動畫新增到layer,新增到圖層開始執行動畫,
//注意:key值的設定與否會影響動畫的效果
//如果不設定key值每次執行都會建立一個動畫,然後建立的動畫會疊加在圖層上
//如果設定key值,系統執行這個動畫時會先檢查這個動畫有沒有被建立,如果沒有的話就建立一個,如果有的話就重新從頭開始執行這個動畫
//你可以通過key值獲取或者刪除一個動畫:
[self.opView.layer addAnimation:animation forKey:@"baseanimation"];
}
CASpringAnimation彈性動畫
CASpringAnimation的屬性(iOS9新加)
//理解下面的屬性的時候可以結合現實物理現象,比如把它想象成一個彈簧上掛著一個金屬小球
//質量,振幅和質量成反比
@property CGFloat mass;
//剛度係數(勁度係數/彈性係數),剛度係數越大,形變產生的力就越大,運動越快
@property CGFloat stiffness;
//阻尼係數,阻止彈簧伸縮的係數,阻尼係數越大,停止越快,可以認為它是阻力系數
@property CGFloat damping;
//初始速率,動畫檢視的初始速度大小速率為正數時,速度方向與運動方向一致,速率為負數時,速度方向與運動方向相反.
@property CGFloat initialVelocity;
//結算時間,只讀.返回彈簧動畫到停止時的估算時間,根據當前的動畫引數估算通常彈簧動畫的時間使用結算時間比較準確
@property(readonly) CFTimeInterval settlingDuration;
下面我們寫一個demo看看效果:
實現程式碼:
- (IBAction)animationBegin:(id)sender
{
UIButton *btn = (UIButton *)sender;
CASpringAnimation *springAnimation = [CASpringAnimation animationWithKeyPath:@"position.y"];
springAnimation.mass = 1;
springAnimation.stiffness = 100;
springAnimation.damping = 1;
springAnimation.initialVelocity = 0;
springAnimation.duration = springAnimation.settlingDuration;
springAnimation.fromValue = @(self.opView.center.y);
springAnimation.toValue = @(self.opView.center.y + (btn.selected?+150:-150));
springAnimation.fillMode = kCAFillModeForwards;
[self.opView.layer addAnimation:springAnimation forKey:nil];
btn.selected = !btn.selected;
}
CAKeyframeAnimation關鍵幀動畫
CAKeyframeAnimation的屬性
//關鍵幀值陣列,一組變化值
@property(nullable, copy) NSArray *values;
//關鍵幀幀路徑,優先順序比values大
@property(nullable) CGPathRef path;
//每一幀對應的時間,時間可以控制速度.它和每一個幀相對應,取值為0.0-1.0,不設則每一幀時間相等.
@property(nullable, copy) NSArray *keyTimes;
//每一幀對應的時間曲線函式,也就是每一幀的運動節奏
@property(nullable, copy) NSArray *timingFunctions;
//動畫的計算模式,預設值: kCAAnimationLinear.有以下幾個值:
//kCAAnimationLinear//關鍵幀為座標點的時候,關鍵幀之間直接直線相連進行插值計算;
//kCAAnimationDiscrete//離散的,也就是沒有補間動畫
//kCAAnimationPaced//平均,keyTimes跟timeFunctions失效
//kCAAnimationCubic對關鍵幀為座標點的關鍵幀進行圓滑曲線相連後插值計算,對於曲線的形狀還可以通過tensionValues,continuityValues,biasValues來進行調整自定義,keyTimes跟timeFunctions失效
//kCAAnimationCubicPaced在kCAAnimationCubic的基礎上使得動畫執行變得均勻,就是系統時間內運動的距離相同,,keyTimes跟timeFunctions失效
@property(copy) NSString *calculationMode;
//動畫的張力,當動畫為立方計算模式的時候此屬性提供了控制插值,因為每個關鍵幀都可能有張力所以連續性會有所偏差它的範圍為[-1,1].同樣是此作用
@property(nullable, copy) NSArray *tensionValues;
//動畫的連續性值
@property(nullable, copy) NSArray *continuityValues;
//動畫的偏斜率
@property(nullable, copy) NSArray *biasValues;
//動畫沿路徑旋轉方式,預設為nil.它有兩個值:
//kCAAnimationRotateAuto//自動旋轉,
//kCAAnimationRotateAutoReverse//自動翻轉
@property(nullable, copy) NSString *rotationMode;
CAKeyframeAnimation可以做很豐富的效果,下面展示了幾種純CAKeyframeAnimation做的效果:
實現程式碼:
- (IBAction)animationBegin:(id)sender {
UIButton *btn = (UIButton *)sender;
switch (btn.tag) {
case 0:
case 1:
case 2:
case 3:
case 4:
case 5:
[self path:btn.tag];break;
case 6:
case 7:
[self values:btn.tag];break;
default:
break;
}
}
-(void)path:(NSInteger)tag{
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
switch (tag) {
case 0:{
//橢圓
CGMutablePathRef path = CGPathCreateMutable();//建立可變路徑
CGPathAddEllipseInRect(path, NULL, CGRectMake(0, 0, 320, 500));
[animation setPath:path];
CGPathRelease(path);
animation.rotationMode = kCAAnimationRotateAuto;
}break;
case 1:{
//貝塞爾,矩形
UIBezierPath *path = [UIBezierPath bezierPathWithRect:CGRectMake(0, 0, 320, 320)];
//animation需要的型別是CGPathRef,UIBezierPath是ui的,需要轉化成CGPathRef
[animation setPath:path.CGPath];
}break;
case 2:{
//貝塞爾,拋物線
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:self.opView.center];
[path addQuadCurveToPoint:CGPointMake(0, 568)
controlPoint:CGPointMake(400, 100)];
[animation setPath:path.CGPath];
}break;
case 3:{
//貝塞爾,s形曲線
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointZero];
[path addCurveToPoint:self.opView.center
controlPoint1:CGPointMake(320, 100)
controlPoint2:CGPointMake( 0, 400)];
;
[animation setPath:path.CGPath];
}break;
case 4:{
//貝塞爾,圓形
UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:self.view.center
radius:150
startAngle:- M_PI * 0.5
endAngle:M_PI * 2
clockwise:YES];
[animation setPath:path.CGPath];
}break;
case 5:{
CGPoint point = CGPointMake(self.view.center.x, 400);
CGFloat xlength = point.x - self.opView.center.x;
CGFloat ylength = point.y - self.opView.center.y;
CGMutablePathRef path = CGPathCreateMutable();
//移動到目標點
CGPathMoveToPoint(path, NULL, self.opView.center.x, self.opView.center.y);
//將目標點的座標新增到路徑中
CGPathAddLineToPoint(path, NULL, point.x, point.y);
//設定彈力因子,
CGFloat offsetDivider = 5.0f;
BOOL stopBounciong = NO;
while (stopBounciong == NO) {
CGPathAddLineToPoint(path, NULL, point.x + xlength / offsetDivider, point.y + ylength / offsetDivider);
CGPathAddLineToPoint(path, NULL, point.x, point.y);
offsetDivider += 6.0;
//當檢視的當前位置距離目標點足夠小我們就退出迴圈
if ((ABS(xlength / offsetDivider) < 10.0f) && (ABS(ylength / offsetDivider) < 10.0f)) {
break;
}
}
[animation setPath:path];
}break;
default:break;
}
[animation setDuration:0.5];
[animation setRemovedOnCompletion:NO];
[animation setFillMode:kCAFillModeBoth];
[self.opView.layer addAnimation:animation forKey:nil];
}
-(void)values:(NSInteger)tag{
CAKeyframeAnimation *animation = nil;
switch (tag) {
case 6:{
animation = [CAKeyframeAnimation animationWithKeyPath:@"transform.rotation"];
CGFloat angle = M_PI_4 * 0.5;
NSArray *values = @[@(angle),@(-angle),@(angle)];
[animation setValues:values];
[animation setRepeatCount:3];
[animation setDuration:0.5];
}break;
case 7:{
animation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
NSValue *p1 = [NSValue valueWithCGPoint:self.opView.center];
NSValue *p2 = [NSValue valueWithCGPoint:CGPointMake(self.view.center.x + 100, 200)];
NSValue *p3 = [NSValue valueWithCGPoint:CGPointMake(self.view.center.x, 300)];
//設定關鍵幀的值
[animation setValues:@[p1,p2,p3]];
[animation setDuration:0.5];
}break;
default:break;
}
UIGraphicsBeginImageContext(self.view.frame.size);
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
[animation setRemovedOnCompletion:NO];
[animation setFillMode:kCAFillModeBoth];
[self.opView.layer addAnimation:animation forKey:nil];
}
CAAnimationGroup動畫組
CAAnimationGroup的屬性
//只有一個屬性,陣列中接受CAAnimation元素
@property(nullable, copy) NSArray *animations;
可以看到CAAnimationGroup只有一個屬性一個CAAnimation陣列.而且它繼承於CAAnimation,它具有CAAnimation的特性,所以它的用法和CAAnimation是一樣的,不同的是他可以包含n個動畫,也就是說他可以接受很多個CAAnimation並且可以讓它們一起開始,這就造成了動畫效果的疊加,效果就是n個動畫同時進行.
來看一個簡單的效果:
實現程式碼:
- (IBAction)animationBegin:(id)sender
{
CAAnimationGroup *group = [CAAnimationGroup animation];
/**
* 移動動畫
*/
CAKeyframeAnimation *position = [self moveAnimation];
/**
* 搖晃動畫
*/
CAKeyframeAnimation *shake = [self shakeAnimation];
/**
* 透明度動畫
*/
CABasicAnimation *alpha = [self alphaAnimation];
/**
* 設定動畫組的時間,這個時間表示動畫組的總時間,它的子動畫的時間和這個時間沒有關係
*/
[group setDuration:3.0];
[group setAnimations:@[position,shake,alpha]];
[self.opView.layer addAnimation:group forKey:nil];
}
#pragma mark -- CAKeyframeAnimation - 路徑平移動畫
-(CAKeyframeAnimation *)moveAnimation
{
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
/**
* 設定路徑,按圓運動
*/
CGMutablePathRef path = CGPathCreateMutable();//CG是C語言的框架,需要直接寫語法
CGPathAddEllipseInRect(path, NULL, CGRectMake(0, 0, 320, 320));
[animation setPath:path];//把路徑給動畫
CGPathRelease(path);//釋放路徑
/**
* 設定動畫時間,這是動畫總時間,不是每一幀的時間
*/
[animation setDuration:3];
/**
*setRemovedOnCompletion 設定動畫完成後是否將圖層移除掉,預設是移除
*setFillMode 當前設定的是向前填充,意味著動畫完成後填充效果為最新的效果,此屬性有效的前提是 setRemovedOnCompletion=NO
*注意:
*1.動畫只是改變人的視覺,它並不會改變檢視的初始位置等資訊,也就是說無論動畫怎麼東,都不會改變view的原始大小,只是看起來像是大小改變了而已
*2.因為沒有改變檢視的根本大小,所以檢視所接收事件的位置還是原來的大小,可以不是顯示的大小
*/
[animation setRemovedOnCompletion:NO];
[animation setFillMode:kCAFillModeForwards];
return animation;
}
#pragma mark -- CAKeyframeAnimation - 搖晃動畫
-(CAKeyframeAnimation *)shakeAnimation
{
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"transform.rotation"];
/**
* 設定路徑,貝塞爾路徑
*/
CGFloat angle = M_PI_4 * 0.1;
NSArray *values = @[@(angle),@(-angle),@(angle)];
[animation setValues:values];
[animation setRepeatCount:10];
/**
* 設定動畫時間,這是動畫總時間,不是每一幀的時間
*/
[animation setDuration:0.25];
return animation;
}
#pragma mark -- CABasicAnimation - 淡如淡出動畫
-(CABasicAnimation *)alphaAnimation
{
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"opacity"];
[animation setDuration:1.0];
/**
* 設定重複次數
*/
[animation setRepeatCount:3];
/**
* 設定自動翻轉
* 設定自動翻轉以後單次動畫時間不變,總動畫時間延遲一倍,它會讓你前半部分的動畫以相反的方式動畫過來
* 比如說你設定執行一次動畫,從a到b時間為1秒,設定自動翻轉以後動畫的執行方式為,先從a到b執行一秒,然後從b到a再執行一下動畫結束
*/
[animation setAutoreverses:YES];
/**
* 設定起始值
*/
[animation setFromValue:@1.0];
/**
* 設定目標值
*/
[animation setToValue:@0.1];
/**
* 將動畫新增到layer 新增到圖層開始執行動畫,
* 注意:key值的設定與否會影響動畫的效果
* 如果不設定key值每次執行都會建立一個動畫,然後建立的動畫會疊加在圖層上
* 如果設定key值,系統執行這個動畫時會先檢查這個動畫有沒有被建立,如果沒有的話就建立一個,如果有的話就重新從頭開始執行這個動畫
* 你可以通過key值獲取或者刪除一個動畫
*/
return animation;
}
CATransition轉場動畫
CATransition屬性
//轉場型別,字串型別引數.系統提供了四中動畫形式:
//kCATransitionFade//逐漸消失
//kCATransitionMoveIn//移進來
//kCATransitionPush//推進來
//kCATransitionReveal//揭開
//另外,除了系統給的這幾種動畫效果,我們還可以使用系統私有的動畫效果:
//@"cube",//立方體翻轉效果
//@"oglFlip",//翻轉效果
//@"suckEffect",//收縮效果,動畫方向不可控
//@"rippleEffect",//水滴波紋效果,動畫方向不可控
//@"pageCurl",//向上翻頁效果
//@"pageUnCurl",//向下翻頁效果
//@"cameralIrisHollowOpen",//攝像頭開啟效果,動畫方向不可控
//@"cameraIrisHollowClose",//攝像頭關閉效果,動畫方向不可控
@property(copy) NSString *type;
//轉場方向,系統一共提供四個方向:
//kCATransitionFromRight//從右開始
//kCATransitionFromLeft//從左開始
//kCATransitionFromTop//從上開始
//kCATransitionFromBottom//從下開始
@property(nullable, copy) NSString *subtype;
//開始進度,預設0.0.如果設定0.3,那麼動畫將從動畫的0.3的部分開始
@property float startProgress;
//結束進度,預設1.0.如果設定0.6,那麼動畫將從動畫的0.6部分以後就會結束
@property float endProgress;
//開始進度
@property(nullable, strong) id filter;
CATransition也是繼承CAAnimation,系統預設提供了12種動畫樣式,加上4個動畫方向,除了方向不可控的四種效果外,大概一共提供了36種動畫.
另外系統還給UIView添加了很多分類方法可以快速完成一些簡單的動畫,如下:
UIView(UIViewAnimation)
@interface UIView(UIViewAnimation)
+ (void)beginAnimations:(nullable NSString *)animationID context:(nullable void *)context; // additional context info passed to will start/did stop selectors. begin/commit can be nested
//提交動畫
+ (void)commitAnimations;
//設定代理
+ (void)setAnimationDelegate:(nullable id)delegate; //設定動畫開始方法
+ (void)setAnimationWillStartSelector:(nullable SEL)selector;
//設定動畫結束方法
+ (void)setAnimationDidStopSelector:(nullable SEL)selector;
//設定動畫時間:default = 0.2
+ (void)setAnimationDuration:(NSTimeInterval)duration;
//設定動畫延遲開始時間:default = 0.0
+ (void)setAnimationDelay:(NSTimeInterval)delay;
//設定動畫延遲開始日期:default = now ([NSDate date])
+ (void)setAnimationStartDate:(NSDate *)startDate;
//設定動畫運動曲線:default =UIViewAnimationCurveEaseInOut
//UIViewAnimationCurveEaseInOut,//慢進慢出
//UIViewAnimationCurveEaseIn, //慢進快出
//UIViewAnimationCurveEaseOut,//快進慢出
//UIViewAnimationCurveLinear//勻速
+ (void)setAnimationCurve:(UIViewAnimationCurve)curve;
//設定重複次數: default = 0.0. May be fractional
+ (void)setAnimationRepeatCount:(float)repeatCount;
//設定是否翻轉動畫: default = NO. used if repeat
+ (void)setAnimationRepeatAutoreverses:(BOOL)repeatAutoreverses;
//設定動畫是否從當前狀態開始:default = NO
+ (void)setAnimationBeginsFromCurrentState:(BOOL)fromCurrentState;
//設定動畫型別
+ (void)setAnimationTransition:(UIViewAnimationTransition)transition forView:(UIView *)view cache:(BOOL)cache;
//設定動畫是否有效
+ (void)setAnimationsEnabled:(BOOL)enabled;
//
+ (BOOL)areAnimationsEnabled;
//
+ (void)performWithoutAnimation:(void (^)(void))actionsWithoutAnimation
//
+ (NSTimeInterval)inheritedAnimationDuration
@end
UIView(UIViewAnimationWithBlocks)
@interface UIView(UIViewAnimationWithBlocks)
//以下方法都大同小異,就不一一做註釋了
+ (void)animateWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay options:(UIViewAnimationOptions)options animations:(void (^)(void))animations completion:(void (^ __nullable)(BOOL finished))completion;
+ (void)animateWithDuration:(NSTimeInterval)duration animations:(void (^)(void))animations completion:(void (^ __nullable)(BOOL finished))completion
+ (void)animateWithDuration:(NSTimeInterval)duration animations:(void (^)(void))animations;
+ (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
+ (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;
+ (void)performSystemAnimation:(UISystemAnimation)animation onViews:(NSArray *)views options:(UIViewAnimationOptions)options animations:(void (^ __nullable)(void))parallelAnimations completion:(void (^ __nullable)(BOOL finished))completion NS_AVAILABLE_IOS(7_0);
@end
UIView (UIViewKeyframeAnimations)
+ (void)animateKeyframesWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay options:(UIViewKeyframeAnimationOptions)options animations:(void (^)(void))animations completion:(void (^ __nullable)(BOOL finished))completion;
+ (void)addKeyframeWithRelativeStartTime:(double)frameStartTime relativeDuration:(double)frameDuration animations:(void (^)(void))animations
以上方法比較多,找值得說的簡單說一下吧:
//單檢視轉場動畫
+ (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
這兩個都是轉場動畫,不同的是第一個是單檢視轉場,第二個是雙檢視轉場.不過需要注意的是:單檢視轉場動畫只能用作屬性動畫做不到的轉場效果,比如屬性動畫不能給UIImageview的image賦值操作做動畫效果等.
我們可以看到以上兩個方法中都有一個共同的引數:
UIViewAnimationOptions
typedef NS_OPTIONS(NSUInteger, UIViewAnimationOptions) {
UIViewAnimationOptionLayoutSubviews = 1 << 0,
UIViewAnimationOptionAllowUserInteraction = 1 << 1, // turn on user interaction while animating
UIViewAnimationOptionBeginFromCurrentState = 1 << 2, // start all views from current value, not initial value
UIViewAnimationOptionRepeat = 1 << 3, // repeat animation indefinitely
UIViewAnimationOptionAutoreverse = 1 << 4, // if repeat, run animation back and forth
UIViewAnimationOptionOverrideInheritedDuration = 1 << 5, // ignore nested duration
UIViewAnimationOptionOverrideInheritedCurve = 1 << 6, // ignore nested curve
UIViewAnimationOptionAllowAnimatedContent = 1 << 7, // animate contents (applies to transitions only)
UIViewAnimationOptionShowHideTransitionViews = 1 << 8, // flip to/from hidden state instead of adding/removing
UIViewAnimationOptionOverrideInheritedOptions = 1 << 9, // do not inherit any options or animation type
UIViewAnimationOptionCurveEaseInOut = 0 << 16, // default
UIViewAnimationOptionCurveEaseIn = 1 << 16,
UIViewAnimationOptionCurveEaseOut = 2 << 16,
UIViewAnimationOptionCurveLinear = 3 << 16,
UIViewAnimationOptionTransitionNone = 0 << 20, // default
UIViewAnimationOptionTransitionFlipFromLeft = 1 << 20,
UIViewAnimationOptionTransitionFlipFromRight = 2 << 20,
UIViewAnimationOptionTransitionCurlUp = 3 << 20,
UIViewAnimationOptionTransitionCurlDown = 4 << 20,
UIViewAnimationOptionTransitionCrossDissolve = 5 << 20,
UIViewAnimationOptionTransitionFlipFromTop = 6 << 20,
UIViewAnimationOptionTransitionFlipFromBottom = 7 << 20,
} NS_ENUM_AVAILABLE_IOS(4_0);
可以看到系統給到的是一個位移列舉,這就意味著這個列舉可以多個值同時使用,但是怎麼用呢?其實那些列舉值可以分為三個部分.
我們分別看一下每個列舉的意思:
第一部分:動畫效果
UIViewAnimationOptionTransitionNone//沒有效果
UIViewAnimationOptionTransitionFlipFromLeft//從左水平翻轉
UIViewAnimationOptionTransitionFlipFromRight//從右水平翻轉
UIViewAnimationOptionTransitionCurlUp//翻書上掀
UIViewAnimationOptionTransitionCurlDown//翻書下蓋UIViewAnimationOptionTransitionCrossDissolve//融合
UIViewAnimationOptionTransitionFlipFromTop//從上垂直翻轉
UIViewAnimationOptionTransitionFlipFromBottom//從下垂直翻轉
第二部分:動畫運動曲線
//開始慢,加速到中間,然後減慢到結束
UIViewAnimationOptionCurveEaseInOut
//開始慢,加速到結束
UIViewAnimationOptionCurveEaseIn
//開始快,減速到結束
UIViewAnimationOptionCurveEaseOut
//線性運動
UIViewAnimationOptionCurveLinear
第三部分:其他
//預設,跟父類作為一個整體
UIViewAnimationOptionLayoutSubviews
//設定了這個,主執行緒可以接收點選事件
UIViewAnimationOptionAllowUserInteraction
//從當前狀態開始動畫,父層動畫運動期間,開始子層動畫.
UIViewAnimationOptionBeginFromCurrentState
//重複執行動畫,從開始到結束, 結束後直接跳到開始態
UIViewAnimationOptionRepeat
//反向執行動畫,結束後會再從結束態->開始態
UIViewAnimationOptionAutoreverse
//忽略繼承自父層持續時間,使用自己持續時間(如果存在)
UIViewAnimationOptionOverrideInheritedDuration
//忽略繼承自父層的線性效果,使用自己的線性效果(如果存在)
UIViewAnimationOptionOverrideInheritedCurve
//允許同一個view的多個動畫同時進行
UIViewAnimationOptionAllowAnimatedContent
//檢視切換時直接隱藏舊檢視、顯示新檢視,而不是將舊檢視從父檢視移除(僅僅適用於轉場動畫) UIViewAnimationOptionShowHideTransitionViews
//不繼承父動畫設定或動畫型別.
UIViewAnimationOptionOverrideInheritedOptions
這下可以看到,這些列舉功能都不一樣但是可以隨意組合,但是組合的時候需要注意,同一型別的列舉不能一起使用比如UIViewAnimationOptionCurveEaseIn和UIViewAnimationOptionCurveEaseOut
然後我們看一下轉場動畫的一些效果:
單檢視轉場
單檢視轉場.gif
雙檢視轉場
雙檢視轉場.gif
CATransition轉場
CATransition轉場.gif
實現程式碼:
- (IBAction)animationBegin:(id)sender
{
UIButton *btn = (UIButton *)sender;
switch (btn.tag)
{
case 0:
[self animationSingleView:YES];
break;
case 1:
[self animationSingleView:NO];
break;
case 2:[self chang3];
break;
default:break;
}
}
/**
* 轉場動畫在執行過程中不可以被停止,
* 轉場動畫在執行過程中不可以使用者互動
* 轉場動畫在執行過程中不可以控制動畫執行進度
*/
/**
* 基於UIView的單檢視轉場動畫
*/
static NSUInteger change1_0Index = 0;
static NSUInteger change1_1Index = 0;
static NSUInteger change1_2Index = 0;
-(void)animationSingleView:(BOOL)sigle
{
/**
* 第一部分
*/
NSArray *array0 = @[
@(UIViewAnimationOptionTransitionNone),
@(UIViewAnimationOptionTransitionFlipFromLeft),//從左水平翻轉
@(UIViewAnimationOptionTransitionFlipFromRight),//從右水平翻轉
@(UIViewAnimationOptionTransitionCurlUp),//翻書上掀
@(UIViewAnimationOptionTransitionCurlDown),//翻書下蓋
@(UIViewAnimationOptionTransitionCrossDissolve),//融合
@(UIViewAnimationOptionTransitionFlipFromTop),//從上垂直翻轉
@(UIViewAnimationOptionTransitionFlipFromBottom),//從下垂直翻轉
];
/**
* 第二部分
*/
NSArray *array1 = @[
@(UIViewAnimationOptionCurveEaseInOut),////開始慢,加速到中間,然後減慢到結束
@(UIViewAnimationOptionCurveEaseIn),//開始慢,加速到結束
@(UIViewAnimationOptionCurveEaseOut),//開始快,減速到結束
@(UIViewAnimationOptionCurveLinear),//線性運動
];
/**
* 第三部分
*/
NSArray *array2 = @[
@(UIViewAnimationOptionLayoutSubviews),//預設,跟父類作為一個整體
@(UIViewAnimationOptionAllowUserInteraction),//設定了這個,主執行緒可以接收點選事件
@(UIViewAnimationOptionBeginFromCurrentState),//從當前狀態開始動畫,父層動畫運動期間,開始子層動畫。
@(UIViewAnimationOptionRepeat),//重複執行動畫,從開始到結束, 結束後直接跳到開始態
@(UIViewAnimationOptionAutoreverse),//反向執行動畫,結束後會再從結束態->開始態
@(UIViewAnimationOptionOverrideInheritedDuration),//忽略繼承自父層持續時間,使用自己持續時間(如果存在)
@(UIViewAnimationOptionOverrideInheritedCurve),//忽略繼承自父層的線性效果,使用自己的線性效果(如果存在)
@(UIViewAnimationOptionAllowAnimatedContent),//允許同一個view的多個動畫同時進行
@(UIViewAnimationOptionShowHideTransitionViews),//檢視切換時直接隱藏舊檢視、顯示新檢視,而不是將舊檢視從父檢視移除(僅僅適用於轉場動畫)
@(UIViewAnimationOptionOverrideInheritedOptions),//不繼承父動畫設定或動畫型別。
];
// CASpringAnimation
// CASpringAnimation
if (sigle)
{
[UIView transitionWithView:self.opView1
duration:1
options:
((NSNumber *)array0[change1_0Index]).integerValue|
((NSNumber *)array1[change1_1Index]).integerValue|
((NSNumber *)array2[change1_2Index]).integerValue
animations:^{
/**
* 單檢視的轉場動畫需要在動畫塊中設定檢視轉場前的內容和檢視轉場後的內容
*/
if (self.opView1.tag == 0)
{
self.opView1.image = [UIImage imageNamed:@"IMG_1730"];
self.opView1.tag = 1;
}
else
{
self.opView1.image = [UIImage imageNamed:@"93F5E460-9D31-4780-8511-37FF91033402"];
self.opView1.tag = 0;
}
} completion:nil];
NSLog(@"動畫:%s:%@:%@:%@",__func__,@(change1_0Index),@(change1_1Index),@(change1_2Index));
}
else
{
/**
* 雙檢視的轉場動畫
* 注意:雙檢視的轉場動畫實際上是操作檢視移除和新增到父檢視的一個過程,from檢視必須要有父檢視,to檢視必須不能有父檢視,否則會出問題
* 比如動畫不準等
*/
UIImageView *fromView = nil;
UIImageView *toView = nil;
if (self.opView1.tag == 0)
{
fromView = self.opView1;
toView = self.opView2;
self.opView1.tag = 1;
}
else
{
fromView = self.opView2;
toView = self.opView1;
self.opView1.tag = 0;
}
[UIView transitionFromView:fromView
toView:toView duration:1.0
options:
((NSNumber *)array0[change1_0Index]).integerValue|
((NSNumber *)array1[change1_1Index]).integerValue|
((NSNumber *)array2[change1_2Index]).integerValue
completion:^(BOOL finished) {
[fromView removeFromSuperview];
}];
[self.view addSubview:toView];
}
change1_0Index += 1;
if (change1_0Index > array0.count - 1)
{
change1_0Index = 0;
change1_1Index += 1;
}
if (change1_1Index > array1.count - 1)
{
change1_1Index = 0;
change1_2Index += 1;
}
if (change1_2Index > array2.count - 1)
{
change1_2Index = 0;
change1_0Index = 0;
change1_2Index = 0;
}
}
/**
* 基於CATransition的檢視轉場動畫
*/
static NSUInteger change3_0Index = 0;
static NSUInteger change3_1Index = 0;
-(void)chang3
{
/**
*建立轉場動畫:注意:CATransaction和CATransition 不一樣
*/
CATransition *transition = [CATransition animation];
transition.duration = 0.25;
NSArray *type_array = @[
//系統提供的動畫
kCATransitionFade,
kCATransitionMoveIn,
kCATransitionPush,
kCATransitionReveal,
//以下是私有api,只能字串訪問
@"cube",//立方體翻轉效果
@"oglFlip",//翻轉效果
@"suckEffect",//收縮效果,動畫方向不可控
@"rippleEffect",//水滴波紋效果,動畫方向不可控
@"pageCurl",//向上翻頁效果
@"pageUnCurl",//向下翻頁效果
@"cameralIrisHollowOpen",//攝像頭開啟效果,動畫方向不可控
@"cameraIrisHollowClose",//攝像頭關閉效果,動畫方向不可控
];
//轉場型別
transition.type = type_array[change3_0Index];
NSArray *subtype_array = @[
kCATransitionFromRight,
kCATransitionFromLeft,
kCATransitionFromTop,
kCATransitionFromBottom
];
//轉場方向
transition.subtype = subtype_array[change3_1Index];
/**
* 設定轉場動畫的開始和結束百分比
*/
transition.startProgress = 0.0;
transition.endProgress = 1.0;
if (self.opView1.tag == 0)
{
self.opView1.tag = 1;
self.opView1.image = [UIImage imageNamed:@"IMG_1730"];
self.opView2.image = [UIImage imageNamed:@"93F5E460-9D31-4780-8511-37FF91033402"];
}
else
{
self.opView1.tag = 0;
self.opView1.image = [UIImage imageNamed:@"93F5E460-9D31-4780-8511-37FF91033402"];
self.opView2.image = [UIImage imageNamed:@"IMG_1730"];
}
[self.opView1.layer addAnimation:transition forKey:nil];
[self.opView2.layer addAnimation:transition forKey:nil];
NSLog(@"動畫:%s:%@:%@",__func__,@(change3_0Index),@(change3_1Index));
change3_1Index += 1;
if (change3_1Index > subtype_array.count - 1)
{
change3_1Index = 0;
change3_0Index += 1;
}
if (change3_0Index > type_array.count - 1)
{
change3_0Index = 0;
}
}