iOS動畫知識梳理

學習及實踐筆記

記錄iOS動畫的學習及實踐html

目錄git

•    顯示層(UIView)動畫
◦    初級動畫
◦    關鍵幀動畫
◦    逐幀動畫
◦    Gif動畫的處理
•    內容層(CALayer)動畫
◦    CoreAnimation結構
◦    CABasicAnimation
◦    CAKeyFrameAnimation
◦    CAGroupAnimation
◦    CATransition
◦    CAEmitterLayer 粒子動畫
◦    CAGradientLayer 梯度動畫
◦    CAShapeLayer 繪製動畫
◦    CAReplicatorLayer 複製動畫

動畫的分類

根據動畫的實現「位置」,能夠分爲 顯示層(UIView)動畫 內容層(CALayer)動畫 github

顯示層(UIView)動畫

初級動畫

初級動畫,是最爲常見的一種動畫。經過UIView屬性的修改,配合UIView的兩種動畫apiblock方法形式實現web

api使用示範spring

// 動畫方法形式
    [UIView beginAnimations:nil context:nil];
    [UIView setAnimationDuration:0.35f];
    [UIView setAnimationRepeatAutoreverses:NO];// 自動翻轉
    [UIView setAnimationDelegate:self];// 需設置代理
    [UIView setAnimationDidStopSelector:@selector(testForAutoreverses)];
    self.testButton.backgroundColor = [UIColor lightGrayColor];
    [UIView commitAnimations];

// 動畫block形式
    [UIView animateWithDuration:2.0f animations:^{
        self.testButton.backgroundColor = [UIColor whiteColor];
    }];

根據簡單動畫的效果,最多見的顯示層(UIView)屬性修改分爲如下幾類api

  • frame屬性相關的位置形狀變化
  • alpha屬性相關
  • transform屬性相關

若是但願使用彈跳效果,系統也一樣提供了api供開發者使用數組

