iOS動畫篇之CoreAnimation動畫
9月 22, 2016發(fā)布在Objective-C
App如果想被大眾喜歡浩销,漂亮的UI和精美的動畫都是必不可少的英支,蘋果雖然為UIView提供了一些常用動畫碉哑,但是大部分看起來比較不錯的效果都是通過操作Layer層實現(xiàn)的度陆,因此了解核心動畫是必要的.CoreAnimation是直接作用在CALayer上的(并非UIView上)非常強大的跨Mac OS X和iOS平臺的動畫處理API蔽介,Core Animation的動畫執(zhí)行過程都是在后臺操作的摘投,不會阻塞主線程。
開發(fā)中用的最多的是CoreAnimation動畫庫虹蓄,簡稱是CA犀呼,所以動畫類都是CA開頭。所有的動畫類都在QuartzCore庫中薇组,在iOS7之前使用需要#import ,iOS7之后系統(tǒng)已經(jīng)將其自動導入了外臂。CoreAnimation動畫都是作用在layer上。
先來看下動畫類的層級關系:
關于上圖中的層級結構只需要了解一下律胀,用的多了宋光,自然就記住了。本篇只講述CABasicAnimation炭菌、CAKeyframeAnimation罪佳、CAAnimationGroup的使用。
上面講了CA動畫都是作用在Layer上黑低,而CA動畫中修改的也是Layer的動畫屬性赘艳,可以產(chǎn)生動畫的layer屬性也有Animatable標識。
CABasicAnimation動畫主要是設置某個動畫屬性的初始值fromValue和結束值toValue克握,來產(chǎn)生動畫效果蕾管。
先上個示例代碼,將一個視圖往上移動一段距離:
CABasicAnimation*postionAnimation = [CABasicAnimationanimationWithKeyPath:@"position.y"];
postionAnimation.duration =1.0f;
postionAnimation.fromValue = @(self.squareView.center.y);
postionAnimation.toValue = @(self.squareView.center.y -300);
postionAnimation.removedOnCompletion =NO;
postionAnimation.delegate =self;
postionAnimation.autoreverses =YES;
postionAnimation.fillMode = kCAFillModeForwards;
postionAnimation.timingFunction = [CAMediaTimingFunctionfunctionWithName:kCAMediaTimingFunctionEaseInEaseOut];
[self.squareView.layer addAnimation:postionAnimation forKey:@"posstionAnimation"];
動畫的創(chuàng)建使用animationWithKeyPath:,因為使用的keyPath所以動畫屬性或者其結構體中元素都可以產(chǎn)生動畫菩暗。
duration動畫的時長娇掏。
fromValue和toValue是CABasicAnimation的屬性,都是id類型的勋眯,所以要將基本類型包裝成對象婴梧。
removedOnCompletion決定動畫執(zhí)行完之后是否將該動畫的影響移除下梢,默認是YES,則layer回到動畫前的狀態(tài)。
fillMode是個枚舉值(四種)塞蹭,當removedOnCompletion設置為NO之后才會起作用孽江。可以設置layer是保持動畫開始前的狀態(tài)還是動畫結束后的狀態(tài)番电,或是其他的岗屏。
autoreverses表示動畫結束后是否 backwards(回退) 到動畫開始前的狀態(tài)∈欤可與上面兩個屬性組合出不同效果这刷。
timingFunction動畫的運動是勻速線性的還是先快后慢等,類似UIView動畫的opitions娩井。另外暇屋,CAMediaTimingFunction 方法可以自定義。
delegate代理洞辣,兩個動畫代理方法:- (void)animationDidStart:(CAAnimation *)anim; 和- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag;
-(void)addAnimation:(CAAnimation *)anim forKey:(nullable NSString *)key; 給某個layer添加動畫咐刨,與之對應的移除某個動畫是- (void)removeAnimationForKey:(NSString *)key;
還有一些其他的屬性,就不一一介紹了扬霜,可以在使用的使用去.h文件中查看定鸟。
在這里簡單介紹一下fillMode
注意:fillMode這個屬性,必須要配合下面這個屬性來使用著瓶。這個屬性的默認值是YES(回到原處),此時fillMode是沒有作用的如果設置為NO那么就需要設置一個fillMode屬性联予,就是動畫結束之后的狀態(tài)杆兵,如果不設置躬拢,動畫也會回到原處。
postionAnimation.removedOnCompletion = NO;
kCAFillModeRemoved 這個是默認值,也就是說當動畫開始前和動畫結束后,動畫對layer都沒有影響,動畫結束后,layer會恢復到之前的狀態(tài)
kCAFillModeForwards 當動畫結束后,layer會一直保持著動畫最后的狀態(tài)
kCAFillModeBackwards 這個和kCAFillModeForwards是相對的,就是在動畫開始前,你只要將動畫加入了
一個layer,layer便立即進入動畫的初始狀態(tài)并等待動畫開始.你可以這樣設定測試代碼,將一個動畫加入一個
layer的時候延遲5秒執(zhí)行.然后就會發(fā)現(xiàn)在動畫沒有開始的時候,只要動畫被加入了layer,layer便處于動畫初
始狀態(tài)
kCAFillModeBoth 理解了上面兩個,這個就很好理解了,這個其實就是上面兩個的合成.動畫加入后開始之
前,layer便處于動畫初始狀態(tài),動畫結束后layer保持動畫最后的狀態(tài).
Z軸
CABasicAnimation *rotationAnimation = [CABasicAnimation animationWithKeyPath:@"transform.rotation"];
rotationAnimation.fromValue = [NSNumber numberWithFloat:0.0];
rotationAnimation.toValue = [NSNumber numberWithFloat:2*M_PI];
rotationAnimation.repeatCount = MAXFLOAT;
rotationAnimation.duration =10;
[self.rotationImgView.layer addAnimation:rotationAnimation forKey:@"transform.rotation.z"];
X軸
CABasicAnimation *rotationXAnimation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.x"];
rotationXAnimation.fromValue = [NSNumber numberWithFloat:0.0];
rotationXAnimation.toValue = [NSNumber numberWithFloat:2*M_PI];
rotationXAnimation.repeatCount = MAXFLOAT;
rotationXAnimation.duration =3;
[self.rotationXImgView.layer addAnimation:rotationXAnimation forKey:@"transform.rotation.x"];
Y軸
CABasicAnimation *rotationYAnimation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.y"];
rotationYAnimation.fromValue = [NSNumber numberWithFloat:0.0];
rotationYAnimation.toValue = [NSNumber numberWithFloat:2*M_PI];
rotationYAnimation.repeatCount = MAXFLOAT;
rotationYAnimation.duration =10;
[self.rotationYImgView.layer addAnimation:rotationYAnimation forKey:@"transform.rotation.y"];
任意方向放大
CABasicAnimation *scaleAnimation = [CABasicAnimation animationWithKeyPath:@"transform.scale"];
scaleAnimation.fromValue = [NSNumber numberWithFloat:1.0];
scaleAnimation.toValue = [NSNumber numberWithFloat:1.5];
scaleAnimation.autoreverses = YES;//自動反向執(zhí)行動畫效果
scaleAnimation.repeatCount = MAXFLOAT;
scaleAnimation.duration = 0.8;
[self.scaleImgView.layer addAnimation:scaleAnimation forKey:@"FlyElephant.scale"];
X軸放大
CABasicAnimation *scaleXAnimation = [CABasicAnimation animationWithKeyPath:@"transform.scale.x"];
scaleXAnimation.fromValue = [NSNumber numberWithFloat:1.0];
scaleXAnimation.toValue = [NSNumber numberWithFloat:1.5];
scaleXAnimation.autoreverses = YES;//自動反向執(zhí)行動畫效果
scaleXAnimation.repeatCount = MAXFLOAT;
scaleXAnimation.duration = 0.8;
[self.scaleXImgView.layer addAnimation:scaleXAnimation forKey:@"FlyElephant.scale.x"];
Y軸放大
CABasicAnimation *scaleYAnimation = [CABasicAnimation animationWithKeyPath:@"transform.scale.y"];
scaleYAnimation.fromValue = [NSNumber numberWithFloat:1.0];
scaleYAnimation.toValue = [NSNumber numberWithFloat:1.5];
scaleYAnimation.autoreverses = YES;//自動反向執(zhí)行動畫效果
scaleYAnimation.repeatCount = MAXFLOAT;
scaleYAnimation.duration = 0.8;
[self.scaleYImgView.layer addAnimation:scaleYAnimation forKey:@"FlyElephant.scale.y"];
Z軸放大
CABasicAnimation *scaleZAnimation = [CABasicAnimation animationWithKeyPath:@"transform.scale.z"];
scaleZAnimation.fromValue = [NSNumber numberWithFloat:1.0];
scaleZAnimation.toValue = [NSNumber numberWithFloat:1.5];
scaleZAnimation.autoreverses = YES;//自動反向執(zhí)行動畫效果
scaleZAnimation.repeatCount = MAXFLOAT;
scaleZAnimation.duration = 0.8;
[self.scaleZImgView.layer addAnimation:scaleZAnimation forKey:@"FlyElephant.scale.z"];
X軸平移
CABasicAnimation *translationX=[CABasicAnimation animationWithKeyPath:@"transform.translation.x"];
translationX.toValue=@(200);
translationX.duration=5;
translationX.removedOnCompletion=NO;
translationX.fillMode=kCAFillModeForwards;
translationX.repeatCount=MAXFLOAT;
translationX.autoreverses=YES;
[self.translationXImgView.layer addAnimation:translationX forKey:@"FlyElephant.translation.x"];
Y軸平移
CABasicAnimation *translationY=[CABasicAnimation animationWithKeyPath:@"transform.translation.y"];
translationY.toValue=@(100);
translationY.duration=5;
translationY.removedOnCompletion=NO;
translationY.fillMode=kCAFillModeForwards;
translationY.repeatCount=MAXFLOAT;
translationY.autoreverses=YES;
[self.translationYImgView.layer addAnimation:translationY forKey:@"FlyElephant.translation.y"];
XY軸平移
CABasicAnimation *translation=[CABasicAnimation animationWithKeyPath:@"transform.translation"];
translation.toValue=[NSValue valueWithCGPoint:CGPointMake(100, 100)];
translation.duration=5;
translation.removedOnCompletion=NO;
translation.fillMode=kCAFillModeForwards;
translation.repeatCount=MAXFLOAT;
translation.autoreverses=YES;
[self.translationImgView.layer addAnimation:translation forKey:@"FlyElephant.translation"];
動畫實例圖:
CAKeyframeAnimation我們一般稱為關鍵幀動畫安聘,主要是利用其values屬性华糖,設置多個關鍵幀屬性值麦向,來產(chǎn)生動畫瘟裸。
CAKeyframeAnimation *keyAnimation = [CAKeyframeAnimation animationWithKeyPath:@"transform"];
keyAnimation.duration = 1.0f;
keyAnimation.beginTime = CACurrentMediaTime() + 1.0;
CATransform3D transform1 = CATransform3DMakeScale(1.5, 1.5, 0);
CATransform3D transform2 = CATransform3DMakeScale(0.8, 0.8, 0);
CATransform3D transform3 = CATransform3DMakeScale(3, 3, 0);
keyAnimation.values = @[[NSValue valueWithCATransform3D:transform1],[NSValue valueWithCATransform3D:transform2],[NSValue valueWithCATransform3D:transform3]];
keyAnimation.keyTimes = @[@0,@0.5,@1];
keyAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
keyAnimation.removedOnCompletion = NO;
keyAnimation.fillMode = kCAFillModeForwards;
[_someView.layer addAnimation:keyAnimation forKey:nil];
beginTime也是CAAnimation類的屬性客叉,可以設置動畫延遲多久執(zhí)行,示例代碼是延遲1秒執(zhí)行话告。
values是CAKeyframeAnimation的屬性兼搏,設置keyPath屬性在幾個關鍵幀的值,也是id類型的沙郭。
keyTimes也是CAKeyframeAnimation的屬性佛呻,每個值對應相應關鍵幀的時間比例值。
timingFunctions也是CAKeyframeAnimation的屬性病线,對應每個動畫段的動畫過渡情況吓著;而timingFunction是CAAnimation的屬性鲤嫡。
CGPoint p1=CGPointMake(self.positionImgView.center.x, self.positionImgView.center.y);
CGPoint p2=CGPointMake(80, 100);
CGPoint p3=CGPointMake(100, 120);
CGPoint p4=CGPointMake(120, 150);
CGPoint p5=CGPointMake(140, 160);
NSArray *values=[NSArray arrayWithObjects:[NSValue valueWithCGPoint:p1],[NSValue valueWithCGPoint:p2],[NSValue valueWithCGPoint:p3],[NSValue valueWithCGPoint:p4],[NSValue valueWithCGPoint:p5], nil];
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
[animation setValues:values];
[animation setDuration:10.0];
[animation setCalculationMode:kCAAnimationCubic];
animation.timingFunction=[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
[self.positionImgView.layer addAnimation:animation forKey:@"FlyElephant.point"];
CGMutablePathRef path = CGPathCreateMutable();
CGPathMoveToPoint(path,NULL,self.positionImgView.center.x,self.positionImgView.center.y);
for(NSInteger i = 1; i < 5 i++){
CGPathAddLineToPoint(path, NULL, self.positionImgView.center.x+i*10,? ? self.positionImgView.center.y);
}
//曲線
CGPathAddCurveToPoint(path,NULL,50.0,275.0,150.0,275.0,70.0,120.0);
CGPathAddCurveToPoint(path,NULL,150.0,275.0,250.0,275.0,90.0,120.0);
CGPathAddCurveToPoint(path,NULL,250.0,275.0,350.0,275.0,110.0,120.0);
CGPathAddCurveToPoint(path,NULL,350.0,275.0,450.0,275.0,130.0,120.0);
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
[animation setPath:path];
[animation setDuration:3.0];
// [animation setAutoreverses:YES];
CFRelease(path);
[self.positionImgView.layer addAnimation:animation:@"FlyElephant"];
通過代碼我們發(fā)現(xiàn),Path和values接收都是一個數(shù)組绑莺,而不是一個固定值暖眼,這里面我們沒有設置keyTimes,下面看一個常見的抖動效果:
CAKeyframeAnimation *animation = [CAKeyframeAnimation animation];
animation.keyPath = @"position.x";
animation.values = @[ @0, @20, @-20, @20, @0 ];
animation.keyTimes = @[ @0, @(1 /8.0), @(1/ 2.0), @(7/ 8.0), @1 ];
animation.duration =0.5;
animation.additive = YES;
[self.textField.layer addAnimation:animation forKey:@"FlyElephant.Shake"];
CAAnimationGroup的用法與其他動畫類一樣纺裁,都是添加到layer上诫肠,比CAAnimation多了一個animations屬性。
先看示例代碼欺缘,動畫效果是視圖一邊向上移動栋豫,一邊繞Y軸旋轉:
CABasicAnimation *rotationYAnimation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.y"];
rotationYAnimation.fromValue = @0;
rotationYAnimation.toValue = @(M_PI);
rotationYAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
CABasicAnimation *postionAnimation = [CABasicAnimation animationWithKeyPath:@"position.y"];
postionAnimation.fromValue = @(_markView.center.y);
postionAnimation.toValue = @(_markView.center.y - 100);
postionAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
CAAnimationGroup *animationGroup = [CAAnimationGroup animation];
animationGroup.duration = kUpDuration;
animationGroup.removedOnCompletion = NO;
animationGroup.fillMode = kCAFillModeForwards;
animationGroup.delegate = self;
animationGroup.animations = @[rotationYAnimation, postionAnimation];
[_markView.layer addAnimation:animationGroup forKey:kJumpAnimation];
CAAnimationGroup的animations中可以放其他任何動畫類(包括CAAnimationGroup),需要注意的是animations里的動畫設置了duration之后動畫可能會有不同谚殊,一般里面不設置丧鸯,在最外層設置group的duration即可。
最開始做動畫一般都會對keyPath這個值莫名其妙络凿,因為它不是常量骡送,需要變換的時候找不到對應的需要設置的值,如果你在網(wǎng)上搜索絮记,很可能看到的是這張圖:
下面這張圖你基本上是找不到的摔踱,如下:
CATransition一般來做轉場動畫。先上gif動畫效果
//修改視圖的背景色
_someView.backgroundColor = [UIColor greenColor];
CATransition *animation = [CATransition animation];
animation.duration = 0.5;
/* 這里可設置的參數(shù)有:kCATransitionFade怨愤、kCATransitionPush派敷、kCATransitionReveal、kCATransitionMoveIn撰洗、
"cube"篮愉、"suckEffect"、"oglFlip"差导、"rippleEffect"试躏、"pageCurl"、"pageUnCurl"设褐、"cameraIrisHollowOpen"颠蕴、
"cameraIrisHollowClose",這些都是動畫類型
*/
animation.type = @"cube";
// 動畫執(zhí)行的方向助析,kCATransitionFromRight犀被、kCATransitionFromLeft、kCATransitionFromTop外冀、kCATransitionFromBottom
animation.subtype = kCATransitionFromRight;
animation.timingFunction = UIViewAnimationOptionCurveEaseInOut;
[_someView.layer addAnimation:animation forKey:nil];
//也可以寫這里
//? ? _someView.backgroundColor = [UIColor greenColor];
只需要在動畫開始前或者動畫開始后替換掉視圖上顯示的內(nèi)容即可寡键。具體可以看我的這篇博客iOS動畫之CATransition動畫
附加的內(nèi)容是關于CALayer和UIBezierPath。個人覺得理解了UIBezierPath和CALayer,才能更好的理解CoreAnimation動畫雪隧。
UIBezierPath主要是用來繪制路徑的西轩,分為一階员舵、二階…..n階。一階是直線藕畔,二階以上才是曲線固灵。而最終路徑的顯示還是得依靠CALayer。用CoreGraphics將路徑繪制出來劫流,最終也是繪制到CALayer上巫玻。
方法一:構造bezierPath對象,一般用于自定義路徑祠汇。
方法二:繪制圓弧路徑仍秤,參數(shù)1是中心點位置,參數(shù)2是半徑可很,參數(shù)3是開始的弧度值诗力,參數(shù)4是結束的弧度值,參數(shù)5是是否順時針(YES是順時針方向我抠,NO逆時針)苇本。
方法三:根據(jù)某個路徑繪制路徑。
方法四:根據(jù)某個CGRect繪制內(nèi)切圓或橢圓(CGRect是正方形即為圓菜拓,為長方形則為橢圓)瓣窄。
方法五:根據(jù)某個CGRect繪制路徑。
方法六:繪制帶圓角的矩形路徑纳鼎,參數(shù)2哪個角俺夕,參數(shù)3,橫贱鄙、縱向半徑劝贸。
方法七:繪制每個角都是圓角的矩形,參數(shù)2是半徑逗宁。
自定義路徑時常用的API:
- (void)moveToPoint:(CGPoint)point; // 移到某個點
- (void)addLineToPoint:(CGPoint)point; // 繪制直線
- (void)addCurveToPoint:(CGPoint)endPoint controlPoint1:(CGPoint)controlPoint1 controlPoint2:(CGPoint)controlPoint2; //繪制貝塞爾曲線
- (void)addQuadCurveToPoint:(CGPoint)endPoint controlPoint:(CGPoint)controlPoint; // 繪制規(guī)則的貝塞爾曲線
- (void)addArcWithCenter:(CGPoint)center radius:(CGFloat)radius startAngle:(CGFloat)startAngle endAngle:(CGFloat)endAngle clockwise:(BOOL)clockwise
// 繪制圓形曲線
- (void)appendPath:(UIBezierPath *)bezierPath; // 拼接曲線
有三種方式:1、直接使用UIBezierPath的方法瞎颗;2件甥、使用CoreGraphics繪制;3言缤、利用CAShapeLayer繪制嚼蚀。
示例代碼如下禁灼,繪制一個右側為弧型的視圖:
- (void)drawRect:(CGRect)rect
{
UIColor *fillColor = [UIColor colorWithRed:0.0 green:0.722 blue:1.0 alpha:1.0];
UIBezierPath *bezierPath = [UIBezierPath bezierPath];
[bezierPath moveToPoint:CGPointMake(0, 0)];
[bezierPath addLineToPoint:CGPointMake(rect.size.width - spaceWidth, 0)];
[bezierPath addQuadCurveToPoint:CGPointMake(rect.size.width - spaceWidth, rect.size.height) controlPoint:CGPointMake(rect.size.width - spaceWidth + _deltaWith, rect.size.height * 0.5)];
[bezierPath addLineToPoint:CGPointMake(0, rect.size.height)];
[bezierPath addLineToPoint:CGPointMake(0, 0)];
[bezierPath closePath];
// 1管挟、bezierPath方法
//? ? [fillColor setFill];
//? ? [bezierPath fill];
// 2、使用CoreGraphics
//? ? CGContextRef ctx = UIGraphicsGetCurrentContext();
//? ? CGContextAddPath(ctx, bezierPath.CGPath);
//? ? CGContextSetFillColorWithColor(ctx, fillColor.CGColor);
//? ? CGContextFillPath(ctx);
// 3.CAShaperLayer
[self.layer.sublayers makeObjectsPerformSelector:@selector(removeFromSuperlayer)];
CAShapeLayer *shapeLayer = [CAShapeLayer layer];
shapeLayer.path = bezierPath.CGPath;
shapeLayer.fillColor = fillColor.CGColor;
[self.layer addSublayer:shapeLayer];
}
上圖這樣的視圖是用UIBezierPath用多個CAShapeLayer制作出來的弄捕,而動畫效果只需要改變進度的layer的strokeEnd和修改下面代表水面進度的視圖位置即可僻孝。動畫的組合也可以有多種方式組合
動畫的示例代碼:
- (void)setProgress:(CGFloat)progress animated:(BOOL)animated duration:(NSTimeInterval)duration
{
CGFloat tempPro = progress;
if (tempPro > 1.0) {
tempPro = 1.0;
} else if (progress < 0.0){
tempPro = 0.0;
}
_progress = tempPro;
CABasicAnimation *pathAniamtion = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
pathAniamtion.duration = duration;
pathAniamtion.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
pathAniamtion.fromValue = [NSNumber numberWithFloat:0.0f];
pathAniamtion.toValue = [NSNumber numberWithFloat:_progress];
pathAniamtion.autoreverses = NO;
[_progressLayer addAnimation:pathAniamtion forKey:nil];
// 水位上升的動畫
if (!_showSolidAnimation) {
return;
}
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
_imageView.transform = CGAffineTransformIdentity;
[UIView animateWithDuration:duration animations:^{
CGRect rect = _imageView.frame;
CGFloat dy = rect.size.height * progress;
_imageView.transform = CGAffineTransformMakeTranslation(0, -dy);
}];
});
}
在用自定義的CAShapeLayer做動畫時导帝,建議在動畫開始前先將動畫屬性與最終的屬性值一致,再開始動畫穿铆,不要使用removedOnCompletion控制最終的狀態(tài)您单,這在WWDC蘋果這么建議