核心動畫——Core Animation

一. CALayer

(一). CALayer簡單介紹

在iOS中,你能看得見摸得着的東西基本上都是UIView,比方一個button、一個文本標籤、一個文本輸入框、一個圖標等等。這些都是UIView,事實上UIView之因此能顯示在屏幕上,全然是因爲它內部的一個圖層。在建立UIView對象時,UIView內部會本身主動建立一個圖層(即CALayer對象),經過UIView的layer屬性可以訪問這個層,要注意的是,這個默認的層不一樣意又一次建立。但可以往層裏面加入子層。UIView可以經過addSubview:方法加入子視圖,當UIView需要顯示到屏幕上時,會調用drawRect:方法進行畫圖,並且會將所有內容繪製在本身的圖層上,畫圖完畢後,系統會將圖層複製到屏幕上,因而就完畢了UIView的顯示,換句話說。UIView自己不具有顯示的功能。是它內部的層纔有顯示功能。數組

(二). 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 CGColorRef borderColor;

// 內容(比方設置爲圖片CGImageRef)
@property(retain) id contents;

X/Y/Z座標軸圖
這裏寫圖片描寫敘述ruby

(三). 關於CALayer的疑惑

  1. 所屬框架
    CALayer是定義在QuartzCore.framework中的;CGImageRef、CGColorRef兩種數據類型是定義在CoreGraphics.framework中的;UIColor、UIImage是定義在UIKit.framework中的markdown

  2. 跨平臺性
    QuartzCore框架和CoreGraphics框架是可以跨平臺使用的。在iOS和Mac OS X上都能使用,但是UIKit僅僅能在iOS中使用,不能在Mac OS X上使用。爲了保證可移植性,QuartzCore不能使用UIImage、UIColor,僅僅能使用CGImageRef、CGColorRef併發

  3. 經過CALayer。就能作出跟UIImageView同樣的界面效果框架

  4. UIView和CALayer的選擇
    UIView與CALayer比較,UIView多了一個事件處理的功能。也就是說。CALayer不能處理用戶的觸摸事件。而UIView可以。因此假設顯示出來的東西需要跟用戶進行交互的話。選擇UIView;假設不需要跟用戶進行交互。選擇UIView或者CALayer都可以。固然。CALayer的性能會高一些,因爲它少了事件處理的功能,更加輕量級。假設兩個UIView是父子關係,那麼它們內部的CALayer也是父子關係。iview

(四). position和anchorPoint

  1. 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

  2. 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所在點。

(五). 隱式動畫

  1. 每一個UIView內部都默認關聯着一個CALayer,咱們可稱這個Layer爲Root Layer(根層或主層)

  2. 所有的非Root Layer,也就是手動建立的CALayer對象,都存在着隱式動畫,根層沒有隱身動畫

  3. 什麼是隱式動畫?當對非Root Layer的部分屬性進行改動時。默認會本身主動產生一些動畫效果,而這些屬性稱爲Animatable Properties(可動畫屬性)

    常見的Animatable Properties:
    bounds:用於設置CALayer的寬度和高度。改動這個屬性會產生縮放動畫
    backgroundColor:用於設置CALayer的背景色。改動這個屬性會產生背景色的漸變更畫
    position:用於設置CALayer的位置。改動這個屬性會產平生移動畫

  4. 要關閉默認的隱式動畫,可以經過動畫事務(CATransaction)關閉默認的隱式動畫效果

    [CATransaction begin];
    [CATransaction setDisableActions:YES];
    self.myview.layer.position = CGPointMake(10, 10);
    [CATransaction commit];

(六). CALayer的基本使用

1. 圖層的基本使用

提示: _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];
}

2. 圖片裁剪成圓形

提示: _imageView是本身定義的UIImageView

- (void)imageLayer {
    // 設置主圖層圓角半徑
    _imageView.layer.cornerRadius = 50;

    // 超出圖層邊框的所有裁剪掉
    // 注意:因爲圖片不是畫在主圖層上的,因此設置主圖層的屬性不影響圖片顯示
    _imageView.layer.masksToBounds = YES;

    // 設置邊框
    _imageView.layer.borderWidth = 0.6;
    _imageView.layer.borderColor = [[UIColor redColor] CGColor];

    // 怎樣推斷之後是否需要裁剪圖片。就推斷下需要顯示圖層的控件是不是正方形。
}

3. 圖層的縮放

提示: _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"];

    }];
}

4. Layer的其它屬性

提示: _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];
}

5. 本身定義圖層的隱式動畫

#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

