iOS CAReplicatorLayer 簡單動畫

寫在最前面,最近在看學習的時候,偶然間發(fā)現(xiàn)一個沒有用過的Layer,于是抽空研究了下,本來應(yīng)該能提前記錄下來,但是苦逼的碼農(nóng)需要租房子,所以耽擱了幾天,但是堅持就是勝利,下面就來看看這個強大的CAReplicatorLayer,通過這個,可以做很多炫酷的動畫,能省很多步驟。

到底是什么呢?

CAReplicatorLayer主要是為了高效生成許多相似的圖層,可以復制自己子層的layer,并且復制出來的layer和原生子層有同樣的屬性,位置,形變,動畫。

相關(guān)屬性

查看API我們可以看到有一下參數(shù)

//拷貝的個數(shù),包括自身 默認為1
@property NSInteger instanceCount;
//是否開啟景深
@property BOOL preservesDepth;
//拷貝的layer執(zhí)行動畫的間隔時間 
@property CFTimeInterval instanceDelay;
//拷貝的layer執(zhí)行的3D變換  在前一個的基礎(chǔ)上
@property CATransform3D instanceTransform;
//拷貝的layer的顏色變換
@property(nullable) CGColorRef instanceColor;
//顏色偏移參數(shù)
@property float instanceRedOffset;
@property float instanceGreenOffset;
@property float instanceBlueOffset;
//透明度偏移參數(shù)
@property float instanceAlphaOffset;
知識補充

在進行實例之前,如果大家對UIBezierPathCAAnimation不太了解的,可以先看看我前面寫的關(guān)于這兩個的文章iOS 之UIBezierPathiOS 核心動畫 Core Animation淺談

實戰(zhàn)

下面我們先看一組效果圖,這是我簡單寫的幾個例子

CAReplicatorLayer1.gif
分析

就上面的效果,我們先拿其中一個進行舉例說明

就拿這個有20個橙色圓圈的動畫來說,之前我也有寫個,但是那個時候并不了解CAReplicatorLayer,就用的比較麻煩的辦法,下面先看看之前的代碼

- (void)setupAnimationInLayer:(CALayer *)layer withSize:(CGFloat)size tintColor:(UIColor *)tintColor
{
    NSTimeInterval beginTime = CACurrentMediaTime();
    //小圓圈的大小
    CGFloat circleSize = size/4.0;
    
    CGFloat startY = (layer.bounds.size.height - size)/2.0;
    CGFloat startX = (layer.bounds.size.width - size)/2.0;
    CGSize layerSize = layer.bounds.size;
    
    CGFloat offeset = (size/2 - circleSize/2) * sinf(M_PI_4);
    
    NSArray *rectArray = @[[NSValue valueWithCGRect:CGRectMake(layerSize.width/2 - circleSize/2, startY, circleSize, circleSize)],
                           [NSValue valueWithCGRect:CGRectMake(layerSize.width/2 - circleSize/2 + offeset, layerSize.height/2-offeset - circleSize/2, circleSize, circleSize)],
                           [NSValue valueWithCGRect:CGRectMake(layerSize.width/2 - circleSize + size/2, layerSize.height/2 - circleSize/2, circleSize, circleSize)],
                           [NSValue valueWithCGRect:CGRectMake(layerSize.width/2 - circleSize/2 + offeset, layerSize.height/2 + offeset - circleSize/2, circleSize, circleSize)],
                           [NSValue valueWithCGRect:CGRectMake(layerSize.width/2 - circleSize/2, startY + size-circleSize, circleSize, circleSize)],
                           [NSValue valueWithCGRect:CGRectMake(layerSize.width/2 - circleSize/2 - offeset, layerSize.height/2 + offeset - circleSize/2, circleSize, circleSize)],
                           [NSValue valueWithCGRect:CGRectMake(startX, layerSize.height/2 - circleSize/2, circleSize, circleSize)],
                           [NSValue valueWithCGRect:CGRectMake(layerSize.width/2 - circleSize/2 - offeset, layerSize.height/2-offeset - circleSize/2, circleSize, circleSize)]];
    NSArray *begintimes = @[@(0),@(0.12),@(0.24),@(0.36),@(0.48),@(0.6),@(0.72),@(0.84)];
    
    for (int i = 0;i < rectArray.count;i++)
    {
        NSValue *data = rectArray[i];
        CGRect rect = data.CGRectValue;
        
        CALayer *sublayer = [CALayer layer];
        sublayer.frame = rect;
        sublayer.backgroundColor = [UIColor whiteColor].CGColor;
        sublayer.cornerRadius = circleSize/2;

        
        
        
        CAKeyframeAnimation *transformAnimation = [CAKeyframeAnimation animationWithKeyPath:@"transform"];
        transformAnimation.values = @[[NSValue valueWithCATransform3D:CATransform3DMakeScale(0.5, 0.5, 0)],[NSValue valueWithCATransform3D:CATransform3DMakeScale(1.0, 1.0, 0)],[NSValue valueWithCATransform3D:CATransform3DMakeScale(0.5, 0.5, 0)]];
        
        CAKeyframeAnimation *opacityAnimation = [CAKeyframeAnimation animationWithKeyPath:@"opacity"];
        opacityAnimation.values = @[@(0.5),@(1.0),@(0.5)];
        //keyTimes這個可選參數(shù)可以為對應(yīng)的關(guān)鍵幀指定對應(yīng)的時間點,其取值范圍為0到1.0,keyTimes中的每一個時間值都對應(yīng)values中的每一幀.當keyTimes沒有設(shè)置的時候,各個關(guān)鍵幀的時間是平分的
//        opacityAnimation.keyTimes
        
        CAAnimationGroup *groupAnimation = [CAAnimationGroup animation];
        groupAnimation.duration = 1;
        groupAnimation.removedOnCompletion = NO;
        groupAnimation.repeatCount = HUGE_VALF;
        groupAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
        groupAnimation.animations = @[transformAnimation,opacityAnimation];
        groupAnimation.beginTime = beginTime + [begintimes[i]doubleValue];
//        groupAnimation.timeOffset = [timeOffeset[i] doubleValue];
        
        [layer addSublayer:sublayer];
        [sublayer addAnimation:groupAnimation forKey:nil];
    }
}

