CoreAnimation動畫(一):遮罩動畫/注水動畫

遮罩動畫/注水動畫

一般用CoreAnimation+mask 來實現(xiàn)其動畫效果。
mask指lyaer.mask屬性,等同于UIView的clipsToBounds屬性,將超出自身范圍外的內(nèi)容剪裁掉,不顯示。

來看動畫效果:


注水動畫

注水動畫的原理(很簡單的):
有兩個layer層,一個圖形層,一個遮罩層。用遮罩層去逐漸覆蓋背景圖形層,達到動畫的效果。

本著由易到難的原則,先看看最簡單的動畫如何去做。

easymaskanimating.gif

這是一個類似進度條的動畫(丑了點),綠色是遮蓋層,橙色是背景圖形層??梢钥吹骄G色的遮罩層逐漸的覆蓋了橙色的背景層。類似進度由0到1的過程。
那么,這個動畫如何實現(xiàn)呢?
根據(jù)條件的不同,動畫實現(xiàn)的方式也不同:

  • 不用控制進度,只要完成動畫就好
  • 需要控制進度,還要控制動畫的快慢

首先,不需要控制進度的實現(xiàn)。在一定時間內(nèi),直接用遮罩層去覆蓋背景圖層好了。這里插一句:

Core Animation 維護了兩個平行 layer 層次結(jié)構(gòu): model layer tree(模型層樹) 和 presentation layer tree(表示層樹)。前者中的 layers 反映了我們能直接看到的 layers 的狀態(tài),而后者的 layers 則是動畫正在表現(xiàn)的值的近似。

當動畫運行結(jié)束后,model layer的值并不會被改變,所以添加動畫的圖層仍然會回到初始位置。但當我們想要動畫結(jié)束后,動畫停留在結(jié)束的位置,該怎么辦呢?兩種方法:

  • 設(shè)定動畫removedOnCompletion屬性為NO,在圖層添加動畫之后(即[layer addAnimation:XXX forKey:ni]之后),手動修改model layer的結(jié)束位置,下面的示例就是用的這種方法.
  • 圖層添加動畫之前,設(shè)定removedOnCompletion屬性為YES,同時設(shè)定animation.fillMode = kCAFillModeForward;

參考這里

position動畫

keyPath=@"position.x"的實現(xiàn)方法:

    //position.x animation
    CALayer *pContainerLayer = [CALayer layer];
    pContainerLayer.frame = CGRectMake(200, 50, 100, 50);
    pContainerLayer.backgroundColor = [[UIColor orangeColor] CGColor];
    [self.view.layer addSublayer:pContainerLayer];
    
    CALayer *pCoverLayer = [CALayer layer];
    pCoverLayer.frame = CGRectMake(0 - 100 , 0, 100, 50);
    pCoverLayer.backgroundColor = [[UIColor greenColor] CGColor];
    [pContainerLayer addSublayer:pCoverLayer];

    CGFloat pToX = 100 / 2;
    CABasicAnimation *pAnimation = [CABasicAnimation animationWithKeyPath:@"position.x"];;
    pAnimation.fromValue = @(pCoverLayer.position.x);
    pAnimation.toValue = @(pToX);
    pAnimation.duration = 5.0f;
    pAnimation.repeatCount = 1;
    pAnimation.removedOnCompletion = YES;

    [pCoverLayer addAnimation:pAnimation forKey:nil];

width動畫

keyPath=@"bounds.size.width"的實現(xiàn)方法:寬度由 0->100

    //bounds.size.width animation
    CALayer *bContainerLayer = [CALayer layer];
    bContainerLayer.frame = CGRectMake(200, 110, 100, 50);
    bContainerLayer.backgroundColor = [[UIColor orangeColor] CGColor];
    [self.view.layer addSublayer:bContainerLayer];
    
    CALayer *bCoverLayer = [CALayer layer];
    bCoverLayer.frame = CGRectMake(0, 0, 0, 50);
    bCoverLayer.backgroundColor = [[UIColor greenColor] CGColor];
    bCoverLayer.anchorPoint = CGPointMake(0, 0.5);
    [bContainerLayer addSublayer:bCoverLayer];
   
    CGFloat bToWidth = 100;
    CABasicAnimation *bAnimation = [CABasicAnimation animationWithKeyPath:@"bounds.size.width"];
    bAnimation.fromValue = @(0);
    bAnimation.toValue = @(bToWidth);
    bAnimation.duration = 5.0f;
    bAnimation.repeatCount = 1;
    bAnimation.removedOnCompletion = YES;
    
    [bCoverLayer addAnimation:bAnimation forKey:nil];
    // change model layer bounds
    bCoverLayer.bounds = CGRectMake(0, 0, 100, 50);

其次,需要控制進度了,上面的方法就沒法使用了。

手動控制進度動畫

主要實現(xiàn)變化,其中速度就是slider從0到1的滑動速度。
slider: 0 -> 1
coverLayer width: 0 -> max(寬度的最大值)