(一). Core Animation簡單介紹

  1. Core Animation,中文翻譯爲核心動畫,是一個Objective-C語言的框架。它是一組很強大的動畫處理API。使用它能作出很炫麗的動畫效果,並且每每是事半功倍。也就是說,使用少許的代碼就可以實現很強大的功能。Core Animation可以用在Mac OS X和iOS平臺。Core Animation的動畫運行過程都是在後臺操做的,不會堵塞主線程。要注意的是。Core Animation是直接做用在CALayer上的,並非UIView。

    運行動畫的本質是改變圖層的屬性。

  2. 核心動畫開發步驟
    (1). 首先得有CALayer
    (2). 初始化一個CAAnimation對象。並設置一些動畫相關屬性
    (3). 經過調用CALayer的addAnimation:forKey:方法。添加CAAnimation對象到CALayer中。這樣就能開始運行動畫了
    (4). 經過調用CALayer的removeAnimationForKey:方法可以中止CALayer中的動畫

(二). 核心動畫繼承結構

這裏寫圖片描寫敘述

(三). CAAnimation簡單介紹

  1. CAAnimation是所有動畫對象的父類,負責控制動畫的持續時間和速度。是個抽象類,不能直接使用。應該使用它詳細的子類

    屬性說明:(前六個屬性來自CAMediaTiming協議的屬性)
    duration:動畫的持續時間
    repeatCount:反覆次數,無限循環可以設置HUGE_VALF或者MAXFLOAT
    repeatDuration:反覆時間
    fillMode:決定當前對象在非active時間段的行爲,比方動畫開始以前或者動畫結束以後
    beginTime:可以用來設置動畫延遲運行時間。若想延遲2s,就設置爲CACurrentMediaTime()+2,CACurrentMediaTime()爲圖層的當前時間

    removedOnCompletion:默以爲YES,表明動畫運行完畢後就從圖層上移除,圖形會恢復到動畫運行前的狀態。假設想讓圖層保持顯示動畫運行後的狀態。那就設置爲NO,只是還要設置fillMode爲kCAFillModeForwards
    timingFunction:速度控制函數,控制動畫運行的節奏
    delegate:動畫代理,用來監聽動畫的運行過程

  2. fillMode屬性值(要想fillMode有效。最好設置removedOnCompletion = NO)

    kCAFillModeRemoved:這個是默認值。也就是說當動畫開始前和動畫結束後。動畫對layer都沒有影響,動畫結束後,layer會恢復到以前的狀態
    kCAFillModeForwards:當動畫結束後,layer會一直保持着動畫最後的狀態
    kCAFillModeBackwards:在動畫開始前,僅僅需要將動畫加入了一個layer。layer便立刻進入動畫的初始狀態並等待動畫開始。
    kCAFillModeBoth:這個事實上就是上面兩個的合成。

    動畫加入後開始以前,layer便處於動畫初始狀態,動畫結束後layer保持動畫最後的狀態

  3. 速度控制函數(CAMediaTimingFunction)

    kCAMediaTimingFunctionLinear(線性):勻速,給你一個相對靜態的感受
    kCAMediaTimingFunctionEaseIn(漸進):動畫緩慢進入,而後加速離開
    kCAMediaTimingFunctionEaseOut(漸出):動畫全速進入。而後減速的到達目的地
    kCAMediaTimingFunctionEaseInEaseOut(漸進漸出):動畫緩慢的進入,中間加速,而後減速的到達目的地。這個是默認的動畫行爲。

  4. 動畫的代理方法

// 動畫開始前調用
- (void)animationDidStart:(CAAnimation *)anim; // 動畫結束後調用 - (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag;

(四). CAPropertyAnimation簡單介紹

CAPropertyAnimation是CAAnimation的子類,也是個抽象類,要想建立動畫對象,應該使用它的兩個子類:CABasicAnimation。CAKeyframeAnimation
屬性說明:

keyPath:經過指定CALayer的一個屬性名稱爲keyPath(NSString類型)。並且對CALayer的這個屬性的值進行改動,達到對應的動畫效果。

比方,指定@「position」爲keyPath,就改動CALayer的position屬性的值,以達到平移的動畫效果

(五). CABasicAnimation簡單介紹

CABasicAnimation(基本動畫),是CAPropertyAnimation的子類

  1. 屬性說明:

    fromValue:keyPath對應屬性的初始值
    toValue:keyPath對應屬性的結束值

  2. 動畫過程說明:

    隨着動畫的進行,在長度爲duration的持續時間內。keyPath對應屬性的值從fromValue漸漸地變爲toValue,keyPath的內容是CALayer的可動畫Animatable屬性。假設fillMode=kCAFillModeForwards同一時候removedOnComletion=NO。那麼在動畫運行完畢後,圖層會保持顯示動畫運行後的狀態。

    但在實質上,圖層的屬性值仍是動畫運行前的初始值,並無真正被改變。

  3. 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簡單介紹