在上面的代碼中,我用了一個數(shù)組rectArray來裝后面圓圈的位置,然后在用了一個for循環(huán),來依次添加圓圈的layer,并且大家注意,在代碼中我還有一個數(shù)組begintimes,這個在后面的CAAnimationGroup中用到了,用來間隔圓圈執(zhí)行動畫。雖然整體看上去代碼并不多,但是其中比較麻煩的就是在計算坐標信息上。

CAReplicatorLayer 簡單解決

在接觸到CAReplicatorLayer后,就不用這么麻煩了,20個圓圈,我們可以通過復制instanceCount這個來進行實現(xiàn),執(zhí)行的時間間隔我們可以通過instanceDelay來實現(xiàn),當然還有一個最重要的就是其位置。查看屬性,我們會發(fā)現(xiàn),CAReplicatorLayer有一個屬性instanceTransform,就是進行3D變換,要形成一個圓形的環(huán)狀,我們可以對其進行Z軸旋轉(zhuǎn),從而達到我們想要的效果。那么每一個所旋轉(zhuǎn)的角度是多少呢?計算一下,就是20個圓圈平分2*M_PI,所以3D變換的代碼應(yīng)該是這樣的

CATransform3D transform = CATransform3DIdentity;
transform = CATransform3DRotate(transform, M_PI / 10.0, 0, 0, 1);

廢話不多說,我們來看看新的解決方案的代碼

//一串圈圈,依次變大變小 透明度也變化
- (void)ballSpinFadeAnimationLayer:(CALayer *)layer withSize:(CGSize)size tintColor:(UIColor *)tintColor
{
    CAReplicatorLayer *replicatorLayer = [CAReplicatorLayer layer];
    replicatorLayer.frame = CGRectMake(0, 0, layer.frame.size.width-40, layer.frame.size.height-40);
    replicatorLayer.backgroundColor = [UIColor whiteColor].CGColor;
    [layer addSublayer:replicatorLayer];
    
    
    CALayer *ballLayer = [CALayer layer];
    ballLayer.frame = CGRectMake((CGRectGetWidth(replicatorLayer.frame) - 10)/2.0, 0, 10, 10);
    ballLayer.backgroundColor = tintColor.CGColor;
    ballLayer.cornerRadius = 5.0;
    
    
    CAKeyframeAnimation *transformAnimation = [CAKeyframeAnimation animationWithKeyPath:@"transform"];
    transformAnimation.values = @[[NSValue valueWithCATransform3D:CATransform3DMakeScale(0.5, 0.5, 0)],[NSValue valueWithCATransform3D:CATransform3DMakeScale(1.0, 1.0, 0)],[NSValue valueWithCATransform3D:CATransform3DMakeScale(0.5, 0.5, 0)]];
    
    CAKeyframeAnimation *opacityAnimation = [CAKeyframeAnimation animationWithKeyPath:@"opacity"];
    opacityAnimation.values = @[@(0.5),@(1.0),@(0.5)];
    
    //opacityAnimation.keyTimes
    //keyTimes這個可選參數(shù)可以為對應(yīng)的關(guān)鍵幀指定對應(yīng)的時間點,其取值范圍為0到1.0,keyTimes中的每一個時間值都對應(yīng)values中的每一幀.當keyTimes沒有設(shè)置的時候,各個關(guān)鍵幀的時間是平分的
    
    CAAnimationGroup *groupAnimation = [CAAnimationGroup animation];
    groupAnimation.duration = 1;
    groupAnimation.removedOnCompletion = NO;
    groupAnimation.repeatCount = HUGE_VALF;
    //勻速
    groupAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
    groupAnimation.animations = @[transformAnimation,opacityAnimation];
    [ballLayer addAnimation:groupAnimation forKey:@""];
    
    //繞Z軸旋轉(zhuǎn)M_PI / 10.0  下面復制20個 剛好是一圈  2*M_PI
    CATransform3D transform = CATransform3DIdentity;
    transform = CATransform3DRotate(transform, M_PI / 10.0, 0, 0, 1);
    
    [replicatorLayer addSublayer:ballLayer];
    replicatorLayer.instanceCount = 20;
    replicatorLayer.instanceTransform = transform;
    replicatorLayer.instanceDelay = 0.05;
}