@interface ViewController ()
@property (nonatomic,strong) CALayer *coverLayer;
@property (weak, nonatomic) IBOutlet UISlider *slider;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    CALayer *sContainerLayer = [CALayer layer];
    sContainerLayer.frame = CGRectMake(200, 110, 100, 50);
    sContainerLayer.backgroundColor = [[UIColor orangeColor] CGColor];
    [self.view.layer addSublayer:sContainerLayer];
        
    CALayer *sCoverLayer = [CALayer layer];
    sCoverLayer.frame = CGRectMake(0, 0, 0, 50);
    sCoverLayer.backgroundColor = [[UIColor greenColor] CGColor];
    sCoverLayer.anchorPoint = CGPointMake(0, 0.5);
    [sContainerLayer addSublayer:sCoverLayer];
    self.coverLayer = sCoverLayer;
        
    [self.slider addTarget:self action:@selector(update:) forControlEvents:UIControlEventValueChanged];
}
//這里的slider.value * 100,100是背景圖層的寬度
- (void)update:(UISlider *)slider {
    self.coverLayer.frame = CGRectMake(0, 0, slider.value * 100, 50);
}

@end

下面來個稍微復雜點的,加入layer的mask屬性:

  • 一種背景圖層使用mask屬性,遮罩層不變,還是一個矩形,然后覆蓋的時候,只能顯示背景圖層的形狀,超出的部分被剪裁掉
  • 另一種mask使用特殊圖形,遮蓋的時候,顯示的是mask的圖層的樣子
注水動畫

這是一個不規(guī)則圖形的注水動畫,其中背景圖層不規(guī)則,遮罩圖層是一個矩形。遮罩圖層顏色是白色,背景圖層是橙色。動畫的運動方向是,白色圖層從下到上,這里對應(yīng)的是size.heigh 由 max(94) -> 0 。
如何生成動畫中的圖形:

  • CAShapeLayer利用屬性path,用貝塞爾創(chuàng)建需要的圖形。工具paintCode非常好用。
  • 設(shè)定背景圖層的mask屬性為上一步生成的shapelayer。這樣就生成特定圖形的圖層了。
    CALayer *canvasLayer = [CALayer layer];
    canvasLayer.frame = CGRectMake(200, 80, 52, 94);
    canvasLayer.backgroundColor = [[UIColor orangeColor] CGColor];
    [self.view.layer addSublayer:canvasLayer];
    
    CAShapeLayer *ovalShapeLayer = [CAShapeLayer layer];
    ovalShapeLayer.path = [[self createBezierPath] CGPath];
    canvasLayer.mask = ovalShapeLayer;
    
    CALayer *coverLayer = [CALayer layer];
    coverLayer.frame = CGRectMake(0, 0 , 52, 94 );
    coverLayer.anchorPoint = CGPointMake(0, 0);
    coverLayer.position = CGPointMake(0, 0);
    coverLayer.backgroundColor = [[UIColor whiteColor] CGColor];
    [canvasLayer addSublayer:coverLayer];
    
    CABasicAnimation *animation = [CABasicAnimation animation];
    animation.keyPath = @"bounds.size.height";
    animation.fromValue = @(94);
    animation.toValue = @(0);
    animation.duration = 5;
    animation.repeatCount = HUGE;
    animation.removedOnCompletion = YES;
    
    [coverLayer addAnimation:animation forKey:nil];

貝塞爾生成的不規(guī)則圖形,frame為{1,1,52,94}

- (UIBezierPath *)createBezierPath {
    // W:H = 70:120
    // oval frame {1,1,52,94}
    UIBezierPath* ovalPath = [UIBezierPath bezierPath];
    [ovalPath moveToPoint: CGPointMake(53, 30.53)];
    [ovalPath addCurveToPoint: CGPointMake(27, 95) controlPoint1: CGPointMake(53, 46.83) controlPoint2: CGPointMake(41.36, 95)];
    [ovalPath addCurveToPoint: CGPointMake(1, 30.53) controlPoint1: CGPointMake(12.64, 95) controlPoint2: CGPointMake(1, 46.83)];
    [ovalPath addCurveToPoint: CGPointMake(27, 1) controlPoint1: CGPointMake(1, 14.22) controlPoint2: CGPointMake(12.64, 1)];
    [ovalPath addCurveToPoint: CGPointMake(53, 30.53) controlPoint1: CGPointMake(41.36, 1) controlPoint2: CGPointMake(53, 14.22)];
    [ovalPath closePath];

    return ovalPath;
}

再來看看波濤洶涌的動畫:

注水動畫矩形背景

這是一個不斷波動并上升的動畫。動畫主要由兩部分組成:

  • 時刻滾動的波浪
  • 不斷上升的水平面

時刻滾動的波浪:創(chuàng)建一個CAShapeLayer層,作為mask。使用CADisplayLink,每楨都重畫shapeLayer的path屬性,這樣子就會產(chǎn)生波浪起伏的效果。

波形動畫

不斷上升的水平面,是利用CABasicAnimation動畫,修改masklayerd的position的結(jié)果。


上升動畫

再說下這里圖層的包含關(guān)系,左面包含右面圖層:view.lyaer->bglayer->canvasLayer->waveLayer
view.layer是當前控制器的view的layer,
bglayer是黑色邊框圖層,
canvasLayer是背景圖層,
waveLayer是遮罩圖層

