寫在最前面,最近在看學習的時候,偶然間發(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;
知識補充
在進行實例之前,如果大家對UIBezierPath和CAAnimation不太了解的,可以先看看我前面寫的關(guān)于這兩個的文章iOS 之UIBezierPath和iOS 核心動畫 Core Animation淺談
實戰(zhàn)
下面我們先看一組效果圖,這是我簡單寫的幾個例子

分析
就上面的效果,我們先拿其中一個進行舉例說明
就拿這個有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,希望對各位有用