此文章單方面對 貝塞爾曲線畫圖 核心動畫 圖層相關 方面做下整理,方便查看!
基本概念
Core Animation(核心動畫)是一組功能強大,在開發(fā)中可以用他來實現很多復雜和絢麗的動畫效果,核心動畫作用在CALayer(Core animation layer)上
結構
代碼和效果
/**
* fillMode 視圖在非Active時的行為
*
* kCAFillModeForwards 動畫開始之后layer迅速移到動畫開始的位置
* kCAFillModeBackwards 動畫被添加的那一刻(動畫開始之前)layer迅速移到開始位置,并且在 removedOnCompletion 為 NO 的情況下,動畫結束會移到layer本身的位置
* kCAFillModeBoth 動畫添加的那一刻(動畫開始之前)layer迅速移到開始位置,并且在 removedOnCompletion 為 NO 的情況下,動畫結束layer會停留在動畫結束的位置
* kCAFillModeRemoved 動畫開始之后layer迅速移到動畫開始的位置,并且在 removedOnCompletion 為 NO 的情況下,動畫結束回憶道layer本身位置
*/
/**
* timingFunction 動畫節(jié)奏 [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]
*
* kCAMediaTimingFunctionLinear 勻速
* kCAMediaTimingFunctionEaseIn 慢進
* kCAMediaTimingFunctionEaseOut 慢出
* kCAMediaTimingFunctionEaseInEaseOut 慢進慢出
* kCAMediaTimingFunctionDefault 默認值(慢進慢出)
*/
/**
* removedOnCompletion 動畫執(zhí)行完畢后是否從圖層上移除 默認為 YES (動畫結束layer移到本身位置)
*/
/**
* 這樣個要配合使用 repeatDuration = repeatCount * duration(動畫一遍持續(xù)時間,主要控制速度)
* repeatCount 動畫重復執(zhí)行次數
* repeatDuration 動畫重復執(zhí)行時間
*/
CABasicAnimation
1.位置相關動畫 (position.y 和 position.x transform.translation.x 和 transform.translation.y)
position fromValue:默認自身位置為起始位置 toValue:移動后的位置
transform.translation fromValue:默認自身位置為起始位置(默認0) toValue:相對于layer原始位置距離
- position.y 和 position.x
/// y軸方向移動 fromValue:默認自身位置為起始位置 toValue:移動后的位置
- (void)animation_CABasicAnimation_position_y
{
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position.y"];
//animation.fromValue = @(self.img.center.y); 默認初始位置
animation.toValue = @(self.img.center.y + 100);
animation.duration = 3;
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
[self.img.layer addAnimation:animation forKey:@"position.y"];
}
- transform.translation.x 和 transform.translation.y
- (void)animation_CABasicAnimation_translation_x
{
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform.translation.x"];
//animation.fromValue = @0; 默認初始位置
animation.toValue = @100;
animation.duration = 1;
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
[self.img.layer addAnimation:animation forKey:@"transform.translation.x"];
}
2.旋轉相關動畫
- transform.rotation.x | transform.rotation.y | transform.rotation.z
3.縮放相關動畫
- transform.scale.x | transform.scale.y | transform.scale.z
- (void)animation_CABasicAnimation_transform_scale
{
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform.scale"];
animation.toValue = @8.0;
animation.duration = 3;
animation.removedOnCompletion = NO;
animation.fillMode = kCAFillModeForwards;
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
[self.img.layer addAnimation:animation forKey:@"transform.scale"];
}
4.自身大小相關
- bounds.origin.x | bounds.origin.y
- 動畫view圖層上圖層和view上的其他控件 不動畫view本身
- 方向x 右為負 左為正
- 方向y 上為負 下為正
//bounds.origin.x
//animation1.fromValue = @0; 默認自身位置為開始位置 為0
animation1.toValue = @(-self.img.bounds.size.width * 0.5);
//bounds.origin.y
//animation2.fromValue = @0; 默認自身位置為開始位置 為0
animation2.toValue = @(-self.img.bounds.size.height * 0.5);
- bounds.size.width | bounds.size.height
- 動畫view本身 不動畫view上的其他控件
//bounds.size.width
//animation1.fromValue = @(self.img.bounds.size.width); 默認起始值為自身寬度
animation1.toValue = @(self.img.bounds.size.width * 0.5);
//bounds.size.height
//animation2.fromValue = @(self.img.bounds.size.height); 默認起始值為自身高度
animation2.toValue = @(self.img.bounds.size.height * 0.5);
5.邊角動畫
- cornerRadius | borderWidth | borderColor
//cornerRadius
//animation1.fromValue = @(self.img.layer.cornerRadius); 默認自身圓角為起始值
animation1.toValue = @50;
animation1.repeatCount = 3;
animation1.repeatDuration = animation.duration * animation.repeatCount;
//borderWidth
//animation.fromValue = @(self.img.layer.borderWidth); 默認自身邊框寬度為起始值
animation2.toValue = @20;
animation2.repeatCount = 3;
animation2.repeatDuration = animation.duration * animation.repeatCount;
//borderColor
//animation3.toValue = (__bridge id _Nullable)([self.img.layer borderColor]); //默認自身邊框顏色為起始值
animation3.toValue = (__bridge id _Nullable)([[UIColor cyanColor] CGColor]);
animation3.repeatCount = 3;
animation3.repeatDuration = animation.duration * animation.repeatCount;
6.自身的一些屬性
- opacity 不透明
animation.fromValue = @1;
animation.toValue = @0;
- backgroundColor
animation.toValue = (__bridge id _Nullable)([[UIColor yellowColor] CGColor]);
- contents (layer.contents)
animation.fromValue = (__bridge id _Nullable)([[UIImage imageNamed:@"春雨醫(yī)生"] CGImage]);
animation.toValue = (__bridge id _Nullable)([[UIImage imageNamed:@"丁香醫(yī)生"] CGImage]);
7.陰影相關動畫
- shadowOffset 陰影位置偏移
//self.img.layer.shadowOpacity = 0.5;
//self.img.layer.shadowOffset = CGSizeMake(0, 0);
animation.fromValue = [NSValue valueWithCGSize:CGSizeMake(0, 0)];
animation.toValue = [NSValue valueWithCGSize:CGSizeMake(20, 20)];
animation.duration = 3;
animation.removedOnCompletion = NO;
animation.fillMode = kCAFillModeForwards;
- shadowColor 陰影顏色
//self.img.layer.shadowOpacity = 0.5;
//self.img.layer.shadowOffset = CGSizeMake(20, 20);
animation.toValue = (__bridge id _Nullable)([UIColor redColor].CGColor);
- shadowOpacity 陰影不透明值 01(透明完全不透明)
//self.img.layer.shadowOpacity = 0;
//self.img.layer.shadowOffset = CGSizeMake(20, 20);
animation.fromValue = @0;
animation.toValue = @1;
- shadowRadius 暫且叫做陰影模糊度吧
//self.img.layer.shadowOpacity = 0.5;
//self.img.layer.shadowOffset = CGSizeMake(20, 20);
animation.fromValue = @3;
animation.toValue = @10;
CAKeyframeAnimation
可以看做是一個有更多位置設定的CABaseAnimation,可以設定keyPath起點、中間關鍵點(可以是多個)、終點的值,每一幀所對應的時間,動畫會沿著設定點進行移動
CAKeyframeAnimation的一些獨有屬性
- values: 關鍵幀數組對象,里面每一個元素即為一個關鍵幀
- path: 動畫路徑對象,可以指定一個路徑,在執(zhí)行動畫時路徑會沿著路徑移動,注:Path在動畫中只會影響視圖的Position
- keyTimes: 設置關鍵幀對應的時間數組,范圍:0.0-1.0之間的浮點型
- 數組中的每一個連續(xù)值都必須大于或等于前面的值,因為里面存儲的是動畫持續(xù)時間內的每一幀的時間點,時間點是從0%-100%,時間不可能回退
- 為了得到最好的結果,數組中的元素個數應該與values中的元素個數或路徑屬性中的控制點的數量相匹配
- timingFunctions: 設置關鍵幀對應速度效果的數組
- calculationMode: 這個屬性用來設定 關鍵幀中間的值是怎么被計算的
NSString * const kCAAnimationLinear
NSString * const kCAAnimationDiscrete 只展示關鍵幀的狀態(tài),沒有中間過程,沒有動畫
NSString * const kCAAnimationPaced
NSString * const kCAAnimationCubic
NSString * const kCAAnimationCubicPaced
- (void)animation_CAKeyframeAnimation_Rect
{
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
animation.duration = 4.0;
animation.repeatCount = 2;
animation.repeatDuration = animation.duration * animation.repeatCount;
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
NSValue *value1 = [NSValue valueWithCGPoint:CGPointMake(self.img.center.x, self.img.center.y)];
NSValue *value2 = [NSValue valueWithCGPoint:CGPointMake(self.img.center.x + 150, self.img.center.y)];
NSValue *value3 = [NSValue valueWithCGPoint:CGPointMake(self.img.center.x + 150, self.img.center.y + 150)];
NSValue *value4 = [NSValue valueWithCGPoint:CGPointMake(self.img.center.x, self.img.center.y + 150)];
NSValue *value5 = [NSValue valueWithCGPoint:CGPointMake(self.img.center.x, self.img.center.y)];
animation.values = @[value1, value2, value3, value4, value5];
animation.keyTimes = @[@0, @0.4, @0.5, @0.9, @1.0];
/* 利用貝塞爾畫的一個矩形,跟上面效果一樣,只不過不能設置關鍵幀動畫時間
UIBezierPath *path = [UIBezierPath bezierPathWithRect:CGRectMake(self.img.center.x, self.img.center.y, 150, 150)];
animation.path = path.CGPath;
*/
[self.img.layer addAnimation:animation forKey:@"position"];
}
利用貝塞爾畫個圓路徑動畫
- (void)animation_CAKeyframeAnimation_Circle
{
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
animation.duration = 3;
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
animation.repeatCount = 2;
animation.repeatDuration = animation.repeatCount * animation.duration;
UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(self.img.center.x + 75, self.img.center.y) radius:75 startAngle:M_PI endAngle:3*M_PI clockwise:YES];
animation.path = path.CGPath;
[self.img.layer addAnimation:animation forKey:@"position"];
}
CATransition
轉場動畫,在開發(fā)中巧用會有意想不到的效果,還方便
/**
* CATransition (type) 過渡動畫的類型
*
* kCATransitionFade 漸變
* kCATransitionMoveIn 覆蓋
* kCATransitionPush 推出
* kCATransitionReveal 揭開(可以說是抽開)
*
* 私有動畫類型的值有:"cube"、"suckEffect"、"oglFlip"、 "rippleEffect"、"pageCurl"、"pageUnCurl"等等
*/
/**
* CATransition (subtype) 過渡動畫的方向
*
* kCATransitionFromRight 從右邊
* kCATransitionFromLeft 從左邊
* kCATransitionFromTop 從頂部
* kCATransitionFromBottom 從底部
*/
下面來組示例,看下效果,生成四個顏色的圖片,來個切換動畫
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
static int i = 0;
i = i >= self.imgsArr.count-1 ? 0:i+1;
self.img.image = [self.imgsArr objectAtIndex:i];
CATransition *animation = [CATransition animation];
animation.duration = 0.5;
animation.type = @"cube";
animation.subtype = kCATransitionFromRight;
[self.img.layer addAnimation:animation forKey:@"transition"];
}
- (NSArray *)imgsArr
{
if (!_imgsArr) {
_imgsArr = [NSArray arrayWithObjects:[self returnImage1WithCGSize:self.img.bounds.size andColor:[UIColor redColor]],
[self returnImage1WithCGSize:self.img.bounds.size andColor:[UIColor greenColor]],
[self returnImage1WithCGSize:self.img.bounds.size andColor:[UIColor cyanColor]],
[self returnImage1WithCGSize:self.img.bounds.size andColor:[UIColor magentaColor]], nil];
}
return _imgsArr;
}
//根據size和傳進來的color生成一張顏色圖片
- (UIImage *)returnImage1WithCGSize:(CGSize)size andColor:(UIColor *)color
{
UIGraphicsBeginImageContextWithOptions(size, NO, 0);
UIBezierPath *path = [UIBezierPath bezierPathWithRect:CGRectMake(0, 0, size.width, size.height)];
[color setFill];
[path fill];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
我平時開發(fā)中的圖片輪播一般都是用這個動畫做的,加兩個手勢就行,缺點就是不能翻頁中間不能停留,沒有scroll那么全的功能,但是代碼簡單方便
示例: 我最近做項目遇到這樣一個問題,登錄界面跳到主界面的時候我一般喜歡用根視圖去跳,因為我認為登錄注冊界面可能八百年才用到一次,用導航或者模態(tài)來轉場不是太好,但是用根視圖跳轉顯得太突兀,動畫不太好看,這時轉場動畫便派上用場
//登錄
SideslipViewController *sideslip = [[SideslipViewController alloc] init];
[UIApplication sharedApplication].keyWindow.rootViewController = sideslip;
CATransition *transition = [[CATransition alloc] init];
transition.duration = 0.3;
transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
transition.type = kCATransitionReveal;
transition.subtype = kCATransitionFromTop;
[[UIApplication sharedApplication].keyWindow.layer addAnimation:transition forKey:@"LoginIn"];
//登出
LoginViewController *login = [[LoginViewController alloc] init];
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:login];
nav.navigationBar.hidden = YES;
[UIApplication sharedApplication].keyWindow.rootViewController = nav;
CATransition *transition = [[CATransition alloc] init];
transition.duration = 0.3;
transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
transition.type = kCATransitionPush;
transition.subtype = kCATransitionFromBottom;
[[UIApplication sharedApplication].keyWindow.layer addAnimation:transition forKey:@"LoginOut"];
CASpringAnimation
iOS9之后出來的新的彈簧動畫,繼承CABasicAnimation
/** 一些重要的屬性
* CASpringAnimation的重要屬性:
*
* mass:質量(影響彈簧的慣性,質量越大,彈簧慣性越大,運動的幅度越大)
* stiffness:彈性系數(彈性系數越大,彈簧的運動越快)
* damping:阻尼系數(阻尼系數越大,彈簧的停止越快)
* initialVelocity:初始速率(彈簧動畫的初始速度大小,彈簧運動的初始方向與初始速率的正負一致,若初始速率為0,表示忽略該屬性)
* settlingDuration:結算時間(根據動畫參數估算彈簧開始運動到停止的時間,動畫設置的時間最好根據此時間來設置)
*/
CASpringAnimation *animation = [CASpringAnimation animationWithKeyPath:@"position"];
animation.mass = 10.0;
animation.stiffness = 500;
animation.damping = 10;
animation.initialVelocity = 5.0f;
animation.duration = animation.settlingDuration; //時間需要注意下,用系統(tǒng)計算出來動畫所需時間
animation.toValue = [NSValue valueWithCGPoint:CGPointMake(self.img.center.x, self.img.center.y + 200)];
[self.img.layer addAnimation:animation forKey:@"position"];
以上零散單個Demo地址: https://github.com/SupermanChao/Animation
CAAnimationGroup
使用Group可以將多個動畫合并一起加入到圖層中,Group中所有動畫一起執(zhí)行,可以展示很多動畫種類
一個簡單的動畫組例子,選取顏色
Demo地址:https://github.com/SupermanChao/AnimationGroup
CATransaction
最后講一下事務(CATransaction),在核心動畫里面存在事務(CATransaction)這樣一個概念,它負責協(xié)調多個動畫原子更新顯示操作,簡單來說事務是核心動畫里面的一個基本的單元,動畫的產生必然伴隨著layer的Animatable屬性的變化,而layer屬性的變化必須屬于某一個事務
事務分為隱式和顯式:
- 隱式:沒有明顯調用事務的方法,由系統(tǒng)自動生成事務,比如直接設置一個layer的position屬性,則會在當前線程自動生成一個事務,并在下一個runLoop中自動commit事務
- 顯式:明顯調用事務的方法([CATransaction begin]和[CATransaction commit])
事務的可設置屬性(會覆蓋隱式動畫的設置):
//動畫持續(xù)時間
+ (CFTimeInterval)animationDuration;
//動畫時間曲線
+ (nullable CAMediaTimingFunction *)animationTimingFunction;
//是否關閉動畫
+ (BOOL)disableActions;
//動畫執(zhí)行完畢回調
+ (nullable void (^)(void))completionBlock;
下面來個小例子,圓環(huán)進度動畫,先看效果后上代碼
代碼地址:https://github.com/SupermanChao/CATransaction-Demo
//圓環(huán)貝塞爾曲線
- (UIBezierPath *)path
{
if (!_path) {
CGFloat radius = (MIN(self.frame.size.width, self.frame.size.height) - self.lineWidth) * 0.5;
CGPoint center = CGPointMake(self.frame.size.width * 0.5, self.frame.size.height * 0.5);
_path = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:-M_PI_2 endAngle:M_PI + M_PI_2 clockwise:YES];
}
return _path;
}
//軌道
- (CAShapeLayer *)outLayer
{
if (!_outLayer) {
_outLayer = [CAShapeLayer layer];
_outLayer.lineWidth = self.lineWidth;
_outLayer.fillColor = [UIColor clearColor].CGColor;
_outLayer.strokeColor = self.pathwayColor.CGColor;
_outLayer.path = self.path.CGPath;
}
return _outLayer;
}
//進度
- (CAShapeLayer *)progressLayer
{
if (!_progressLayer) {
_progressLayer = [CAShapeLayer layer];
_progressLayer.lineWidth = self.lineWidth;
_progressLayer.fillColor = [UIColor clearColor].CGColor;
_progressLayer.lineCap = kCALineCapRound;
_progressLayer.strokeColor = self.scheduleColor.CGColor;
_progressLayer.path = self.path.CGPath;
_progressLayer.strokeStart = 0;
_progressLayer.strokeEnd = 0.001;
}
return _progressLayer;
}
//定時器協(xié)調運作
- (void)onTimer
{
switch (self.accuracy) {
case LCAnimationAccuracyLow:
self.progress += 2;
break;
case LCAnimationAccuracyHeight:
self.progress += 0.5;
break;
case LCAnimationAccuracyVeryHeight:
self.progress += 0.1;
break;
default:
self.progress += 1;
break;
}
[CATransaction begin];
[CATransaction setAnimationTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]];
[CATransaction setAnimationDuration:self.totalTime / self.count];
self.progressLayer.strokeEnd = self.progress / 100.0;
[CATransaction commit];
if (self.progress >= 99.99) {
[self lc_stopAnimation];
if ([self.delegate respondsToSelector:@selector(lc_animationFinishAction)]) {
[self.delegate lc_animationFinishAction];
}
};
}