CAKeyframeAnimation關鍵幀動畫,也是CAPropertyAnimation的子類,與CABasicAnimation的差異是:
CABasicAnimation僅僅能從一個數值(fromValue)變到還有一個數值(toValue),而CAKeyframeAnimation會使用一個NSArray保存這些數值

  1. 屬性說明:

    values:上述的NSArray對象。裏面的元素稱爲「關鍵幀」(keyframe)。

    動畫對象會在指定的時間(duration)內。依次顯示values數組中的每一個關鍵幀
    path:可以設置一個CGPathRef、CGMutablePathRef。讓圖層依照路徑軌跡移動。path僅僅對CALayer的anchorPoint和position起做用。假設設置了path,那麼values將被忽略
    keyTimes:可以爲對應的關鍵幀指定對應的時間點。其取值範圍爲0到1.0,keyTimes中的每一個時間值都對應values中的每一幀。

    假設沒有設置keyTimes,各個關鍵幀的時間是平分的。


    CABasicAnimation可看作是僅僅有2個關鍵幀的CAKeyframeAnimation。

  2. 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簡單介紹

CAAnimationGroup動畫組,是CAAnimation的子類。可以保存一組動畫對象,將CAAnimationGroup對象加入層後。組中所有動畫對象可以同一時候併發運行。

  1. 屬性說明:

    animations:用來保存一組動畫對象的NSArray
    默認狀況下,一組動畫對象是同一時候運行的。也可以經過設置動畫對象的beginTime屬性來更改動畫的開始時間

  2. 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簡單介紹

CATransition過分動畫又叫轉場動畫。是CAAnimation的子類,用於作轉場動畫。可以爲層提供移出屏幕和移入屏幕的動畫效果。iOS比Mac OS X的轉場動畫效果少一點。
UINavigationController就是經過CATransition實現了將控制器的視圖推入屏幕的動畫效果

  1. 動畫屬性:

    type:動畫過渡類型
    subtype:動畫過渡方向
    startProgress:動畫起點(在整體動畫的百分比)
    endProgress:動畫終點(在整體動畫的百分比)

  2. 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

(九). CALayer上動畫的暫停與恢復

#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動畫與核心動畫的差異

注意: 核心動畫一切都是假象。並不會真正改變圖層的屬性值,假設之後作動畫的時候不需要與用戶交互。可以用核心動畫。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

(十一). CALayer上動畫的暫停和恢復

#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計時器

CADisplayLink是一種以屏幕刷新頻率觸發的時鐘機制。每秒鐘運行大約60次左右
CADisplayLink是一個計時器,可以使畫圖代碼與視圖的刷新頻率保持同步,而NSTimer沒法確保計時器實際被觸發的準確時間

  1. 用法:
    定義CADisplayLink並制定觸發調用方法
    將顯示連接加入到主運行循環隊列

  2. 使用實例(代碼片)

#pragma mark - 定時器懶載入
- (CADisplayLink *)link {
    if (_link == nil) {
        // 初始化定時器
        _link = [CADisplayLink displayLinkWithTarget:self selector:@selector(angleChange)];
        // 定時器加入主運行循環
        [_link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
    }
    return _link;
}

三. CALayer補充

(一). CAGradientLayer(漸變圖層)

  1. 屬性

    colors: 設置漸變顏色
    locations: 設置漸變的定位點
    startPoint: 設置漸變的開始點,取值0~1
    endPoint: 設置漸變的結束點,取值0~1
    type: 類型

  2. 使用實例
    樣例:圖片摺疊

#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

(二). CAReplicatorLayer(複製圖層)

  1. 屬性

    instanceCount: 子層總數(包含原生子層)
    instanceDelay: 複製子層動畫延遲時長
    instanceTransform: 複製子層形變(不包含原生子層),每一個複製子層都是相對上一個。


    instanceColor: 子層顏色,會和原生子層背景色衝突,所以兩者選其一設置。
    instanceRedOffset、instanceGreenOffset、instanceBlueOffset、instanceAlphaOffset`: 顏色通道偏移量,每一個複製子層都是相對上一個的偏移量。

  2. 使用步驟

    (1). 建立複製層
    (2). 建立一個子圖層
    (3). 複製子圖層

  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(形狀圖層)

  1. CAShapeLayer繼承於CALayer,可以使用CALayer的所有屬性值;

  2. CAShapeLayer需要貝塞爾曲線配合使用纔有意義(也就是說纔有效果)

  3. 使用CAShapeLayer(屬於CoreAnimation)與貝塞爾曲線可以實現不在view的drawRect(繼承於CoreGraphics走的是CPU,消耗的性能較大)方法中畫出一些想要的圖形

  4. CAShapeLayer動畫渲染直接提交到手機的GPU其中,相較於view的drawRect方法使用CPU渲染而言。其效率極高,能大大優化內存使用狀況

  5. 詳細實例
    樣例: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

提示:粘性計算圖
這裏寫圖片描寫敘述

相關文章
相關標籤/搜索