在iOS中,你能看得見摸得着的東西基本上都是UIView,比方一個button、一個文本標籤、一個文本輸入框、一個圖標等等。這些都是UIView,事實上UIView之因此能顯示在屏幕上,全然是因爲它內部的一個圖層。在建立UIView對象時,UIView內部會本身主動建立一個圖層(即CALayer對象),經過UIView的layer屬性可以訪問這個層,要注意的是,這個默認的層不一樣意又一次建立。但可以往層裏面加入子層。UIView可以經過addSubview:方法加入子視圖,當UIView需要顯示到屏幕上時,會調用drawRect:方法進行畫圖,並且會將所有內容繪製在本身的圖層上,畫圖完畢後,系統會將圖層複製到屏幕上,因而就完畢了UIView的顯示,換句話說。UIView自己不具有顯示的功能。是它內部的層纔有顯示功能。數組
// 寬度和高度
@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 CGColorRef borderColor;
// 內容(比方設置爲圖片CGImageRef)
@property(retain) id contents;
X/Y/Z座標軸圖
ruby
所屬框架
CALayer是定義在QuartzCore.framework中的;CGImageRef、CGColorRef兩種數據類型是定義在CoreGraphics.framework中的;UIColor、UIImage是定義在UIKit.framework中的markdown
跨平臺性
QuartzCore框架和CoreGraphics框架是可以跨平臺使用的。在iOS和Mac OS X上都能使用,但是UIKit僅僅能在iOS中使用,不能在Mac OS X上使用。爲了保證可移植性,QuartzCore不能使用UIImage、UIColor,僅僅能使用CGImageRef、CGColorRef併發
經過CALayer。就能作出跟UIImageView同樣的界面效果框架
UIView和CALayer的選擇
UIView與CALayer比較,UIView多了一個事件處理的功能。也就是說。CALayer不能處理用戶的觸摸事件。而UIView可以。因此假設顯示出來的東西需要跟用戶進行交互的話。選擇UIView;假設不需要跟用戶進行交互。選擇UIView或者CALayer都可以。固然。CALayer的性能會高一些,因爲它少了事件處理的功能,更加輕量級。假設兩個UIView是父子關係,那麼它們內部的CALayer也是父子關係。iview
position和anchorPoint簡單介紹dom
@property CGPoint position;
用來設置CALayer在父層中的位置
以父層的左上角爲原點(0, 0)函數@property CGPoint anchorPoint;
稱爲「定位點」、「錨點」
決定着CALayer身上的哪一個點會在position屬性所指的位置
以本身的左上角爲原點(0, 0)
它的x、y取值範圍都是0~1,默認值爲(0.5, 0.5)oop
position和anchorPoint的聯繫
提示: CALayer的錨點anchorPoint決定了位置點position是CALayer身上的哪一個點,在開發中通常先肯定錨點再肯定位置post
好比: 假定CALayer的尺寸爲50*50,把CALayer的position設置爲(100,100);
當CALayer的anchorPoint設置爲(0,0),CALayer身上的(0,0)點就是position所在點。
當CALayer的anchorPoint設置爲(0.5,0.5),CALayer身上的(25,25)點就是position所在點;
當CALayer的anchorPoint設置爲(1,1),CALayer身上的(50,50)點就是position所在點;
當CALayer的anchorPoint設置爲(0.5,0)。CALayer身上的(25,0)點就是position所在點;
當CALayer的anchorPoint設置爲(1,0.5),CALayer身上的(50,25)點就是position所在點。
每一個UIView內部都默認關聯着一個CALayer,咱們可稱這個Layer爲Root Layer(根層或主層)
所有的非Root Layer,也就是手動建立的CALayer對象,都存在着隱式動畫,根層沒有隱身動畫
什麼是隱式動畫?當對非Root Layer的部分屬性進行改動時。默認會本身主動產生一些動畫效果,而這些屬性稱爲Animatable Properties(可動畫屬性)
常見的Animatable Properties:
bounds:用於設置CALayer的寬度和高度。改動這個屬性會產生縮放動畫
backgroundColor:用於設置CALayer的背景色。改動這個屬性會產生背景色的漸變更畫
position:用於設置CALayer的位置。改動這個屬性會產平生移動畫
要關閉默認的隱式動畫,可以經過動畫事務(CATransaction)關閉默認的隱式動畫效果
[CATransaction begin];
[CATransaction setDisableActions:YES];
self.myview.layer.position = CGPointMake(10, 10);
[CATransaction commit];
提示: _myView是本身定義的UIView
- (void)myViewLayer {
// 圓角半徑
_myView.layer.cornerRadius = 50;
// 陰影,陰影尾隨圓角半徑的改變而改變
// Opacity:設置不透明度
_myView.layer.shadowOpacity = 1;
// 設置陰影偏移量
_myView.layer.shadowOffset = CGSizeMake(10, 10);
// 設置陰影顏色,注意:圖層的顏色都是核心畫圖框架,CGColor
_myView.layer.shadowColor = [[UIColor redColor] CGColor];
// 設置陰影的半徑
_myView.layer.shadowRadius = 10;
// 邊框
// 邊框寬度
_myView.layer.borderWidth = 1;
// 邊框顏色
_myView.layer.borderColor = [[UIColor blueColor] CGColor];
}
提示: _imageView是本身定義的UIImageView
- (void)imageLayer {
// 設置主圖層圓角半徑
_imageView.layer.cornerRadius = 50;
// 超出圖層邊框的所有裁剪掉
// 注意:因爲圖片不是畫在主圖層上的,因此設置主圖層的屬性不影響圖片顯示
_imageView.layer.masksToBounds = YES;
// 設置邊框
_imageView.layer.borderWidth = 0.6;
_imageView.layer.borderColor = [[UIColor redColor] CGColor];
// 怎樣推斷之後是否需要裁剪圖片。就推斷下需要顯示圖層的控件是不是正方形。
}
提示: _myView是本身定義的UIView
- (void)transLayer {
// 圖層的縮放
[UIView animateWithDuration:2 animations:^{
// // 旋轉
// _myView.layer.transform = CATransform3DMakeRotation(M_PI, 1, 1, 0);
// // 縮放
// _myView.layer.transform = CATransform3DMakeScale(0.5, 0.5, 0.5);
//
// 使用KVO高速進行圖層縮放與旋轉
// 不要使用setValue:forKey:設置。因爲有可能不能實現
[_myView.layer setValue:@(M_PI) forKeyPath:@"transform.rotation"];
[_myView.layer setValue:@0.5 forKeyPath:@"transform.scale"];
}];
}
提示: _myView是本身定義的UIView
- (void)transLayer {
// 設置圖層背景顏色
// _myView.layer.backgroundColor = [[UIColor blueColor] CGColor];
// 設置尺寸
_myView.layer.bounds = CGRectMake(0, 0, 150, 150);
// 設置位置,默認指圖層中點。詳細由anchorPoint決定
// _myView.layer.position = CGPointMake(0, 0);
// 錨點(x,y的範圍都是0-1),決定了position的含義
// _myView.layer.anchorPoint = CGPointMake(0.3, 0);
// 設置圖層內容(比方設置爲圖片CGImageRef)
UIImage *image = [UIImage imageNamed:@"阿狸頭像"];
_myView.layer.contents = (id)image.CGImage;
// 可以加入子圖層
CALayer *myLayer = [CALayer layer];
myLayer.backgroundColor = [[UIColor yellowColor] CGColor];
myLayer.bounds = CGRectMake(0, 0, 50, 50);
[_myView.layer addSublayer:myLayer];
}
#import "ViewController.h"
// 計算弧度
#define angle2radion(angle) ((angle) / 180.0 * M_PI)
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIView *myView;
@property (strong, nonatomic) CALayer *myLayer;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
// 建立圖層
_myLayer = [CALayer layer];
_myLayer.bounds = CGRectMake(0, 0, 100, 100);
_myLayer.position = CGPointMake(75, 173);
_myLayer.backgroundColor = [[UIColor redColor] CGColor];
[self.view.layer addSublayer:_myLayer];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
_myLayer.transform = CATransform3DMakeRotation(angle2radion(arc4random_uniform(360) + 1), 1, 1, 1);
_myLayer.position = CGPointMake(arc4random_uniform(200) + 50, arc4random_uniform(400) + 50);
_myLayer.cornerRadius = arc4random_uniform(50);
_myLayer.backgroundColor = [[self randomColor] CGColor];
_myLayer.borderWidth = arc4random_uniform(10);
_myLayer.borderColor = [[self randomColor] CGColor];
// _myView.layer.transform = CATransform3DMakeRotation(angle2radion(arc4random_uniform(360) + 1), 1, 1, 1);
// _myView.layer.position = CGPointMake(arc4random_uniform(200) + 50, arc4random_uniform(400) + 50);
// _myView.layer.cornerRadius = arc4random_uniform(50);
// _myView.layer.backgroundColor = [[self randomColor] CGColor];
// _myView.layer.borderWidth = arc4random_uniform(10);
// _myView.layer.borderColor = [[self randomColor] CGColor];
}
- (UIColor *)randomColor
{
CGFloat r = arc4random_uniform(256) / 255.0;
CGFloat b = arc4random_uniform(256) / 255.0;
CGFloat g = arc4random_uniform(256) / 255.0;
return [UIColor colorWithRed:r green:g blue:b alpha:1];
}
@end
Core Animation,中文翻譯爲核心動畫,是一個Objective-C語言的框架。它是一組很強大的動畫處理API。使用它能作出很炫麗的動畫效果,並且每每是事半功倍。也就是說,使用少許的代碼就可以實現很強大的功能。Core Animation可以用在Mac OS X和iOS平臺。Core Animation的動畫運行過程都是在後臺操做的,不會堵塞主線程。要注意的是。Core Animation是直接做用在CALayer上的,並非UIView。
運行動畫的本質是改變圖層的屬性。
核心動畫開發步驟
(1). 首先得有CALayer
(2). 初始化一個CAAnimation對象。並設置一些動畫相關屬性
(3). 經過調用CALayer的addAnimation:forKey:方法。添加CAAnimation對象到CALayer中。這樣就能開始運行動畫了
(4). 經過調用CALayer的removeAnimationForKey:方法可以中止CALayer中的動畫
CAAnimation是所有動畫對象的父類,負責控制動畫的持續時間和速度。是個抽象類,不能直接使用。應該使用它詳細的子類
屬性說明:(前六個屬性來自CAMediaTiming協議的屬性)
duration:動畫的持續時間
repeatCount:反覆次數,無限循環可以設置HUGE_VALF或者MAXFLOAT
repeatDuration:反覆時間
fillMode:決定當前對象在非active時間段的行爲,比方動畫開始以前或者動畫結束以後
beginTime:可以用來設置動畫延遲運行時間。若想延遲2s,就設置爲CACurrentMediaTime()+2,CACurrentMediaTime()爲圖層的當前時間removedOnCompletion:默以爲YES,表明動畫運行完畢後就從圖層上移除,圖形會恢復到動畫運行前的狀態。假設想讓圖層保持顯示動畫運行後的狀態。那就設置爲NO,只是還要設置fillMode爲kCAFillModeForwards
timingFunction:速度控制函數,控制動畫運行的節奏
delegate:動畫代理,用來監聽動畫的運行過程
fillMode屬性值(要想fillMode有效。最好設置removedOnCompletion = NO)
kCAFillModeRemoved:這個是默認值。也就是說當動畫開始前和動畫結束後。動畫對layer都沒有影響,動畫結束後,layer會恢復到以前的狀態
kCAFillModeForwards:當動畫結束後,layer會一直保持着動畫最後的狀態
kCAFillModeBackwards:在動畫開始前,僅僅需要將動畫加入了一個layer。layer便立刻進入動畫的初始狀態並等待動畫開始。
kCAFillModeBoth:這個事實上就是上面兩個的合成。動畫加入後開始以前,layer便處於動畫初始狀態,動畫結束後layer保持動畫最後的狀態
速度控制函數(CAMediaTimingFunction)
kCAMediaTimingFunctionLinear(線性):勻速,給你一個相對靜態的感受
kCAMediaTimingFunctionEaseIn(漸進):動畫緩慢進入,而後加速離開
kCAMediaTimingFunctionEaseOut(漸出):動畫全速進入。而後減速的到達目的地
kCAMediaTimingFunctionEaseInEaseOut(漸進漸出):動畫緩慢的進入,中間加速,而後減速的到達目的地。這個是默認的動畫行爲。
動畫的代理方法
// 動畫開始前調用
- (void)animationDidStart:(CAAnimation *)anim; // 動畫結束後調用 - (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag;
CAPropertyAnimation是CAAnimation的子類,也是個抽象類,要想建立動畫對象,應該使用它的兩個子類:CABasicAnimation。CAKeyframeAnimation
屬性說明:
keyPath:經過指定CALayer的一個屬性名稱爲keyPath(NSString類型)。並且對CALayer的這個屬性的值進行改動,達到對應的動畫效果。
比方,指定@「position」爲keyPath,就改動CALayer的position屬性的值,以達到平移的動畫效果
CABasicAnimation(基本動畫),是CAPropertyAnimation的子類
屬性說明:
fromValue:keyPath對應屬性的初始值
toValue:keyPath對應屬性的結束值
動畫過程說明:
隨着動畫的進行,在長度爲duration的持續時間內。keyPath對應屬性的值從fromValue漸漸地變爲toValue,keyPath的內容是CALayer的可動畫Animatable屬性。假設fillMode=kCAFillModeForwards同一時候removedOnComletion=NO。那麼在動畫運行完畢後,圖層會保持顯示動畫運行後的狀態。
但在實質上,圖層的屬性值仍是動畫運行前的初始值,並無真正被改變。
CABasicAnimation基本使用
動畫效果:觸碰屏幕,開始心跳
#import "ViewController.h"
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIImageView *imageView;
@end
@implementation ViewController
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
// 建立動畫
CABasicAnimation *anim = [CABasicAnimation animation];
// 設置哪一個屬性要產生動畫
anim.keyPath = @"transform.scale";
// 設置動畫變化的值
anim.toValue = @0.5;
// 設置動畫完畢的時候不要移除動畫,默認YES
anim.removedOnCompletion = NO;
// 設置動畫運行完畢要保持最新的效果
// 注意:要和removedOnCompletion配合使用才幹夠
anim.fillMode = kCAFillModeForwards;
// 設置動畫次數
anim.repeatCount = 5;
// 給圖片加入動畫
// 注意:假設不單獨設置anim.keyPath,就必須在方法裏設置forKey
[_imageView.layer addAnimation:anim forKey:nil];
}
@end
CAKeyframeAnimation關鍵幀動畫,也是CAPropertyAnimation的子類,與CABasicAnimation的差異是:
CABasicAnimation僅僅能從一個數值(fromValue)變到還有一個數值(toValue),而CAKeyframeAnimation會使用一個NSArray保存這些數值
屬性說明:
values:上述的NSArray對象。裏面的元素稱爲「關鍵幀」(keyframe)。
動畫對象會在指定的時間(duration)內。依次顯示values數組中的每一個關鍵幀
path:可以設置一個CGPathRef、CGMutablePathRef。讓圖層依照路徑軌跡移動。path僅僅對CALayer的anchorPoint和position起做用。假設設置了path,那麼values將被忽略
keyTimes:可以爲對應的關鍵幀指定對應的時間點。其取值範圍爲0到1.0,keyTimes中的每一個時間值都對應values中的每一幀。假設沒有設置keyTimes,各個關鍵幀的時間是平分的。
CABasicAnimation可看作是僅僅有2個關鍵幀的CAKeyframeAnimation。
CAKeyframeAnimation的基本使用
動畫效果:在屏幕上畫路徑。圖片跟着路徑不停的跑
#import "DrawView.h"
#define angle2Radion(angle) (angle / 180.0 * M_PI)
@interface DrawView ()
@property (nonatomic, strong) UIBezierPath *path;
@end
@implementation DrawView
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
// 獲取觸控點
CGPoint curP = [touch locationInView:self];
// 建立路徑
_path = [UIBezierPath bezierPath];
// 設置路徑起點
[_path moveToPoint:curP];
}
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
// 獲取觸控點
CGPoint moveP = [touch locationInView:self];
// 設置路徑下一個點
[_path addLineToPoint:moveP];
// 重繪
[self setNeedsDisplay];
}
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
CAKeyframeAnimation *anim = [CAKeyframeAnimation animation];
// anim.values = @[@(angle2Radion(-10)),@(angle2Radion(10)),@(angle2Radion(-10))];
// 設置需要動畫屬性
anim.keyPath = @"position";
// 設置動畫路徑
anim.path = _path.CGPath;
// 動畫持續時間
anim.duration = 0.5;
// 設置動畫次數
anim.repeatCount = MAXFLOAT;
[[[self.subviews firstObject] layer] addAnimation:anim forKey:nil];
}
- (void)drawRect:(CGRect)rect {
// Drawing code
// 描邊
[_path stroke];
}
@end
CAAnimationGroup動畫組,是CAAnimation的子類。可以保存一組動畫對象,將CAAnimationGroup對象加入層後。組中所有動畫對象可以同一時候併發運行。
屬性說明:
animations:用來保存一組動畫對象的NSArray
默認狀況下,一組動畫對象是同一時候運行的。也可以經過設置動畫對象的beginTime屬性來更改動畫的開始時間
CAAnimationGroup的基本使用
動畫效果:觸碰屏幕時,view同一時候作反轉縮放移動的動畫
#import "ViewController.h"
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIView *myView;
@end
@implementation ViewController
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
CAAnimationGroup *animGroup = [CAAnimationGroup animation];
CABasicAnimation *scaleAnim = [CABasicAnimation animation];
scaleAnim.keyPath = @"transform.scale";
scaleAnim.toValue = @0.5;
CABasicAnimation *rotationAnim = [CABasicAnimation animation];
rotationAnim.keyPath = @"transform.rotation";
rotationAnim.toValue = @(arc4random_uniform(M_PI));
CABasicAnimation *positionAnim = [CABasicAnimation animation];
positionAnim.keyPath = @"position";
positionAnim.toValue = [NSValue valueWithCGPoint:CGPointMake(arc4random_uniform(200), arc4random_uniform(200))];
animGroup.animations = @[scaleAnim, rotationAnim, positionAnim];
[_myView.layer addAnimation:animGroup forKey:nil];
}
@end
CATransition過分動畫又叫轉場動畫。是CAAnimation的子類,用於作轉場動畫。可以爲層提供移出屏幕和移入屏幕的動畫效果。iOS比Mac OS X的轉場動畫效果少一點。
UINavigationController就是經過CATransition實現了將控制器的視圖推入屏幕的動畫效果
動畫屬性:
type:動畫過渡類型
subtype:動畫過渡方向
startProgress:動畫起點(在整體動畫的百分比)
endProgress:動畫終點(在整體動畫的百分比)
CATransition的基本使用
動畫效果:觸碰屏幕時。圖片動畫切換
#import "ViewController.h"
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIImageView *imageView;
@end
@implementation ViewController
static int name = 1;
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
if (name == 4) {
name = 1;
}
// 圖片名稱
NSString *imageName = [NSString stringWithFormat:@"%d",name++];
// 建立圖片
_imageView.image = [UIImage imageNamed:imageName];
// 轉場動畫
CATransition *anim = [CATransition animation];
// 轉場類型有:`fade', `moveIn', `push' and `reveal'
anim.type = @"pageCurl";
anim.duration = 2;
[_imageView.layer addAnimation:anim forKey:nil];
}
@end
#pragma mark - 暫停CALayer的動畫
-(void)pauseLayer:(CALayer*)layer
{
CFTimeInterval pausedTime = [layer convertTime:CACurrentMediaTime() fromLayer:nil];
// 讓CALayer的時間中止走動
layer.speed = 0.0;
// 讓CALayer的時間停留在pausedTime這個時刻
layer.timeOffset = pausedTime;
}
#pragma mark - 恢復CALayer的動畫
-(void)resumeLayer:(CALayer*)layer
{
CFTimeInterval pausedTime = layer.timeOffset;
// 1. 讓CALayer的時間繼續行走
layer.speed = 1.0;
// 2. 取消上次記錄的停留時刻
layer.timeOffset = 0.0;
// 3. 取消上次設置的時間
layer.beginTime = 0.0;
// 4. 計算暫停的時間(這裏也可以用CACurrentMediaTime()-pausedTime)
CFTimeInterval timeSincePause = [layer convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime;
// 5. 設置相對於父座標系的開始時間(日後退timeSincePause)
layer.beginTime = timeSincePause;
}
注意: 核心動畫一切都是假象。並不會真正改變圖層的屬性值,假設之後作動畫的時候不需要與用戶交互。可以用核心動畫。UIView必須需改屬性值才幹有動畫效果。
#import "ViewController.h"
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIView *myView;
@end
@implementation ViewController
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
CABasicAnimation *anim = [CABasicAnimation animation];
anim.keyPath = @"position";
anim.toValue = [NSValue valueWithCGPoint:CGPointMake(300, 300)];
// 取消反彈必須放在加入動畫以前
anim.removedOnCompletion = NO;
anim.fillMode = kCAFillModeForwards;
// 設置代理
anim.delegate = self;
[_myView.layer addAnimation:anim forKey:nil];
// [UIView animateWithDuration:0.25 animations:^{
//
// _myView.layer.position = CGPointMake(300, 300);
//
// } completion:^(BOOL finished) {
//
// NSLog(@"%@",[NSValue valueWithCGPoint:_myView.layer.position]);
//
// }];
}
// 動畫結束後調用
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {
NSLog(@"%@",[NSValue valueWithCGPoint:_myView.layer.position]);
}
- (void)viewDidLoad {
NSLog(@"%@",[NSValue valueWithCGPoint:_myView.layer.position]);
}
@end
#import "ViewController.h"
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIView *myView;
@end
@implementation ViewController
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
CABasicAnimation *anim = [CABasicAnimation animation];
anim.keyPath = @"transform.scale";
anim.toValue = @0.2;
anim.duration = 5;
anim.removedOnCompletion = NO;
anim.fillMode = kCAFillModeForwards;
[_myView.layer addAnimation:anim forKey:nil];
}
// 恢復動畫
- (IBAction)pauser:(id)sender {
[self resumeLayer:_myView.layer];
}
// 暫停動畫
- (IBAction)reusme:(id)sender {
[self pauseLayer:_myView.layer];
}
#pragma mark - 暫停CALayer的動畫
-(void)pauseLayer:(CALayer*)layer
{
CFTimeInterval pausedTime = [layer convertTime:CACurrentMediaTime() fromLayer:nil];
// 讓CALayer的時間中止走動
layer.speed = 0.0;
// 讓CALayer的時間停留在pausedTime這個時刻
layer.timeOffset = pausedTime;
}
#pragma mark - 恢復CALayer的動畫
-(void)resumeLayer:(CALayer*)layer
{
CFTimeInterval pausedTime = layer.timeOffset;
// 1. 讓CALayer的時間繼續行走
layer.speed = 1.0;
// 2. 取消上次記錄的停留時刻
layer.timeOffset = 0.0;
// 3. 取消上次設置的時間
layer.beginTime = 0.0;
// 4. 計算暫停的時間(這裏也可以用CACurrentMediaTime()-pausedTime)
CFTimeInterval timeSincePause = [layer convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime;
// 5. 設置相對於父座標系的開始時間(日後退timeSincePause)
layer.beginTime = timeSincePause;
}
@end
CADisplayLink是一種以屏幕刷新頻率觸發的時鐘機制。每秒鐘運行大約60次左右
CADisplayLink是一個計時器,可以使畫圖代碼與視圖的刷新頻率保持同步,而NSTimer沒法確保計時器實際被觸發的準確時間
用法:
定義CADisplayLink並制定觸發調用方法
將顯示連接加入到主運行循環隊列
使用實例(代碼片)
#pragma mark - 定時器懶載入
- (CADisplayLink *)link {
if (_link == nil) {
// 初始化定時器
_link = [CADisplayLink displayLinkWithTarget:self selector:@selector(angleChange)];
// 定時器加入主運行循環
[_link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
}
return _link;
}
屬性
colors: 設置漸變顏色
locations: 設置漸變的定位點
startPoint: 設置漸變的開始點,取值0~1
endPoint: 設置漸變的結束點,取值0~1
type: 類型
使用實例
樣例:圖片摺疊
#import "ViewController.h"
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIImageView *topView;
@property (weak, nonatomic) IBOutlet UIImageView *downView;
@property (weak, nonatomic) IBOutlet UIView *dragView;
@property (strong, nonatomic) CAGradientLayer *gradLayer;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 1. 設置contentsRect屬性,控制顯示內容,取值0~1,0.5表示顯示一半,1表示顯示所有
_topView.layer.contentsRect = CGRectMake(0, 0, 1, 0.5);
_topView.layer.anchorPoint = CGPointMake(0.5, 1);
_downView.layer.contentsRect = CGRectMake(0, 0.5, 1, 0.5);
_downView.layer.anchorPoint = CGPointMake(0.5, 0);
// 2. 給最上面的view加入手勢
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(pan:)];
[_dragView addGestureRecognizer:pan];
// 3. 建立一個漸變圖層
CAGradientLayer *gradLayer = [CAGradientLayer layer];
// 設置圖層的尺寸和downView同樣
gradLayer.frame = _downView.bounds;
// 設置圖層透明度
gradLayer.opacity = 0;
// 設置漸變顏色
gradLayer.colors = @[(id)[[UIColor blackColor] CGColor], (id)[[UIColor clearColor] CGColor]];
// // 設置漸變的定位點
// gradLayer.locations = @[@0.5, @0.3, @0.1];
// // 設置漸變的開始點,取值0~1
// gradLayer.startPoint = CGPointMake(0.5, 0);
// 把漸變圖層加入到最如下的downView
[_downView.layer addSublayer:gradLayer];
_gradLayer = gradLayer;
}
#pragma mark - 手勢觸發事件
- (void)pan:(UIPanGestureRecognizer *)pan {
// 獲取偏移量
CGPoint transP = [pan locationInView:_dragView];
// 計算旋轉角度。向下逆時針旋轉
CGFloat angle = -transP.y / 200.0 * M_PI;
// 設置一個旋轉對象
CATransform3D transform = CATransform3DIdentity;
// 添加旋轉的立體感。遠小近大。d表明距離
transform.m34 = -1 / 450.0;
// 給topView設置旋轉
transform = CATransform3DRotate(transform, angle, 1, 0, 0);
_topView.layer.transform = transform;
// 4. 設置陰影漸變
_gradLayer.opacity = transP.y * 1 / 150.0;
// 5. 設置彈性效果
if (pan.state == UIGestureRecognizerStateEnded) {
// 彈簧效果的動畫
// SpringWithDamping:彈性係數,越小。彈簧效果越明顯
[UIView animateWithDuration:0.6 delay:0 usingSpringWithDamping:0.2 initialSpringVelocity:10 options:UIViewAnimationOptionCurveEaseInOut animations:^{
_topView.layer.transform = CATransform3DIdentity;
_gradLayer.opacity = 0;
} completion:^(BOOL finished) {
}];
}
}
@end
屬性
instanceCount: 子層總數(包含原生子層)
instanceDelay: 複製子層動畫延遲時長
instanceTransform: 複製子層形變(不包含原生子層),每一個複製子層都是相對上一個。
instanceColor: 子層顏色,會和原生子層背景色衝突,所以兩者選其一設置。
instanceRedOffset、instanceGreenOffset、instanceBlueOffset、instanceAlphaOffset`: 顏色通道偏移量,每一個複製子層都是相對上一個的偏移量。
使用步驟
(1). 建立複製層
(2). 建立一個子圖層
(3). 複製子圖層
使用實例
樣例:音量震動條
// 1. 建立複製圖層
CAReplicatorLayer *repLayer = [CAReplicatorLayer layer];
repLayer.frame = _myView.bounds;
[_myView.layer addSublayer:repLayer];
// 2. 建立一個普通圖層
CALayer *layer = [CALayer layer];
// 尺寸
layer.bounds = CGRectMake(0, 0, 30, 150);
// 錨點
layer.anchorPoint = CGPointMake(0.5, 1);
// 位置
layer.position = CGPointMake(25, _myView.bounds.size.height);
// 顏色
layer.backgroundColor = [[UIColor whiteColor] CGColor];
[repLayer addSublayer:layer];
// 3. 建立一個核心動畫
CABasicAnimation *anim = [CABasicAnimation animation];
anim.keyPath = @"transform.scale.y";
anim.toValue = @0.1;
anim.repeatCount = MAXFLOAT;
anim.duration = 0.5;
// 設置反轉動畫,動畫結束時運行逆動畫
anim.autoreverses = YES;
[layer addAnimation:anim forKey:nil];
// 4. 設置複製圖層
// instanceCount表示複製層裏面子層總數,包含原始層
repLayer.instanceCount = 5;
// 設置複製子層偏移量,不包含原始層,相對於原始層x偏移
repLayer.instanceTransform = CATransform3DMakeTranslation(45, 0, 0);
// 設置複製層動畫的延遲時間
repLayer.instanceDelay = 0.1;
// 設置圖層的顏色,假設原始層設置不是白色。不要設置這個屬性
repLayer.instanceColor = [[UIColor yellowColor] CGColor];
// 設置顏色偏移量
repLayer.instanceGreenOffset = -0.45;
CAShapeLayer繼承於CALayer,可以使用CALayer的所有屬性值;
CAShapeLayer需要貝塞爾曲線配合使用纔有意義(也就是說纔有效果)
使用CAShapeLayer(屬於CoreAnimation)與貝塞爾曲線可以實現不在view的drawRect(繼承於CoreGraphics走的是CPU,消耗的性能較大)方法中畫出一些想要的圖形
CAShapeLayer動畫渲染直接提交到手機的GPU其中,相較於view的drawRect方法使用CPU渲染而言。其效率極高,能大大優化內存使用狀況
詳細實例
樣例:QQ粘性效果
注意:要正常運行QQ粘性效果,必須關閉系統默認的約束
// 不使用系統默認的約束
self.view.translatesAutoresizingMaskIntoConstraints = NO;
#import "SJMButton.h"
// 最大圓心距離
#define kMaxDistance 80
@interface SJMButton ()
@property (nonatomic,strong) UIView *smallView;
@property (nonatomic, assign) CGFloat oldSmallViewRadius;
@property (nonatomic, weak) CAShapeLayer *shapeLayer;
@end
@implementation SJMButton
#pragma mark - 懶載入形狀圖層
- (CAShapeLayer *)shapeLayer {
if (_shapeLayer == nil) {
CAShapeLayer *layer = [CAShapeLayer layer];
_shapeLayer = layer;
// 注意:必須後設置顏色和加入
layer.fillColor = self.backgroundColor.CGColor;
[self.superview.layer insertSublayer:layer below:self.layer];
}
return _shapeLayer;
}
#pragma mark - 懶載入小圓
- (UIView *)smallView {
if (_smallView == nil) {
UIView *view = [[UIView alloc] init];
view.backgroundColor = self.backgroundColor;
_smallView = view;
// 小圓加入button的父控件上
[self.superview insertSubview:_smallView belowSubview:self];
}
return _smallView;
}
#pragma mark - 初始化
-(instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
[self setUp];
}
return self;
}
- (void)awakeFromNib {
[self setUp];
}
- (void)setUp {
CGFloat w = self.bounds.size.width;
_oldSmallViewRadius = w / 2;
// 設置button的圓角
self.layer.cornerRadius = w / 2;
// 加入Pan手勢
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(pan:)];
[self addGestureRecognizer:pan];
self.smallView.center = self.center;
self.smallView.bounds = self.bounds;
self.smallView.layer.cornerRadius = w / 2;
}
#pragma mark - PAN手勢的觸發事件
- (void)pan:(UIPanGestureRecognizer *)pan {
// 獲取偏移量
CGPoint transP = [pan translationInView:self];
// 注意:改動形變並不會改動中心點
//self.transform = CGAffineTransformTranslate(self.transform, transP.x, transP.y);
// 改動button的center
CGPoint center = self.center;
center.x += transP.x;
center.y += transP.y;
self.center = center;
// 復位
[pan setTranslation:CGPointZero inView:self];
// 顯示後面圓。後面圓的半徑,隨着兩個圓心的距離不斷添加而減少
//計算圓心距離
CGFloat d = [self distanceWithCenter:self.center otherCenter:self.smallView.center];
// 計算小圓的半徑
CGFloat smallViewRadius = _oldSmallViewRadius - d / 10;
// 設置小圓的尺寸
self.smallView.bounds = CGRectMake(0, 0, smallViewRadius * 2, smallViewRadius * 2);
self.smallView.layer.cornerRadius = smallViewRadius;
/*-------------------------------描寫敘述不規則矩形----------------------------------*/
// 繪製不規則矩形。不能經過畫圖,因爲畫圖僅僅能在當前控件上畫,超出部分不會顯示
// 這裏使用形狀圖層CAShapeLayer繪製
// 當圓心有距離但距離不大的時候調用
if (d > 0 && self.smallView.hidden == NO)
{
self.shapeLayer.path = [[self pathWithBigCirCleView:self smallCirCleView:self.smallView] CGPath];
}
// 當圓心距離大於最大距離的時候調用
// 效果是可以拖出來
if (d > kMaxDistance) {
// 隱藏小圓
self.smallView.hidden = YES;
// 移除不規則矩形
[self.shapeLayer removeFromSuperlayer];
self.shapeLayer = nil;
}
/*-------------------------------手指擡起的時候還原----------------------------------*/
if (pan.state == UIGestureRecognizerStateEnded)
{
if (d > kMaxDistance) {
// 展現gif動畫
UIImageView *imageView = [[UIImageView alloc] initWithFrame:self.bounds];
NSMutableArray *arrM = [NSMutableArray array];
for (int i = 1; i < 9; i++) {
UIImage *image = [UIImage imageNamed:[NSString stringWithFormat:@"%d",i]];
[arrM addObject:image];
}
// 動畫組
imageView.animationImages = arrM;
// 動畫時間
imageView.animationDuration = 0.5;
// 動畫次數
imageView.animationRepeatCount = 1;
// 開始動畫
[imageView startAnimating];
[self addSubview:imageView];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.4 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// 把本身移除出父控件
[self removeFromSuperview];
});
} else {
// 移除不規則矩形
[self.shapeLayer removeFromSuperlayer];
self.shapeLayer = nil;
// 還原位置。有彈性效果
[UIView animateWithDuration:0.5 delay:0 usingSpringWithDamping:0.2 initialSpringVelocity:0 options:UIViewAnimationOptionCurveLinear animations:^{
self.center = self.smallView.center;
} completion:^(BOOL finished) {
self.smallView.hidden = NO;
}];
}
}
}
#pragma mark - 計算兩點距離
- (CGFloat)distanceWithCenter:(CGPoint)center otherCenter:(CGPoint)otherCenter {
CGFloat offsetX = center.x - otherCenter.x;
CGFloat offsetY = center.y - otherCenter.y;
return sqrt(offsetX * offsetX + offsetY *offsetY);
}
#pragma mark - 描寫敘述兩圓之間的矩形路線
- (UIBezierPath *)pathWithBigCirCleView:(UIView *)bigCirCleView smallCirCleView:(UIView *)smallCirCleView
{
CGPoint bigCenter = bigCirCleView.center;
CGFloat x2 = bigCenter.x;
CGFloat y2 = bigCenter.y;
CGFloat r2 = bigCirCleView.bounds.size.width / 2;
CGPoint smallCenter = smallCirCleView.center;
CGFloat x1 = smallCenter.x;
CGFloat y1 = smallCenter.y;
CGFloat r1 = smallCirCleView.bounds.size.width / 2;
// 獲取圓心距離
CGFloat d = [self distanceWithCenter:bigCenter otherCenter:smallCenter];
CGFloat sinθ = (x2 - x1) / d;
CGFloat cosθ = (y2 - y1) / d;
// 座標系基於父控件
CGPoint pointA = CGPointMake(x1 - r1 * cosθ , y1 + r1 * sinθ);
CGPoint pointB = CGPointMake(x1 + r1 * cosθ , y1 - r1 * sinθ);
CGPoint pointC = CGPointMake(x2 + r2 * cosθ , y2 - r2 * sinθ);
CGPoint pointD = CGPointMake(x2 - r2 * cosθ , y2 + r2 * sinθ);
CGPoint pointO = CGPointMake(pointA.x + d / 2 * sinθ , pointA.y + d / 2 * cosθ);
CGPoint pointP = CGPointMake(pointB.x + d / 2 * sinθ , pointB.y + d / 2 * cosθ);
UIBezierPath *path = [UIBezierPath bezierPath];
// A
[path moveToPoint:pointA];
// AB
[path addLineToPoint:pointB];
// 繪製BC曲線
[path addQuadCurveToPoint:pointC controlPoint:pointP];
// CD
[path addLineToPoint:pointD];
// 繪製DA曲線
[path addQuadCurveToPoint:pointA controlPoint:pointO];
return path;
}
@end
提示:粘性計算圖