+ (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

參數介紹:
dampingRatio:速度衰減比例。取值範圍0 ~ 1,值越低震動越強
velocity:初始速度,越大速度越快xcode

關鍵幀動畫

使咱們具有控制一段連貫時間內,精確到某個時間點(關鍵幀)上的連續變化的能力緩存

// 系統api
+ (void)animateKeyframesWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay options:(UIViewKeyframeAnimationOptions)options animations:(void (^)(void))animations completion:(void (^ __nullable)(BOOL finished))completion NS_AVAILABLE_IOS(7_0);
+ (void)addKeyframeWithRelativeStartTime:(double)frameStartTime relativeDuration:(double)frameDuration animations:(void (^)(void))animations NS_AVAILABLE_IOS(7_0); // start time and duration are values between 0.0 and 1.0 specifying time and duration relative to the overall time of the keyframe animation

api中的options參數解釋 官方文檔app

UIViewKeyframeAnimationOptionCalculationModeLinear     = 0 << 10, // 線性運算模式.The option to use a simple linear calculation when interpolating between keyframe values.
    UIViewKeyframeAnimationOptionCalculationModeDiscrete   = 1 << 10, // 離散運算模式,展現關鍵幀.The option to not interpolate between keyframe values, but rather to jump directly to each new keyframe value.
    UIViewKeyframeAnimationOptionCalculationModePaced      = 2 << 10, // 均勻運算模式.The option to compute intermediate keyframe values using a simple pacing algorithm. This option results in an evenly paced animation.
    UIViewKeyframeAnimationOptionCalculationModeCubic      = 3 << 10, // The option to compute intermediate frames using a default Catmull-Rom spline that passes through the keyframe values. You cannot adjust the parameters of this algorithm.
    UIViewKeyframeAnimationOptionCalculationModeCubicPaced = 4 << 10 // The option to compute intermediate frames using the cubic scheme while ignoring the timing properties of the animation. Instead, timing parameters are calculated implicitly to give the animation a constant velocity.

最後兩種Cubic運算模式,附上實驗代碼及效果圖:(可達鴨眉頭一皺,發現這兩個參數並不簡單)

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.redCircle = [[UIView alloc] init];
    _redCircle.backgroundColor = [UIColor redColor];
    _redCircle.frame = CGRectMake(0, 0, 50, 50);
    _redCircle.layer.cornerRadius = 5;
    [_redCircle.layer masksToBounds];
    
    [self.view addSubview:_redCircle];
    
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{

    [self keyframeAnimation];
    
}

- (void)keyframeAnimation{

    [UIView animateKeyframesWithDuration:10 delay:0 options:UIViewKeyframeAnimationOptionCalculationModeCubic animations:^{
        
        [UIView addKeyframeWithRelativeStartTime:0.0 relativeDuration:1/5.0 animations:^{
            self.redCircle.frame = CGRectMake(50, 200, 50, 50);
        }];
        [UIView addKeyframeWithRelativeStartTime:1/5.0 relativeDuration:1/5.0 animations:^{
            self.redCircle.frame = CGRectMake(100, 250, 50, 50);
        }];
        [UIView addKeyframeWithRelativeStartTime:2/5.0 relativeDuration:1/5.0 animations:^{
            self.redCircle.frame = CGRectMake(150, 275, 50, 50);
        }];
        [UIView addKeyframeWithRelativeStartTime:3/5.0 relativeDuration:1/5.0 animations:^{
            self.redCircle.frame = CGRectMake(200, 350, 50, 50);
        }];
        [UIView addKeyframeWithRelativeStartTime:4/5.0 relativeDuration:1/5.0 animations:^{
            self.redCircle.frame = CGRectMake(250, 500, 50, 50);
        }];
        
    } completion:^(BOOL finished) {
        
    }];
    
    
}
  • CalculationModeCubic是一種特殊的貝塞爾曲線(關於貝塞爾曲線原理,文下文中有詳細解釋),就像官方文檔中指出的,經過default Catmull-Rom spline在關鍵幀值中插入兩個運算點,讓貝塞爾曲線可以穿過全部的點而實現運算得出的動畫速度更加平滑,防止出現因動畫時間太短形成的動畫突變的狀況

cubic-normal.gif

cubic-reverse.gif

參考資料

參考連接1
參考連接2

  • CalculationModeCubicPaced:暫且解釋爲均勻平滑模式,多是由於忽略了時間屬性?特殊狀況下仍會出現突轉的狀況

cubicPaced-normal
cubicPaced-reverse.gif

經過實例對比,不論是下落仍是突轉,cubic的效果都更加平滑

逐幀動畫

  • 若是是圖片序列的展現,系統提供了序列幀的接口
    示例代碼:
self.view.backgroundColor = [UIColor lightGrayColor];  
    self.animationView = [[UIImageView alloc] init];
    self.animationView.image = [UIImage imageNamed:@"m2_10001"];
    [self.animationView sizeToFit];
    self.animationView.center = self.view.center;
    [self.view addSubview:self.animationView];
    
    
    NSMutableArray *imgArr = [NSMutableArray new];
    for (int i = 1; i < 67; i ++) {
        UIImage *img = [UIImage imageNamed:[NSString stringWithFormat:@"m2_1%04d",i]];
        [imgArr addObject:img];
    }
    
    self.animationView.animationImages = [imgArr copy];
    self.animationView.animationDuration = 67/30.0;
    [self.animationView startAnimating];

序列幀動畫

  • 若是用到了圖形上下文的繪製,就須要NSTimer或者CADisplayLink實現幀速率的控制,設置好相應的timeinterval或者frameinterval便可。( CADisplayLink固定刷新率60Hz,frameInterval屬性的賦值n,實際間隔時間爲n x 1/60`,頻率固定,推薦使用 )
    drawrect.gif

Gif動畫

Gif相關

image i/o

Image I/O框架提供了不透明數據類型來讀取圖像數據和寫圖像數據到一個目的地(CGImageSourceRef和CGImageDestinationRef)。它支持不少圖像格式,包括標準web格式、搞動態範圍圖像,和原始相機數據。Image I/O還有許多其餘特性:

1.mac平臺上的最快的圖像解碼和編碼。
2.逐步加載圖片的能力。
3.支持圖像元數據。
4.緩存效果。

你能夠經過下面的對象來建立image source和image destination:

1.URL:即CFURLRef對象。
2.Core Foundation對象:CFDataRef和CFmutableDataRef。
3.Quartz data consumer(CGDataConsumerRef)和data provider(CGDataProviderRef)對象。

1.使用Image I/O框架: #import <ImageIO/ImageIO.h>
2.使用UTType:#import <MobileCoreServices/MobileCoreServices.h>
二、支持的圖像格式:例如JPEG、JPEG2000、RAW、TIFF、BMP、PNG。在不一樣的平臺不是全部的格式都支持。你能夠調用下列函數來得到支持的格式:
1)CGImageSourceCopyTypeIdentifiers:返回同一類型修飾符的數組,表示支持的圖像源
2)CGImageDestinationCopyTypeIdentifiers:返回支持的目的地的Uniform Type Identifiers(UTIs)

相關知識連接
image i/o
UTType官方文檔
UTIs

Gif播放

iOS原生是不支持gif格式圖片的,因此當咱們想要使用一張gif圖片時,須要將gif圖片經過image i/o轉換成相應的序列幀圖片後,生成UIImage使用
具體步驟以下圖

Gif使用流程

咱們要使用的GIF圖片

gakki2.gif

下面請看咱們轉換的具體代碼:

NSString *path = [[NSBundle mainBundle] pathForResource:@"gakki2" ofType:@"gif"];
    UIImage *animateImg = [UIImage imageWithContentsOfFile:path];
    UIImageView *gifImageView = [[UIImageView alloc] initWithImage:animateImg];
    [gifImageView sizeToFit];
    gifImageView.center = self.view.center;
    
    [self.view addSubview:gifImageView];

看一下效果
gakkiUnmove.gif
不出所料,果真直接使用是行不通的對嗎?可是Gakki仍是很好看有木有~

咱們按照正確的方式打開

/**
 *  GIF -> NSData -> ImageIO -> UIImage
 */
- (void)testOfGif{

    NSString *path = [[NSBundle mainBundle] pathForResource:@"gakki2" ofType:@"gif"];
//    UIImage *animateImg = [UIImage imageWithContentsOfFile:path];
    
    NSData *gifData = [NSData dataWithContentsOfFile:path];
    
    CGImageSourceRef gifdataSource = CGImageSourceCreateWithData((__bridge CFDataRef)gifData, NULL);
    size_t count = CGImageSourceGetCount(gifdataSource);
    
    UIImage *animateImg = nil;
    if (count <= 1)
    {
        animateImg = [UIImage imageWithData:gifData];
    }
    else
    {
        NSMutableArray *imgArr = [[NSMutableArray alloc] init];
        CGFloat timeinterval = 0.0f;
        
        for (size_t i = 0; i < count; i ++) {
            CGImageRef imgRf = CGImageSourceCreateImageAtIndex(gifdataSource, i, NULL);
            
            if (!imgRf) {
                continue;
            }
            
            timeinterval += [self getKeyFrameDurationWithIndex:i andSoureceRef:gifdataSource];
            
            UIImage *img = [UIImage imageWithCGImage:imgRf scale:[UIScreen mainScreen].scale orientation:UIImageOrientationUp];
            [imgArr addObject:img];
            CGImageRelease(imgRf);

        }
        
        CFRelease(gifdataSource);
        
        animateImg = [UIImage animatedImageWithImages:imgArr duration:timeinterval];
        
    }
    
    
    UIImageView *gifImageView = [[UIImageView alloc] initWithImage:animateImg];
    [gifImageView sizeToFit];
    gifImageView.center = self.view.center;
    
    [self.view addSubview:gifImageView];
    
}

- (CGFloat)getKeyFrameDurationWithIndex:(size_t )index andSoureceRef:(CGImageSourceRef )soureRef{

    CGFloat frameDuration = 0.1f;
    
    CFDictionaryRef cfFrameProperties = CGImageSourceCopyPropertiesAtIndex(soureRef, index, NULL);
    NSDictionary *frameProperties = (__bridge NSDictionary *)cfFrameProperties;
    
    NSDictionary *gifProperties = frameProperties[(NSString *)kCGImagePropertyGIFDictionary];
    
    NSNumber *duration = gifProperties[(NSString *)kCGImagePropertyGIFUnclampedDelayTime];
    
    if (duration)
    {
        frameDuration = duration.floatValue;
    }
    else
    {
        NSNumber *duration = gifProperties[(NSString *)kCGImagePropertyGIFDelayTime];
        if (duration) frameDuration = duration.floatValue;
    }
    
    if (frameDuration < 0.011f) {
        frameDuration = 0.1f;
    }
    
    CFRelease(cfFrameProperties);
    
    return frameDuration;
    
}

注意點:

  • 由於image i/o屬於Core Foundation,因此出現了大量的__bridge處理。書寫的時候有點不習慣,不過看一下xcode的錯誤信息便能知道緣由
  • 咱們的第二步處理,是經過CGImageSourceRef 讀取單幀圖片的時間信息。這裏須要注意一下,由於咱們在用序列幀圖片數組合成gif文件時,須要對每一張圖片的該信息賦值
  • 操做CF信息的時,最後都要進行CFRelease操做
  • 仔細看代碼的同窗也許會注意到下面對於當前幀時間的處理,咱們按照SDWebImage中相關處理的備註作出解釋:
if (frameDuration < 0.011f) {
        frameDuration = 0.1f;
    }

SDWebImage: UIImage + GIF.m
Many annoying ads specify a 0 duration to make an image flash as quickly as possible.

We follow Firefox's behavior and use a duration of 100 ms for any frames that specify a duration of <= 10 ms. See <rdar://problem/7689300> and http://webkit.org/b/36082 for more information.

看一下咱們運行的效果:幀速率的效果跟原圖保持一致
gakkiAwake.gif

序列幀生成Gif

它能夠實際應用於視頻轉Gif的操做,由於視頻咱們能夠按照時間去進行相應的截圖,若是控制好時間間隔,循環截圖,經過如下方法生成Gif應該能夠實現。這裏就暫時不實驗了,感興趣的同窗能夠試一下。
附視頻截圖方法

// 經過視頻url截取封面
+ (UIImage*) thumbnailImageForVideo:(NSURL *)videoURL {
    
    AVURLAsset *asset = [[AVURLAsset alloc] initWithURL:videoURL options:nil];
    
    AVAssetImageGenerator *gen = [[AVAssetImageGenerator alloc] initWithAsset:asset];
    
    gen.appliesPreferredTrackTransform = YES;
    
    CMTime time = CMTimeMakeWithSeconds(1.0, 600);// 設置時間
    
    NSError *error = nil;
    
    CMTime actualTime;
    
    CGImageRef image = [gen copyCGImageAtTime:time actualTime:&actualTime error:&error];
    UIImage *thumbImg = [[UIImage alloc] initWithCGImage:image];
    if (image) {
        CFRelease(image);
    }
    return thumbImg;
    
}

言歸正傳,還記得咱們前面講逐幀動畫時,用到的圖片數組嗎?咱們利用它們,來合成一張gif被進行沙盒存儲

Gif合成流程

代碼實現,此處再次提醒你們須要導入的頭文件
1.使用Image I/O框架: #import <ImageIO/ImageIO.h>
2.使用UTType:#import <MobileCoreServices/MobileCoreServices.h>

/**
 *  序列幀圖片的讀取 -> 建立Gif文件,獲取CGImageDestinationRef -> 爲單幀圖片CGImageRef屬性賦值 -> 爲Gif設置相關屬性 -> 經過CGImageDestinationRef合成gif
 */
- (void)createGifFile{

    NSMutableArray *imgArr = [NSMutableArray new];
    for (int i = 1; i < 67; i ++) {
        UIImage *img = [UIImage imageNamed:[NSString stringWithFormat:@"m2_1%04d",i]];
        [imgArr addObject:img];
    }
    
    NSString *path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
    NSString *magicPath = [path stringByAppendingString:@"/magic.gif"];
    
    NSLog(@"magicPath : %@",magicPath);
    
    CFURLRef URLRef = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, (__bridge CFStringRef)magicPath, kCFURLPOSIXPathStyle, NO);
    CGImageDestinationRef gifRef = CGImageDestinationCreateWithURL(URLRef, kUTTypeGIF,imgArr.count, NULL);
    
    NSDictionary *keyFrameDict = @{(NSString *)kCGImagePropertyGIFDelayTime:@(1/30.0)};// 30fps
    NSDictionary *gifKeyFrameDict = @{(NSString *)kCGImagePropertyGIFDictionary:keyFrameDict};
    
    for (UIImage *image in imgArr) {// 每幀圖片進行設置
        
        CGImageRef imageRef = [image CGImage];
        CGImageDestinationAddImage(gifRef, imageRef, (__bridge CFDictionaryRef) gifKeyFrameDict);
        
    }
    
    NSDictionary *gifSettingDict = @{
                                     
                                     // 色彩空間格式
                                     (NSString *)kCGImagePropertyColorModel:(NSString *)kCGImagePropertyColorModelGray,
                                     // 色彩深度
                                     (NSString *)kCGImagePropertyDepth:@(8),
                                     // gif執行次數
                                     (NSString *)kCGImagePropertyGIFLoopCount:@(1)
                                     
                                     };
    
    NSDictionary *gifDict = @{
                              
                              (NSString *)kCGImagePropertyGIFDictionary : gifSettingDict
                              
                              };
    
    CGImageDestinationSetProperties(gifRef, (__bridge CFDictionaryRef) gifDict);
    
    CGImageDestinationFinalize(gifRef);
    
}

在設置gif圖片的屬性時,咱們用到了 色彩空間格式 色彩深度 兩個參數,色彩格式咱們採用最爲常見的RGB格式,色彩深度咱們設置爲@(8)。須要你們根據本身的實際須要去設置。

圖像類型 色階位數
黑白圖像 1
灰度圖像 8
彩色圖像 16 或 32

咱們看一下沙盒中咱們生成的Gif :finder shift + command + g

magicgif.gif


內容層(CALayer)動畫

內容層動畫,是指在必定時間內,對layerAnimatable Property進行修改所產生的動畫。因此,熟悉CALayer的常見屬性,會對咱們後邊用CoreAnimation實現 內容層動畫 大有幫助。

  • 下邊咱們列舉一些常見的屬性並作了必要備註

    • positon
    • cornerRadius
    • translation.x/y/z
    • transform.scale/rotation
    • borderWidth/borderColor
    • opacity ( like UIView's alpha )
    • shadowOpacity
    • shadowRadius ( default is 3.0f )
    • shadowOffset ( default is (.0f,-3.0f) )
    • backgroundColor
      ( when we want to change color,we need to use UIColor like this :(__bridge id _Nullable)([UIColor greenColor].CGColor) )

這裏須要注意的是,若是一個layer對象存在對應着的View,則稱這個layer是一個Root Layer,Root Layer 通常都是經過CALayer或其子類直接建立的。
全部的非Root Layer在設置Animatable Properties的時候都存在着隱式動畫,默認的duration是0.25秒.
咱們看一個簡單CAShapeLayer隱式動畫的例子

- (void)testForCALayer{

    self.testLayer = [[CAShapeLayer alloc] init];
    _testLayer.strokeEnd = 0;
    _testLayer.strokeColor = [UIColor redColor].CGColor;
    _testLayer.fillColor = [UIColor clearColor].CGColor;
    _testLayer.lineWidth = 3;
    _testLayer.path = [UIBezierPath bezierPathWithArcCenter:self.view.center radius:100 startAngle:-M_PI_2 endAngle:3/2.0*M_PI clockwise:YES].CGPath;
    [self.view.layer addSublayer:_testLayer];
    
}

- (void)animationForLayer{

    self.testLayer.strokeEnd = 1;
    
}

touchesBegan方法中調用animationForLayer的方法,能夠發現,咱們並無設置任何動畫相關的語句 效果以下

Layer的隱式動畫.gif

CoreAnimation結構

CA structure

CABasicAnimation

CABasicAnimation

CABasicAnimation必要屬性的設置:

  • keyPath傳入layer的屬性字符串
  • duration設置基本動畫時長
  • - (void)setRomovedOnCompletion 默認爲YES,會在動畫結束時,將動畫從render tree中一次性移除,恢復初始狀態。一般咱們設置爲NO,保留動畫結束時的效果
  • fillModel屬性決定當前對象過了非active時間段的行爲,一般咱們選擇kCAFillModeForwards

    • kCAFillModeRemoved 這個是默認值,也就是說當動畫開始前和動畫結束後,動畫對layer都沒有影響,動畫結束後,layer會恢復到以前的狀態
    • kCAFillModeForwards 當動畫結束後,layer會一直保持着動畫最後的狀態
    • kCAFillModeBackwardskCAFillModeForwards是相對的,就是在動畫開始前,你只要將動畫加入了一個layer,layer便當即進入動畫的初始狀態並等待動畫開始.你能夠這樣設定測試代碼,將一個動畫加入一個layer的時候延遲5秒執行.而後就會發如今動畫沒有開始的時候,只要動畫被加入了layer,layer便處於動畫初始狀態
    • kCAFillModeBoth 理解了上面兩個,這個就很好理解了,這個其實就是上面兩個的合成.動畫加入後開始以前,layer便處於動畫初始狀態,動畫結束後layer保持動畫最後的狀態.

如下三個屬性,參照官方描述,很容易理解

  • fromValue
  • toValue
  • byValue

    /* Father's Love
    * The objects defining the property values being interpolated between.
    * All are optional, and no more than two should be non-nil. The object
    * type should match the type of the property being animated (using the
    * standard rules described in CALayer.h). The supported modes of
    * animation are:
    *
    * - both `fromValue' and `toValue' non-nil. Interpolates between
    * `fromValue' and `toValue'.
    *
    * - `fromValue' and `byValue' non-nil. Interpolates between
    * `fromValue' and `fromValue' plus `byValue'.
    *
    * - `byValue' and `toValue' non-nil. Interpolates between `toValue'
    * minus `byValue' and `toValue'.
    *
    * - `fromValue' non-nil. Interpolates between `fromValue' and the
    * current presentation value of the property.
    *
    * - `toValue' non-nil. Interpolates between the layer's current value
    * of the property in the render tree and `toValue'.
    *
    * - `byValue' non-nil. Interpolates between the layer's current value
    * of the property in the render tree and that plus `byValue'. */

若是但願加入顯示層動畫中的spring效果,Father Apple貼心的爲咱們準備了CABasicAnimation的子類CASpringAnimation,推薦使用。控制彈性動畫的屬性

  • damping
  • initialVelocity
  • mass
  • stiffness

簡單demo演示

BasicAnimation.gif

- (void)testForCABasicAnimation{

    UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
    [button setTitle:@"Animation" forState:UIControlStateNormal];
    [button setBackgroundColor:[UIColor lightGrayColor]];
    [button setTitleColor:[UIColor purpleColor] forState:UIControlStateNormal];
    [button sizeToFit];
    button.center = self.view.center;
    button.layer.borderColor = [UIColor redColor].CGColor;
    [self.view addSubview:button];
    self.testButton = button;
    
    self.testButton.layer.shadowColor = [UIColor blackColor].CGColor;
    self.testButton.layer.shadowOpacity = 0.5;
    
    NSLog(@"\nshadowOffset:%f,%f",self.testButton.layer.shadowOffset.width,self.testButton.layer.shadowOffset.height);
    NSLog(@"\nshadowRadius:%f",self.testButton.layer.shadowRadius);

}

- (void)callBasicAnimation{

    CASpringAnimation *basic = [[CASpringAnimation alloc] init];
    basic.keyPath = @"cornerRadius";
    basic.toValue =  @15;
    basic.duration = 2.0f;
    basic.fillMode = kCAFillModeForwards;
    [basic setRemovedOnCompletion:NO];
    basic.damping = 6;
    basic.initialVelocity = 5;
    basic.mass = 0.5;
    
    [self.testButton.layer addAnimation:basic forKey:nil];
    
}

CAKeyFrameAnimation

顯示層動畫中的關鍵幀動畫相似,提供更加精確的動畫控制能力

經常使用屬性

  • values 數組類型,描述每一個關鍵幀的相關屬性
  • keyTimes NSNumber泛型數組類型,描述關鍵幀時間信息,範圍
    [0,1],時間點:keytime * durationaddKeyframeWithRelativeStartTime 中的時間描述相似,再也不贅述
  • path 屬性,更加精確的控制動畫的路徑

簡單demo演示

CAKeyframeAnimation.gif

- (void)callKeyframeAnimation{// CAKeyframeAnimation
    
    CAKeyframeAnimation *keyframe = [[CAKeyframeAnimation alloc] init];
    keyframe.keyPath = @"position";
    keyframe.duration = 5.0f;
//    keyframe.repeatCount = MAXFLOAT;
    keyframe.fillMode = kCAFillModeForwards;
    keyframe.calculationMode = kCAAnimationCubic;
    [keyframe setRemovedOnCompletion:NO];
//    keyframe.values = @[
//                        (__bridge id _Nullable)[UIColor redColor].CGColor,
//                        (__bridge id _Nullable)[UIColor yellowColor].CGColor,
//                        (__bridge id _Nullable)[UIColor greenColor].CGColor,
//                        (__bridge id _Nullable)[UIColor blueColor].CGColor,
//                        (__bridge id _Nullable)[UIColor purpleColor].CGColor
//                        ];
    
    keyframe.path = [self keyframePath];
    
    [self.testButton.layer addAnimation:keyframe forKey:nil];

    
}

- (CGMutablePathRef )keyframePath{

    CGMutablePathRef path = CGPathCreateMutable();
    CGPathMoveToPoint(path, NULL, 100, 100);
    CGPathAddLineToPoint(path, NULL, 100, 200);
    CGPathAddLineToPoint(path, NULL, 200, 350);

    CGPathAddArc(path, NULL, 200, 350, 100, 0, M_PI, NO);
    
    return path;
    
}

CAGroupAnimation

提供了animations數組接口,提供多個動畫進行組合的能力

簡單demo演示

CAAnimationGroup.gif

- (void)callAnimationGroup{

    CAKeyframeAnimation *keyframe = [[CAKeyframeAnimation alloc] init];
    keyframe.keyPath = @"position";
    keyframe.path = [self keyframePath];
    
    CAKeyframeAnimation *colors = [[CAKeyframeAnimation alloc] init];
    colors.keyPath = @"backgroundColor";
    colors.values = @[
                        (__bridge id _Nullable)[UIColor redColor].CGColor,
                        (__bridge id _Nullable)[UIColor yellowColor].CGColor,
                        (__bridge id _Nullable)[UIColor greenColor].CGColor,
                        (__bridge id _Nullable)[UIColor blueColor].CGColor,
                        (__bridge id _Nullable)[UIColor purpleColor].CGColor
                        ];
    
    CAAnimationGroup *group = [[CAAnimationGroup alloc] init];
    group.animations = @[keyframe,colors];
    group.duration = 5.0f;
    group.fillMode = kCAFillModeForwards;
    [group setRemovedOnCompletion:NO];
    
    [self.testButton.layer addAnimation:group forKey:nil];
    
}

CATransition

用於不一樣視圖場景之間的切換
使用過程相對簡單

  1. 實例化CATransition,設置相應的轉場動畫key
  2. 設置合適的轉場動畫屬性,好比duration,type,subtype
  3. 將動畫效果添加到相應視圖的layer

系統提供的相關Api(已作必要註釋)

  • 公有type Api

    // 淡入淡出效果
    CA_EXTERN NSString * const kCATransitionFade
      CA_AVAILABLE_STARTING (10.5, 2.0, 9.0, 2.0);
    // 移動效果
    CA_EXTERN NSString * const kCATransitionMoveIn
      CA_AVAILABLE_STARTING (10.5, 2.0, 9.0, 2.0);
    // 推送效果
    CA_EXTERN NSString * const kCATransitionPush
      CA_AVAILABLE_STARTING (10.5, 2.0, 9.0, 2.0);
    // 揭開效果
    CA_EXTERN NSString * const kCATransitionReveal
      CA_AVAILABLE_STARTING (10.5, 2.0, 9.0, 2.0);
  • 私有type Api(只能使用字符串表示 使用有風險,上傳需謹慎

    • pageCurl 向上翻頁
    • pageUnCurl 向下翻頁
    • cube 立方體翻轉
    • oglFlip 翻轉效果
    • stuckEffect 收縮效果
    • rippleEffect 水滴波紋效果
    • cameraIrisHollowOpen 相機打開效果
    • cameraIrisHollowClose 相機關閉效果
  • subtypes 轉場方向

    CA_EXTERN NSString * const kCATransitionFromRight
      CA_AVAILABLE_STARTING (10.5, 2.0, 9.0, 2.0);
    CA_EXTERN NSString * const kCATransitionFromLeft
      CA_AVAILABLE_STARTING (10.5, 2.0, 9.0, 2.0);
    CA_EXTERN NSString * const kCATransitionFromTop
      CA_AVAILABLE_STARTING (10.5, 2.0, 9.0, 2.0);
    CA_EXTERN NSString * const kCATransitionFromBottom
      CA_AVAILABLE_STARTING (10.5, 2.0, 9.0, 2.0);

簡單demo演示

CATransition.gif

- (void)setImgViewForCATransiton{

    UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 200, 200)];
    imageView.center = self.view.center;
    imageView.image = [UIImage imageNamed:@"pic_1"];
    imageView.contentMode = UIViewContentModeScaleAspectFill;
    imageView.layer.cornerRadius = 5;
    imageView.layer.masksToBounds = YES;
    
    self.testImgView = imageView;
    [self.view addSubview:imageView];
    
}

// CATransition
- (void)testForCATransition{

    self.testImgView.image = [UIImage imageNamed:@"pic_2"];
    
    CATransition *transition = [[CATransition alloc] init];
    transition.type = kCATransitionReveal;
    transition.subtype = kCATransitionFromTop;
    transition.duration = 2;

    [self.testImgView.layer addAnimation:transition forKey:nil];
    
}

題外話

特殊的CALayer動畫

CAEmitterLayer 粒子動畫

粒子系統,由CAEmitterLayer粒子發射器圖層CAEmitterCell粒子發射單元組成

  • CAEmitterLayer
@interface CAEmitterLayer : CALayer
// 粒子單元數組
@property(nullable, copy) NSArray<CAEmitterCell *> *emitterCells;
// 粒子的建立速率,默認值爲1s Animatable
@property float birthRate;
// 粒子的存活時間係數,默認值爲1s Animatable
@property float lifetime;
// 發射器在xy平面的位置 Animatable
@property CGPoint emitterPosition;
// 發射器在z平面的位置 Animatable
@property CGFloat emitterZPosition;
// 發射器的尺寸大小
@property CGSize emitterSize;
// 發射器的深度
@property CGFloat emitterDepth;
// 發射器形狀 包括'point' (the default)點, 'line'線, 'rectangle'矩形,'circle'圓形, 'cuboid'立方體 and 'sphere'球形
@property(copy) NSString *emitterShape;
// 發射器的發射模式 包括'points'發射器內部發出, 'outline'發射器邊緣發出, 'surface'發射器表面發出 and 'volume' (the default)發射器中點發出
@property(copy) NSString *emitterMode;
// 發射器渲染模式 'unordered' (the default)粒子無序, 'oldestFirst'越早聲明的粒子渲染層級越高,'oldestLast'越晚聲明的粒子渲染層級越高, 'backToFront' (i.e. sorted into Z order) 按照z軸的順序渲染and 'additive'粒子混合. The first four use source-over compositing, the last uses additive compositing.
@property(copy) NSString *renderMode;
// 開啓深度顯示(三維空間效果)
@property BOOL preservesDepth;
// 粒子速度係數 * cell速度 默認1 Animatable
@property float velocity;
// 粒子縮放係數 * cellscale 默認1 Animatable
@property float scale;
// 粒子旋轉位置係數 *cellspin 默認1 Animatable
@property float spin;
// 初始化隨機的粒子種子 默認0
@property unsigned int seed;

@end
  • CAEmitterCell
// 構建方法
+ (instancetype)emitterCell;
// 發射單元名稱,用來構建 keyPath
@property(nullable, copy) NSString *name;
// 是否容許被渲染
@property(getter=isEnabled) BOOL enabled;
// 每秒建立的粒子數,默認值0 Animatable
@property float birthRate;
// 粒子平均存活時間及存活時間容差
@property float lifetime;
@property float lifetimeRange;
// 發射緯度:z軸方向的發射角度 發射經度:x-y平面中,相對x軸的發射角度 Animatable
@property CGFloat emissionLatitude;
@property CGFloat emissionLongitude;
// 發射角度的容差 Animatable
@property CGFloat emissionRange
// 粒子平均速度及容差 默認值0 Animatable
@property CGFloat velocity;
@property CGFloat velocityRange;
//  粒子在各個方向的加速度 Animatable
@property CGFloat xAcceleration;
@property CGFloat yAcceleration;
@property CGFloat zAcceleration;
// 粒子平均縮放相關屬性 Animatable
@property CGFloat scale;
@property CGFloat scaleRange;
@property CGFloat scaleSpeed;
// 粒子平均旋轉相關屬性 Animatable
@property CGFloat spin;
@property CGFloat spinRange;
// 粒子的主顏色 及 R/G/B/Alpha 的容差 Animatable
@property(nullable) CGColorRef color;
@property float redRange;// 默認 0
@property float greenRange;// 默認 0
@property float blueRange;// 默認 0
@property float alphaRange;// 默認 0
// 粒子的主顏色基礎上 R/G/B/Alpha變化速度 Defaults to (0, 0, 0, 0) Animatable
@property float redSpeed;
@property float greenSpeed;
@property float blueSpeed;
@property float alphaSpeed;
// 渲染粒子,能夠設置爲一個CGImage的對象
@property(nullable, strong) id contents;
// 渲染範圍
@property CGRect contentsRect;
// image的像素尺寸同視圖大小的比例,默認爲1.0
@property CGFloat contentsScale;

項目中,粒子動畫的應用場景,大可能是配合彈框提示出現的

簡單demo演示
實現綵帶噴撒動畫的效果

  • 初始化粒子系統,實現基本效果(須要注意的是,Acceleration屬性的設置,咱們給y方向設置爲9.8,熱愛物理的童鞋有木有想起什麼來呢~~)
- (void)testForCAEmitterLayer{

    // 初始化發射器圖層
    CAEmitterLayer *emitterLayer = [CAEmitterLayer layer];
    emitterLayer.birthRate = 1;
    emitterLayer.lifetime = 10;
    emitterLayer.emitterPosition = self.view.center;
    emitterLayer.emitterSize = self.waveButton.frame.size;
    emitterLayer.emitterShape = kCAEmitterLayerRectangle;
    emitterLayer.emitterMode = kCAEmitterLayerOutline;
    emitterLayer.renderMode = kCAEmitterLayerOldestFirst;

    // 初始化發射單元並標記name,方便經過keypath修改相關屬性
    CGSize particleSize = CGSizeMake(15, 25);
    CAEmitterCell *blueCell = [self demo_CAEmitterCellWithImage:[self createImageWithColor:[UIColor blueColor] andSize:particleSize]];
    blueCell.name = @"blue";
    CAEmitterCell *yellowCell = [self demo_CAEmitterCellWithImage:[self createImageWithColor:[UIColor yellowColor] andSize:particleSize]];
    yellowCell.name = @"yellow";
    CAEmitterCell *redCell = [self demo_CAEmitterCellWithImage:[self createImageWithColor:[UIColor redColor] andSize:particleSize]];
    redCell.name = @"red";
    
    // 將發射單元添加至發射器
    emitterLayer.emitterCells = @[blueCell,yellowCell,redCell];
    
    // 在目標位置添加發射器
    [self.view.layer addSublayer:emitterLayer];
    
}

- (CAEmitterCell *)demo_CAEmitterCellWithImage:(UIImage *)image{

    CAEmitterCell *emitterCell = [CAEmitterCell emitterCell];
    // contents
    emitterCell.contents = (__bridge id _Nullable)(image.CGImage);
    // birthRate
    emitterCell.birthRate = 10;
    // lifetime
    emitterCell.lifetime = 40;
    emitterCell.lifetimeRange = 20;
    // emission
    // emitterCell.emissionLongitude
    // emitterCell.emissionLatitude
    emitterCell.emissionRange = M_PI_2;
    // volocity
    emitterCell.velocity = 200;
    emitterCell.velocityRange = 20;
    // Acceleration
    emitterCell.xAcceleration = 0;
    emitterCell.yAcceleration = 9.8;
    // scale
    emitterCell.scale = 0.6;
    emitterCell.scaleRange = 0.6;
    // spin
    emitterCell.spin = M_PI * 2;
    emitterCell.spinRange = M_PI * 2;
    // color
    // emitterCell.redRange = 0.1f;
    // emitterCell.greenRange = 0.1f;
    // emitterCell.blueRange = 0.1f;
    // emitterCell.alphaRange = 1;
    // color speed
    // emitterCell.alphaSpeed = 10;
    
    
    return emitterCell;
    
}

- (UIImage *)createImageWithColor:(UIColor *)color andSize:(CGSize)size{

    UIGraphicsBeginImageContext(size);
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextSetFillColorWithColor(context, color.CGColor);
    CGContextFillRect(context, CGRectMake(0, 0, size.width, size.height));
    
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    
    return image;
    
}

效果以下
CAEmitterLayer_1.gif

  • 添加內容層動畫,優化效果(由於EmitterLayer繼承自CALayer,因此顯示層的CoreAnimation對它一樣適用。同時,這裏就用到了咱們設置的EmitterCellname屬性值)
CAAnimationGroup *demo_ParticleAnimation(){

    CABasicAnimation *blue = [CABasicAnimation animation];
    blue.keyPath = @"emitterCells.blue.birthRate";
    blue.fromValue = @30;
    blue.toValue = @0;
    
    CABasicAnimation *red = [CABasicAnimation animation];
    red.keyPath = @"emitterCells.red.birthRate";
    red.fromValue = @30;
    red.toValue = @0;
    
    CABasicAnimation *yellow = [CABasicAnimation animation];
    yellow.keyPath = @"emitterCells.yellow.birthRate";
    yellow.fromValue = @30;
    yellow.toValue = @0;
    
    CAAnimationGroup *group = [[CAAnimationGroup alloc] init];
    group.animations = @[blue,red,yellow];
    group.duration = 1.0f;
    group.fillMode = kCAFillModeForwards;
    [group setRemovedOnCompletion:NO];
    
    return group;
    
}

合適位置給發射器添加動畫

// 添加發射動畫
[emitterLayer addAnimation:demo_ParticleAnimation() forKey:nil];

效果以下

CAEmitterLayer_2.gif

CAGradientLayer 梯度動畫

CAGradientLayer是系統提供的顏色梯度變化能力的類,使用相對比較簡單

下面看一下頭文件中的幾個簡單接口

// CGColorRef顏色數組
@property(nullable, copy) NSArray *colors;
// 控制顏色範圍的數組[0,1] 類比`KeyFrameAnimation`中的`keyTimes`理解
@property(nullable, copy) NSArray<NSNumber *> *locations;
// 相似`anchorPoint`,是相對於視圖自己映射關係的相對位置,要來絕對梯度變化的方向
@property CGPoint startPoint;
@property CGPoint endPoint;

項目中彷佛用到的地方很少,也許在音頻的應用中作些跑馬燈(此處手動滑稽)或者正常點的二維碼掃描動畫中用到。其餘的應用場景暫時想不出,若是這篇文章真的有讀者的話,歡迎補充交流

簡單demo演示
接下來,咱們像經典致敬,山寨一個活動解鎖效果
直接上代碼

// GradientLayer
- (void)testForGradientLayer{

    CAGradientLayer *gradientLayer = [[CAGradientLayer alloc] init];
    gradientLayer.frame = self.label.bounds;
    gradientLayer.startPoint = CGPointMake(0, 0);// 相似anchor point的映射關係
    gradientLayer.endPoint = CGPointMake(1, 0);
    gradientLayer.colors = @[(__bridge id _Nullable)[UIColor clearColor].CGColor,(__bridge id _Nullable)[UIColor whiteColor].CGColor,(__bridge id _Nullable)[UIColor clearColor].CGColor];
    gradientLayer.locations = @[@0,@0,@0.3];// [0,1]
    
    CABasicAnimation *basic = [CABasicAnimation animation];
    basic.keyPath = @"locations";
    basic.toValue = @[@0.7,@1,@1];
    basic.duration = 3.0;
    basic.fillMode = kCAFillModeForwards;

    [basic setRemovedOnCompletion:NO];
    basic.repeatCount = MAXFLOAT;
    
    [gradientLayer addAnimation:basic forKey:nil];
    
    // 設置蒙板
    self.label.layer.mask = gradientLayer;
    
}

- (void)setupUI4GradientLayer{

    self.label = [[UILabel alloc] init];
    _label.text = @"Slide To Unlock";
    _label.textColor = [UIColor whiteColor];
    _label.textAlignment = NSTextAlignmentCenter;
    _label.font = [UIFont systemFontOfSize:18 weight:UIFontWeightMedium];
    _label.frame = CGRectMake(30, CGRectGetHeight(self.view.bounds) -  100, CGRectGetWidth(self.view.bounds) - 60, 60);
    
    [self.view addSubview:_label];
    self.view.backgroundColor = [UIColor blackColor];
    
}

效果以下(彷佛還蠻不錯的-。-)

CAGradientLayer.gif

CAShapeLayer 繪製動畫

CAShapeLayer配合CGPathRef或者UIBezierPath路徑,堪稱繪圖利器。上文中演示隱式動畫時已經提到,下面再經過例子,演示幾種常見的用法

  • UIBezierPath(不瞭解貝塞爾曲線原理的同窗,請移步本文第二章貝塞爾曲線
  • CGPath

CAShapeLayer.gif

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{

    self.testLayer.strokeEnd = 1;
    self.testLayer2.strokeEnd = 1;
    self.testLayer3.strokeEnd = 1;
    
}

// CAshapeLayer
- (void)testForCAShapeLayer{
    
    // bezierPath
    CGRect screenBounds = [UIScreen mainScreen].bounds;
    UIBezierPath *path = [UIBezierPath bezierPath];
    [path moveToPoint:CGPointMake(20, 200)];
    [path addCurveToPoint:CGPointMake(screenBounds.size.width - 20, 200) controlPoint1:CGPointMake(screenBounds.size.width/2, 50) controlPoint2:CGPointMake(screenBounds.size.width/2, 350)];

    CAShapeLayer *shapeLayer = [[CAShapeLayer alloc] init];
    shapeLayer.strokeEnd = 0;
    shapeLayer.strokeColor = [UIColor redColor].CGColor;
    shapeLayer.fillColor = [UIColor clearColor].CGColor;
    shapeLayer.lineWidth = 3;
    shapeLayer.path = path.CGPath;
    
    self.testLayer = shapeLayer;
    [self.view.layer addSublayer:shapeLayer];
    
    // CGPath
    CGMutablePathRef mutablePath = CGPathCreateMutable();
    CGPathMoveToPoint(mutablePath, NULL, 20, 200);
    CGPathAddLineToPoint(mutablePath, NULL, screenBounds.size.width/2, 50);
    CGPathAddLineToPoint(mutablePath, NULL, screenBounds.size.width/2, 350);
    CGPathAddLineToPoint(mutablePath, NULL, screenBounds.size.width - 20, 200);
    
    CAShapeLayer *shapeLayer2 = [[CAShapeLayer alloc] init];
    shapeLayer2.strokeEnd = 0;
    shapeLayer2.strokeColor = [UIColor lightGrayColor].CGColor;
//    shapeLayer2.fillColor = [[UIColor blueColor] colorWithAlphaComponent:0.1].CGColor;
    shapeLayer2.fillColor = [UIColor clearColor].CGColor;
    shapeLayer2.lineWidth = 5;
    shapeLayer2.lineCap = kCALineCapRound;
    shapeLayer2.lineJoin = kCALineJoinBevel;
    shapeLayer2.path = mutablePath;
    
    self.testLayer2 = shapeLayer2;
    [self.view.layer addSublayer:shapeLayer2];
    
    
    CGMutablePathRef mutablePath2 = CGPathCreateMutable();
    CGPathMoveToPoint(mutablePath2, NULL, screenBounds.size.width/2, screenBounds.size.height - 50);
    CGPathAddLineToPoint(mutablePath2, NULL, screenBounds.size.width/2, 380);

    
    CAShapeLayer *shapeLayer3 = [CAShapeLayer layer];
    shapeLayer3.strokeColor = [UIColor purpleColor].CGColor;
    shapeLayer3.lineWidth = 20;
    shapeLayer3.path = mutablePath2;
    shapeLayer3.strokeEnd = 0;
    
    self.testLayer3 = shapeLayer3;
    [self.view.layer addSublayer:shapeLayer3];
    
    
    // slider
    UISlider *slider = [[UISlider alloc] initWithFrame:CGRectMake(20, screenBounds.size.height - 50, screenBounds.size.width - 40, 50)];
    slider.minimumValue = 0;
    slider.maximumValue = 1;
    slider.value = 1;
    [slider addTarget:self action:@selector(valueChanged:) forControlEvents:UIControlEventValueChanged];
    [self.view addSubview:slider];
    
}

- (void)valueChanged:(UISlider *)slider{

    self.testLayer.strokeEnd = slider.value;
    self.testLayer2.strokeEnd = slider.value;
    self.testLayer3.strokeEnd = slider.value;
    
}
CAReplicatorLayer 複製動畫

顧名思義,這個Layer是讓咱們具有了複製能力。經過複製一些帶動畫的layer,實現有層次感的動畫,由於接口較少,且容易理解,特別注意下instanceTransform屬性便可。有興趣的同窗直接進代碼


// sound wave
    CAShapeLayer *circle = [CAShapeLayer layer];
    circle.bounds = CGRectMake(0, 0, 100, 100);
    circle.position = self.view.center;
    circle.opacity = 1;
    
    UIBezierPath *circlepath  = [UIBezierPath bezierPathWithArcCenter:CGPointMake(50, 50) radius:15 startAngle:0 endAngle:2 * M_PI  clockwise:YES];
    circle.fillColor = ThemeColor.CGColor;
    circle.path = circlepath.CGPath;
    

    CABasicAnimation *opacity = [CABasicAnimation animation];
    opacity.keyPath = @"opacity";
    opacity.fromValue = @1;
    opacity.toValue = @0;
    

    CABasicAnimation *scale = [CABasicAnimation animation];
    scale.keyPath = @"transform.scale";
    scale.fromValue = @1;
    scale.toValue = @(0.3);
    
    CAAnimationGroup *group = [CAAnimationGroup animation];
    group.duration = 0.8;
    group.fillMode = kCAFillModeForwards;
    [group setRemovedOnCompletion:YES];
    group.animations = @[scale];
    group.autoreverses = YES;
    group.repeatCount = MAXFLOAT;
    [circle addAnimation:group forKey:nil];

    
    CAReplicatorLayer *gradientLayer = [CAReplicatorLayer layer];
    [gradientLayer addSublayer:circle];
    gradientLayer.instanceCount = 3;
    gradientLayer.instanceDelay = 0.3;
    
//    [self.view.layer addSublayer:gradientLayer];
    
    // loading ball
    CAReplicatorLayer *instanceGradient = [CAReplicatorLayer layer];
    [instanceGradient addSublayer:circle];
    instanceGradient.instanceCount = 3;
    instanceGradient.instanceDelay = 0.2;
    instanceGradient.instanceTransform = CATransform3DMakeTranslation(50, 0, 0);
    
    [self.view.layer addSublayer:instanceGradient];

總結實踐

帶點擊水紋及加載提示的button動畫

  • 關鍵點:

    • 水波:NSTimer逐幀動畫
    • 位置:顯示層動畫,修改framecenter
    • 形狀:內容層動畫,使用AnimationGroup組合cornerRadiusopacity的修改
    • 旋轉:CADisplayLink逐幀動畫
    • 對號:修改CAShapeLayerstrokeEnd屬性實現的CABasicAnimation動畫

效果以下:

demo.gif

(demo地址在文章最後)


貝塞爾曲線

原理描述:給定n+1個數據點,p0(x0,y0)...pn(xn,yn),生成一條曲線,使得該曲線與這些點所鏈接的折線相近 

相關資料

百度百科:貝塞爾曲線
原理解析:貝塞爾曲線
Bezier.gif

能夠看到,上圖中折線的中間點,即爲咱們所說的控制點,他的位置變化,直接影響了曲線的走向

常見案例

可手動移除的動態Badgeview

手機QQ中最先出現(強迫症福音有木有),能夠根據手勢滑動的距離動態改變badge的形狀

咱們能夠大體根據下圖中繪製的樣例,理解貝塞爾曲線在這個例子中的做用

這個Badgeview由如下幾部分組成:

  • 上方跟隨手勢的小球
  • 下方固定位置的小球
  • 中間須要填充紅色的繪製區域 (注:肯定上半部分曲線的三個點爲 C/P/B,肯定下半部分曲線的三個點爲D/O/A,這幾個點的位置肯定,用到了最基礎的正弦函數 餘弦函數 以及三角形求變長的簡單計算)

總結

全部的複雜動畫,都是由基本顯示層動畫內容層動畫組合而成。嘗試作過一些練習後,筆者這隻鶸,發現動畫真的是一件有意思的事情呢!

筆者簡書地址:iOS-動畫知識梳理
傳送門:Demo

相關文章
相關標籤/搜索