#import "ViewController.h"

@interface ViewController ()

@property (nonatomic, strong) CADisplayLink *displayLink;
//背景圖層
@property (nonatomic, strong) CALayer *canvasLayer;
//遮罩圖層
@property (nonatomic, strong) CAShapeLayer *waveLayer;
//背景圖層frame
@property (nonatomic) CGRect frame;
//遮罩圖層frame
@property (nonatomic) CGRect shapeFrame;

@end

@implementation ViewController
//初始相位
static float phase = 0;
//相位偏移量
static float phaseShift = 0.25;
- (void)viewDidLoad {
    [super viewDidLoad];
    //shapePointY=200,設(shè)定mask其實位置(0,200)
    CGFloat shapePointY = 200;
    CGRect frame = CGRectMake(0, 0, 100, 200);
    CGRect shapeFrame = CGRectMake(0, shapePointY, 100, 200);
    self.frame = frame;
    self.shapeFrame = shapeFrame;
    
    //黑色邊框
    CALayer *bglayer = [CALayer layer];
    bglayer.frame = CGRectMake(0, 20, 100, 200);
    bglayer.borderWidth = 1.0;
    bglayer.borderColor = [[UIColor blackColor] CGColor];
    [self.view.layer addSublayer:bglayer];
    
    //創(chuàng)建背景圖層
    self.canvasLayer = [CALayer layer];
    self.canvasLayer.frame = frame;
    self.canvasLayer.backgroundColor = [UIColor orangeColor].CGColor;
    [bglayer addSublayer:self.canvasLayer];
    //創(chuàng)建遮罩圖層
    self.waveLayer = [CAShapeLayer layer];
    self.waveLayer.frame = shapeFrame;
    //設(shè)定mask為waveLayer
    self.canvasLayer.mask = self.waveLayer;
    
    //開始動畫
    [self startAnimating];
}

- (void)startAnimating {
    [self.displayLink invalidate];
    self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(update)];
    [self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
    
    //獲得結(jié)束時的position.y值
    CGPoint position = self.waveLayer.position;
    position.y = position.y - self.shapeFrame.size.height;
    CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"];
    animation.fromValue = [NSValue valueWithCGPoint:self.waveLayer.position];
    animation.toValue = [NSValue valueWithCGPoint:position];
    animation.duration = 5.0;
    animation.repeatCount = HUGE_VALF;
    animation.removedOnCompletion = NO;
    [self.waveLayer addAnimation:animation forKey:nil];
    
}
//波浪滾動 phase相位每楨變化值:phaseShift
- (void)update {
    CGRect frame = self.frame;
    phase += phaseShift;
    UIGraphicsBeginImageContext(frame.size);
    UIBezierPath *wavePath = [UIBezierPath bezierPath];
    //用UIBezierPath畫一個閉合的路徑
    CGFloat endX = 0;
    for(CGFloat x = 0; x < frame.size.width ; x += 1) {
        endX=x;
        //正弦函數(shù),求y值
        CGFloat y = 5 * sinf(2 * M_PI *(x / frame.size.width)  + phase) ;
        if (x==0) {
            [wavePath moveToPoint:CGPointMake(x, y)];
        }else {
            [wavePath addLineToPoint:CGPointMake(x, y)];
        }
    }
    CGFloat endY = CGRectGetHeight(frame);
    [wavePath addLineToPoint:CGPointMake(endX, endY)];
    [wavePath addLineToPoint:CGPointMake(0, endY)];
    //修改每楨的wavelayer.path
    self.waveLayer.path = [wavePath CGPath];
    UIGraphicsEndImageContext();
}
@end

最后,讓波浪在特定容器中運動。即文章開始的動效。思路有兩種:

  • 直接添加一個背景圖片到bglayer上
  • 將bglayer設(shè)定為CAShapeLayer,path為想要圖形的值,動畫在CAShpaeLayer中顯示。

最后Demo這里

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 轉(zhuǎn)載:http://www.itdecent.cn/p/32fcadd12108 每個UIView有一個伙伴稱為l...
    F麥子閱讀 6,567評論 0 13
  • 每個UIView有一個伙伴稱為layer,一個CALayer。UIView實際上并沒有把自己畫到屏幕上;它繪制本身...
    shenzhenboy閱讀 3,250評論 0 17
  • 書寫的很好,翻譯的也棒!感謝譯者,感謝感謝! iOS-Core-Animation-Advanced-Techni...
    錢噓噓閱讀 2,427評論 0 6
  • 在iOS中隨處都可以看到絢麗的動畫效果,實現(xiàn)這些動畫的過程并不復雜,今天將帶大家一窺ios動畫全貌。在這里你可以看...
    每天刷兩次牙閱讀 8,686評論 6 30
  • 沙盒環(huán)境就是為了和外部環(huán)境進行隔離,把每個程序都封裝在不同盒子里面,以保證每個程序的python環(huán)境都是獨立的 把...
    又不行了閱讀 1,117評論 0 0

友情鏈接更多精彩內(nèi)容