對比之下,明顯發(fā)現(xiàn)簡單很多,而且思路也很清晰。

下面我們再對第一個心形動畫進行分析一下:
這個心形動畫截圖沒有截完全,其效果我簡單描述下,從中心最凹處每隔一個時間段吐出一個圓圈,然后每一個都按照心形的軌跡進行運動。我們就不可能通過instanceTransform來創(chuàng)建軌跡,因為這個是在初始化的時候就已經(jīng)創(chuàng)建好其位置了。所以我們只能在其復制的layer上想辦法??梢赃@樣來思考,就是復制的layer每隔一個時間段就開始去執(zhí)行心形動畫。那么心形動畫我們怎么去實現(xiàn)呢?由于這是一個不規(guī)則的圖形,而且是曲線,所以我們想到了二次貝塞爾曲線,我們可以通過兩個二次貝塞爾曲線來進行拼接。
下面我們來看完整的代碼

//愛心類型
- (void)loveAnimationLayer:(CALayer *)layer withSize:(CGSize)size tintColor:(UIColor *)tintColor
{
    CAReplicatorLayer *replicatorLayer = [CAReplicatorLayer layer];
    replicatorLayer.frame = CGRectMake(0, 0, layer.frame.size.width, layer.frame.size.height);
    replicatorLayer.backgroundColor = [UIColor whiteColor].CGColor;
    [layer addSublayer:replicatorLayer];
    
    CALayer *lineBallLayer = [CALayer layer];
    lineBallLayer.backgroundColor = tintColor.CGColor;
    lineBallLayer.cornerRadius = 5;
    lineBallLayer.frame = CGRectMake((size.width - 10)/2.0, 20, 10, 10);
    
    
    
    UIBezierPath *tPath = [UIBezierPath bezierPath];
    [tPath moveToPoint:CGPointMake(size.width/2.0, 25)];
    //二次貝塞爾曲線
    [tPath addQuadCurveToPoint:CGPointMake(size.width/2.0, 100) controlPoint:CGPointMake(size.width/2.0 + 80, -10)];
    [tPath addQuadCurveToPoint:CGPointMake(size.width/2.0, 25) controlPoint:CGPointMake(size.width/2.0 - 80, -10)];
    [tPath closePath];//封閉路徑

    
    CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
    animation.path = tPath.CGPath;//根據(jù)path路徑來進行動畫
    animation.duration = 8;//動畫時間
    animation.repeatCount = HUGE;//一直重復動畫
    [lineBallLayer addAnimation:animation forKey:@""];//key可以不設(shè)置
    
    [replicatorLayer addSublayer:lineBallLayer];
    //    replicatorLayer.instanceColor = [UIColor colorWithRed:1 green:1 blue:1 alpha:1].CGColor;
    replicatorLayer.instanceGreenOffset = -0.03;       // 顏色值遞減。
    replicatorLayer.instanceRedOffset = -0.02;         // 顏色值遞減。
    replicatorLayer.instanceBlueOffset = -0.01;        // 顏色值遞減。
    replicatorLayer.instanceCount = 40;//復制lineBallLayer 40個
    replicatorLayer.instanceDelay = 0.2;//每個復制對象執(zhí)行path路徑動畫的時間間隔 前一個和后一個之間
}

其中我對顏色也進行了遞減,這樣看到的效果更加明顯。

寫在最后

CAReplicatorLayer確實是個好東西,之前孤陋寡聞了。
最后附上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)容

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