前言
此demo主要注重,按鈕的動(dòng)畫(huà)并沒(méi)有實(shí)現(xiàn)離線下載,如果對(duì)動(dòng)畫(huà)不感興趣的老鐵就可以不看了。
效果來(lái)源:網(wǎng)絡(luò)搜索微操作的時(shí)候搜到的
分析
看到一個(gè)動(dòng)畫(huà)我們不要一拿到就開(kāi)始做,首先多分析分析,這個(gè)怎么實(shí)現(xiàn)有哪些元素構(gòu)成
開(kāi)始做下載操作之前會(huì)有個(gè)豎線變成小圓點(diǎn),然后網(wǎng)上拋出去的動(dòng)畫(huà)
然后是下載中
還有最后的完成狀態(tài)
總的我們歸整以下只有背景圓、豎線、箭頭、進(jìn)度圓、波浪(最后的勾也是波浪的一種狀態(tài))和顯示文件大小的Label。這里對(duì)于圖形的繪制我們都選擇
CAShapeLayer
/**
背景圓
*/
@property(nonatomic,strong)CAShapeLayer *bgCircleShapeLayer;
/** 豎線*/
@property(nonatomic,strong)CAShapeLayer *pointShapeLayer;
/** 箭頭*/
@property(nonatomic,strong)CAShapeLayer *arrowShapeLayer;
/** 進(jìn)度*/
@property(nonatomic,strong)CAShapeLayer *progressShapeLayer;
/** 波浪*/
@property(nonatomic,strong)AIDownloadWaveLayer *waveLayer;
/** 文件大小*/
@property(nonatomic,weak)UILabel *progressLabel;
動(dòng)畫(huà)
點(diǎn)擊了按鈕到實(shí)際開(kāi)始下載之間,我們是有個(gè)動(dòng)畫(huà)
1、豎線變成點(diǎn)
這種變路徑代碼我們選擇基礎(chǔ)動(dòng)畫(huà)中的path
屬性
//變?yōu)辄c(diǎn)
UIBezierPath *pointPath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(self.ai_middleX, self.ai_middleY + 1) radius:.5 startAngle:0 endAngle:2*M_PI clockwise:NO];
CABasicAnimation *changeToPoint = [CABasicAnimation animationWithKeyPath:@"path"];
changeToPoint.toValue = (__bridge id)(pointPath.CGPath);
changeToPoint.fillMode = kCAFillModeForwards;
changeToPoint.removedOnCompletion = NO;
changeToPoint.duration = .2;
[self.pointShapeLayer addAnimation:changeToPoint forKey:nil];
2、箭頭變成直線
在變成直線的時(shí)候縱坐標(biāo)在網(wǎng)上移動(dòng)一點(diǎn),這樣會(huì)有種圓點(diǎn)是被線彈上去的感覺(jué)
上下的位移肯定選擇position.y
箭頭變?yōu)榫€要有種把圓點(diǎn)彈出去的感覺(jué)動(dòng)畫(huà)選擇CASpringAnimation
屬性還是選擇path
這里直線的路徑要注意節(jié)點(diǎn)左右對(duì)稱(chēng)不然達(dá)不到左右一樣的效果。
//箭頭變?yōu)榫€
CABasicAnimation *lineAniamtion = [CABasicAnimation animationWithKeyPath:@"position.y"];
lineAniamtion.duration = .2;
lineAniamtion.fillMode = kCAFillModeBackwards;
lineAniamtion.toValue = @(self.arrowShapeLayer.y +10);
lineAniamtion.removedOnCompletion = NO;
UIBezierPath *linePath = [self linePath];
CASpringAnimation *lineSpringAnimation = [CASpringAnimation animationWithKeyPath:@"path"];
lineSpringAnimation.toValue = (__bridge id _Nullable)(linePath.CGPath);
lineSpringAnimation.duration = lineSpringAnimation.settlingDuration;
lineSpringAnimation.damping = 0;
lineSpringAnimation.mass = 30;
lineSpringAnimation.stiffness = 5;
lineSpringAnimation.initialVelocity = 30;
lineSpringAnimation.fillMode = kCAFillModeForwards;
lineSpringAnimation.beginTime = .2;
lineSpringAnimation.removedOnCompletion = NO;
CAAnimationGroup *groupAnimation = [CAAnimationGroup animation];
groupAnimation.duration = 1;
groupAnimation.fillMode = kCAFillModeForwards;
groupAnimation.removedOnCompletion = NO;
groupAnimation.animations = @[lineAniamtion,lineSpringAnimation];
[self.arrowShapeLayer addAnimation:groupAnimation forKey:nil];
//圓點(diǎn)起跳
CASpringAnimation *pointSpringAnimation = [CASpringAnimation animationWithKeyPath:@"position.y"];
pointSpringAnimation.delegate = self;
[pointSpringAnimation setValue:@"pointLayer" forKey:@"name"];
pointSpringAnimation.toValue = @(-self.ai_height*.5 - self.bgCircleShapeLayer.lineWidth * 0.5 + self.pointShapeLayer.lineWidth * 0.5 );
pointSpringAnimation.duration = pointSpringAnimation.settlingDuration;
pointSpringAnimation.fillMode = kCAFillModeForwards;
pointSpringAnimation.removedOnCompletion = NO;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(changeToPoint.duration * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self.pointShapeLayer addAnimation:pointSpringAnimation forKey:nil];
});
3、圓點(diǎn)起跳到背景圓上
這里我們還是選擇CASpringAnimation
動(dòng)畫(huà)上下移動(dòng)就選擇position.y
屬性,由于這個(gè)小圓點(diǎn)跳出完成后才開(kāi)算下載,我們要在圓點(diǎn)起跳動(dòng)畫(huà)完成后回調(diào),我們?yōu)閯?dòng)畫(huà)添加key/value在動(dòng)畫(huà)結(jié)束的時(shí)候好找到。
[pointSpringAnimation setValue:@"pointLayer" forKey:@"name"];
//圓點(diǎn)起跳
CASpringAnimation *pointSpringAnimation = [CASpringAnimation animationWithKeyPath:@"position.y"];
pointSpringAnimation.delegate = self;
[pointSpringAnimation setValue:@"pointLayer" forKey:@"name"];
pointSpringAnimation.toValue = @(-self.ai_height*.5 - self.bgCircleShapeLayer.lineWidth * 0.5 + self.pointShapeLayer.lineWidth * 0.5 );
pointSpringAnimation.duration = pointSpringAnimation.settlingDuration;
pointSpringAnimation.fillMode = kCAFillModeForwards;
pointSpringAnimation.removedOnCompletion = NO;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(changeToPoint.duration * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self.pointShapeLayer addAnimation:pointSpringAnimation forKey:nil];
});
開(kāi)始下載
下載的時(shí)候要做幾件事
0、狀態(tài)變更,然后回調(diào)
1、隱藏變?yōu)橹本€的箭頭
[self opacityAnimationWithLayer:self.arrowShapeLayer fromValue:1. toValue:0.];
2、文件大小label顯示并變大
//文件大小
[self scaleAnimationWithLayer:self.progressLabel.layer fromValue:.1 toValue:1.];
[self opacityAnimationWithLayer:self.progressLabel.layer fromValue:0. toValue:1.];
1和2的步驟都有不透明度的變化,所以這里直接把透明度提取出一個(gè)方法,我這里是使用pop動(dòng)畫(huà),想多熟悉下當(dāng)讓用CABaseAnimation
也可以
/**
不透明度動(dòng)畫(huà)
@param layer 要執(zhí)行動(dòng)畫(huà)的layer
@param from 從多少開(kāi)始
@param to 到多少
*/
- (void)opacityAnimationWithLayer:(CALayer*)layer fromValue:(CGFloat)from toValue:(CGFloat)to {
POPBasicAnimation *opacityAnimation = [POPBasicAnimation animationWithPropertyNamed:kPOPLayerOpacity];
opacityAnimation.toValue = @(to);
opacityAnimation.fromValue = @(from);
opacityAnimation.duration = .3;
[layer pop_addAnimation:opacityAnimation forKey:nil];
}
然后待會(huì)下載完成后文件大小的label會(huì)有變小的動(dòng)畫(huà),所以這里把,大小的變化也提取出一個(gè)方法
/**
縮放動(dòng)畫(huà)
@param layer 所要縮放的layer
@param from 從多少比例開(kāi)始
@param to 到多少比例
*/
- (void)scaleAnimationWithLayer:(CALayer*)layer fromValue:(CGFloat)from toValue:(CGFloat)to {
//文件大小
POPBasicAnimation *scaleAnimation = [POPBasicAnimation animationWithPropertyNamed:kPOPLayerScaleXY];
scaleAnimation.fromValue = [NSValue valueWithCGPoint:CGPointMake(from, from)];
scaleAnimation.toValue = [NSValue valueWithCGPoint:CGPointMake(to, to)];
scaleAnimation.duration = .3;
[layer pop_addAnimation:scaleAnimation forKey:nil];
}
提取這些方法的好處不止于這里減少重復(fù)代碼,而且以后有類(lèi)似的動(dòng)畫(huà)也可以直接復(fù)制過(guò)去使用
3、添加波浪動(dòng)畫(huà)
這里我簡(jiǎn)單說(shuō)下波浪動(dòng)畫(huà)原理
- 正弦函數(shù): y =Asin(ωx+φ)+C
- A 表示振幅,也就是使用這個(gè)變量來(lái)調(diào)整波浪的高度
- ω表示周期,也就是使用這個(gè)變量來(lái)調(diào)整在屏幕內(nèi)顯示的波浪的數(shù)量
- φ表示波浪橫向的偏移,也就是使用這個(gè)變量來(lái)調(diào)整波浪的流動(dòng)
- C表示波浪縱向的位置,也就是使用這個(gè)變量來(lái)調(diào)整波浪在屏幕中豎直的位置。
在自己找到合適的A和ω后我們只需要改變?chǔ)瘴疫@里只做了四個(gè)波形,所以每個(gè)加π/2四個(gè)后剛好回到原點(diǎn),波浪就是這這四個(gè)波形的切換然后就形成了一次波浪,在一次波浪完成后繼續(xù)調(diào)用下次波浪。
下面是一次波浪動(dòng)畫(huà)的代碼:
- (void)waveAnimateWithLayer:(CALayer*)layer {
// 1
CABasicAnimation *waveAnimationStart = [CABasicAnimation animationWithKeyPath:@"path"];
waveAnimationStart.fromValue = (__bridge id _Nullable)(self.wavePathStarting.CGPath);
waveAnimationStart.toValue = (__bridge id _Nullable)(self.wavePath1.CGPath);
waveAnimationStart.beginTime = 0.0;
waveAnimationStart.duration = KAnimationDuration;
// 2
CABasicAnimation *waveAnimation1 = [CABasicAnimation animationWithKeyPath:@"path"];
waveAnimation1.fromValue = (__bridge id _Nullable)(self.wavePath1.CGPath);
waveAnimation1.toValue = (__bridge id _Nullable)(self.wavePath2.CGPath);
waveAnimation1.beginTime = waveAnimationStart.beginTime + waveAnimationStart.duration;
waveAnimation1.duration = KAnimationDuration;
// 3
CABasicAnimation *waveAnimation2 = [CABasicAnimation animationWithKeyPath:@"path"];
waveAnimation2.fromValue = (__bridge id _Nullable)(self.wavePath2.CGPath);
waveAnimation2.toValue = (__bridge id _Nullable)(self.wavePath3.CGPath);
waveAnimation2.duration = KAnimationDuration;
waveAnimation2.beginTime = waveAnimation1.beginTime + waveAnimation1.duration;
// 4
CABasicAnimation *waveAnimationLow = [CABasicAnimation animationWithKeyPath:@"path"];
waveAnimationLow.fromValue = (__bridge id _Nullable)(self.wavePath3.CGPath);
waveAnimationLow.toValue = (__bridge id _Nullable)(self.wavePath4.CGPath);
waveAnimationLow.duration = KAnimationDuration;
waveAnimationLow.beginTime = waveAnimation2.beginTime + waveAnimation2.duration;
// 5
CABasicAnimation *waveAnimationCompelted = [CABasicAnimation animationWithKeyPath:@"path"];
waveAnimationCompelted.fromValue = (__bridge id _Nullable)(self.wavePath4.CGPath);
waveAnimationCompelted.toValue = (__bridge id _Nullable)(self.wavePathStarting.CGPath);
waveAnimationCompelted.duration = KAnimationDuration;
waveAnimationCompelted.beginTime = waveAnimationLow.beginTime + waveAnimationLow.duration;
// 6
CAAnimationGroup *animationGroup = [[CAAnimationGroup alloc] init];
animationGroup.delegate = self;
[animationGroup setValue:@"group" forKey:@"name"];
animationGroup.animations = \
@[waveAnimationStart,waveAnimation1,waveAnimation2,waveAnimationLow];
animationGroup.duration = waveAnimationLow.beginTime + waveAnimationLow.duration;
animationGroup.fillMode = kCAFillModeForwards;
animationGroup.removedOnCompletion = NO;
[layer addAnimation:animationGroup forKey:nil];
self.allAnimationDuration = animationGroup.duration;
}
接下來(lái)就是最實(shí)際的效果,讓進(jìn)度圓圈同步下載的進(jìn)度,由于是逆時(shí)針?biāo)栽O(shè)置strokeStart
-(void)setProgress:(CGFloat)progress {
_progress = progress;
self.progressShapeLayer.strokeStart = 1-progress;
if (progress >= 1) {
[self end];
}
}
復(fù)位
最后全部事情完成,如果想恢復(fù)到一開(kāi)始的狀態(tài)
1、進(jìn)度條消失
進(jìn)度條我這里并不是讓他真的消失,而是讓他寬度變?yōu)?
2、圓點(diǎn)變?yōu)樨Q線
3、箭頭出現(xiàn)
4、移除波浪
/**
復(fù)位
*/
-(void)reset {
//變更狀態(tài)
self.state = AIDownloadButtonNone;
[self.pointShapeLayer removeAllAnimations];
[self scaleAnimationWithLayer:self.progressLabel.layer fromValue:1. toValue:.1];
[self opacityAnimationWithLayer:self.progressLabel.layer fromValue:1. toValue:0];
self.progressShapeLayer.strokeStart = 1;
self.progress = 0.;
self.state = AIDownloadButtonNone;
//進(jìn)度消失
POPBasicAnimation *progressAnimation = [POPBasicAnimation animationWithPropertyNamed:kPOPShapeLayerLineWidth];
progressAnimation.toValue = @0.;
progressAnimation.duration = .3;
[self.progressShapeLayer pop_addAnimation:progressAnimation forKey:nil];
//點(diǎn)變成豎線
UIBezierPath *pointPath = [UIBezierPath bezierPath];
[pointPath moveToPoint: CGPointMake(self.ai_middleX, self.ai_height *0.25)];
[pointPath addLineToPoint: CGPointMake(self.ai_middleX, self.ai_height *0.75 - self.arrowShapeLayer.lineWidth)];
CABasicAnimation *pointToLineAnimation = [CABasicAnimation animationWithKeyPath:@"path"];
pointToLineAnimation.toValue = (__bridge id _Nullable)(pointPath.CGPath);
pointToLineAnimation.duration = .3;
pointToLineAnimation.removedOnCompletion = NO;
pointToLineAnimation.fillMode = kCAFillModeForwards;
[self.pointShapeLayer addAnimation:pointToLineAnimation forKey:nil];
//移除波浪
[self.waveLayer removeFromSuperlayer];
//箭頭
self.arrowShapeLayer.opacity = 1.;
UIBezierPath *arrowPath = [UIBezierPath bezierPath];
[arrowPath moveToPoint: CGPointMake(self.ai_middleX * .75, self.ai_height *(0.25 + .5 * 0.6))];
[arrowPath addLineToPoint: CGPointMake(self.ai_middleX, self.ai_height *0.75)];
[arrowPath addLineToPoint: CGPointMake(self.ai_middleX * 1.25, self.ai_height *(0.25 + .5 * 0.6))];
CABasicAnimation *arrowAnimation = [CABasicAnimation animationWithKeyPath:@"path"];
arrowAnimation.toValue = (__bridge id _Nullable)(arrowPath.CGPath);
arrowAnimation.duration = .3;
arrowAnimation.removedOnCompletion = NO;
arrowAnimation.fillMode = kCAFillModeForwards;
[self.arrowShapeLayer addAnimation:arrowAnimation forKey:nil];
}
如果對(duì)基礎(chǔ)動(dòng)畫(huà)使用有不太明白的可以看下我另一篇文章登錄動(dòng)畫(huà)也可以在GitHub上查看源碼,你的star是我最大的支持