需求
近期工作需求一個(gè)展示任務(wù)進(jìn)度的動(dòng)畫(huà),通過(guò)填充現(xiàn)有進(jìn)度圖的方式展示,并且有里程碑的節(jié)點(diǎn)需要配合數(shù)據(jù)進(jìn)行暫停。好吧,文字講得我自己都很難理解,來(lái)看一下效果吧。

紅色的填充為當(dāng)前的進(jìn)度,驗(yàn)證登錄階段到導(dǎo)入賬單階段(導(dǎo)入階段到完成)是有一個(gè)里程碑的節(jié)點(diǎn)的,所以需要通過(guò)數(shù)據(jù)來(lái)控制進(jìn)度,讓紅色的進(jìn)度條暫停在某一個(gè)位置等待數(shù)據(jù)。
方案思考
1.動(dòng)畫(huà)部分
展示一下層次結(jié)構(gòu),一切一目了然

簡(jiǎn)單的來(lái)講:其實(shí)是有兩個(gè)進(jìn)度控制View,分別是灰色和紅色,這里稱(chēng)紅色的為maskView,灰色為bottomView。
在結(jié)構(gòu)層次圖中可以意識(shí)到只要讓maskView有個(gè)位移動(dòng)畫(huà)就可以滿足我們的需求。
注意:這個(gè)位移動(dòng)畫(huà)并不是UIView層面的動(dòng)畫(huà),單純移動(dòng)整個(gè)maskView其實(shí)是會(huì)造成bottomView和maskView圖標(biāo)錯(cuò)位的現(xiàn)象,這并不是想要的效果。
正確的是對(duì)maskViewlayer的maskLayer層進(jìn)行動(dòng)畫(huà)操作。
實(shí)現(xiàn)思路:讓maskView的maskLayer初始位置在maskView的frame之外的左側(cè)。動(dòng)畫(huà)開(kāi)始之后讓maskLayer慢慢向右移動(dòng),達(dá)到紅色慢慢向右填充的效果。對(duì)maskLayer使用的動(dòng)畫(huà)可以是CABasicAnimation 或者CAKeyframeAnimation。此demo中使用關(guān)鍵幀動(dòng)畫(huà) CAKeyframeAnimation
關(guān)鍵代碼示例:
//初始化maksView的maskLayer位置
CALayer *maskLayer; = [CALayer layer];
maskLayer.backgroundColor = [[UIColor whiteColor] CGColor]; //任何顏色,用到的只是alpha
maskLayer.anchorPoint = CGPointZero;
maskLayer.frame = CGRectOffset(self.frame,-CGRectGetWidth(self.frame), 0);
self.layer.mask = maskLayer;
//開(kāi)始動(dòng)畫(huà)
NSArray *values = @[[NSValue valueWithCGPoint:CGPointMake(-CGRectGetWidth(self.frame), 0)],
[NSValue valueWithCGPoint:CGPointMake(0, 0)]];
CGFloat duration = 4.0;
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
animation.values = values;
animation.duration = duration;
animation.delegate = self;
animation.fillMode = kCAFillModeForwards;
animation.removedOnCompletion = NO;
[maskLayer addAnimation:animation forKey:@"MaskAnimation"];
2.進(jìn)度控制部分
如果只需要本文剛開(kāi)始的貼圖那樣的效果,第一部分已經(jīng)足夠了。
第一部分只是解決了生成動(dòng)畫(huà)的問(wèn)題,使用CAKeyframeAnimation對(duì)maskLayer進(jìn)行關(guān)鍵幀動(dòng)畫(huà)是比較常見(jiàn)的做法,如果對(duì)CAKeyframeAnimation或者CABasicAnimation使用不熟悉可以參閱《iOS核心動(dòng)畫(huà)》緩沖
接下來(lái)要完成的是根據(jù)數(shù)據(jù)狀態(tài)來(lái)控制動(dòng)畫(huà),簡(jiǎn)單梳理了下我們要完成的目標(biāo):
| 狀態(tài) | 動(dòng)畫(huà)描述 |
|---|---|
| 登錄中 | 紅色maskLayer開(kāi)始向右移動(dòng) |
| 登錄超時(shí) | 紅色maskLayer移動(dòng)暫停在1,2兩個(gè)圖標(biāo)之間的進(jìn)度條間 |
| 登錄成功 | 紅色maskLayer繼續(xù)向右移動(dòng) |
| 導(dǎo)入中 | 紅色maskLayer繼續(xù)向右移動(dòng)到2至3部分 |
| 導(dǎo)入超時(shí) | 紅色maskLayer移動(dòng)暫停在2,3兩個(gè)圖標(biāo)之間的進(jìn)度條間 |
| 導(dǎo)入完成 | 紅色maskLayer繼續(xù)完成剩下的動(dòng)畫(huà) |
| 提前完成 | 直接從當(dāng)前的百分比位置快速完成剩下的動(dòng)畫(huà) |
| 恢復(fù)進(jìn)度 | 從任意進(jìn)度恢復(fù)動(dòng)畫(huà) |
拋開(kāi)是數(shù)據(jù),我們要完成的是以下五點(diǎn):
- 設(shè)定超時(shí)機(jī)制
- 暫停動(dòng)畫(huà)
- 恢復(fù)動(dòng)畫(huà)
- 從任意進(jìn)度值初始化動(dòng)畫(huà)
- 從當(dāng)前進(jìn)度值快速完成動(dòng)畫(huà)
2.1 設(shè)定超時(shí)機(jī)制
這一步其實(shí)比較簡(jiǎn)單
先設(shè)定默認(rèn)的登錄超時(shí)時(shí)間和導(dǎo)入超時(shí)時(shí)間
static const CGFloat kGeneralLoginTime = 20.0;
static const CGFloat kGeneralImportTime = 30.0;
在視圖中創(chuàng)建一個(gè)定時(shí)器,從動(dòng)畫(huà)開(kāi)始計(jì)算經(jīng)過(guò)的時(shí)間,與默認(rèn)的時(shí)間判斷是否在該狀態(tài)已經(jīng)超時(shí)。
- (void)displayLinkEvent {
_currentTime ++;
if (_currentTime == kGeneralLoginTime * 60 && self.status == Logining) {
[self waitForVerify];
NSLog(@"登錄超時(shí) %ld", _currentTime / 60);
} else if (_currentTime - _loginSuccessTime == kGeneralImportTime * 60 && self.status == Importing) {
[self waitForImport];
NSLog(@"導(dǎo)入超時(shí) %ld", (_currentTime - _loginSuccessTime)/60);
}
_lastPresentPosition = _maskLayer.presentationLayer.position;
}
2.2 暫停動(dòng)畫(huà)
//暫停動(dòng)畫(huà)
- (void)pauseLayer {
CFTimeInterval pausedTime = [_maskLayer convertTime:CACurrentMediaTime() fromLayer:nil];
self.layer.speed = 0.0;
self.layer.timeOffset = pausedTime;
}
2.3 恢復(fù)動(dòng)畫(huà)
//恢復(fù)動(dòng)畫(huà)
- (void)resumeLayer {
CFTimeInterval pausedTime = [self.layer timeOffset];
self.layer.speed = 1.0;
self.layer.timeOffset = 0.0;
self.layer.beginTime = 0.0;
CFTimeInterval timeSincePause = [_maskLayer convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime;
self.layer.beginTime = timeSincePause;
}
2.4 從任意進(jìn)度值初始化
//設(shè)置一個(gè)startVlaue的初始化值
- (void)setStartValue:(CGFloat)startValue {
[self stopAnimation];
_startValue = startValue;
//_lastPresentPosition為maskLayer上一次展示position屬性
_lastPresentPosition = CGPointMake(startValue * CGRectGetWidth(self.frame) - CGRectGetWidth(self.frame), 0);
[self resumeLayer];
[self startAnimation];
}
//從上一次動(dòng)畫(huà)
- (void)startAnimation {
NSArray *values = @[[NSValue valueWithCGPoint:_lastPresentPosition],
[NSValue valueWithCGPoint:CGPointMake(0, 0)]];
CGFloat duration = kGeneralLoginTime + kGeneralImportTime + 1;
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
animation.values = values;
animation.duration = duration;
animation.delegate = self;
//自然導(dǎo)入完成之后不保留mask層的位置信息,回歸到初始位置
animation.fillMode = kCAFillModeRemoved;
animation.removedOnCompletion = YES;
[_maskLayer addAnimation:animation forKey:@"MaskAnimation"];
_displayLink.paused = NO;
}
2.5 從當(dāng)前進(jìn)度值快速完成動(dòng)畫(huà)
// 提前結(jié)束動(dòng)畫(huà)
- (void)animationAdvancedFinished
{
[self resumeLayer];
CGFloat duration = _advancedFinishDuring;
NSArray *values = @[[NSValue valueWithCGPoint:_lastPresentPosition],
[NSValue valueWithCGPoint:CGPointMake(0, 0)]];
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
animation.values = values;
animation.duration = duration;
animation.delegate = self;
//提前結(jié)束動(dòng)畫(huà)完成之后保留mask層的位置信息,保持動(dòng)畫(huà)完成時(shí)的樣式
animation.fillMode = kCAFillModeForwards;
animation.removedOnCompletion = NO;
[_maskLayer addAnimation:animation forKey:@"AdvancedMaskAnimation"];
}
上面代碼片段中的_advancedFinishDuring動(dòng)畫(huà)時(shí)間,是根據(jù)觸發(fā)快速完成動(dòng)畫(huà)時(shí)的進(jìn)度值百分比 乘以 快速完成動(dòng)畫(huà)所需要的總時(shí)間計(jì)算出來(lái)的。下面是計(jì)算的一個(gè)例子:
- (void)verifySuccess {
[self resumeLayer];
self.status = Importing;
self.displayLink.paused = NO;
if (!self.loginSuccessTime) {
self.loginSuccessTime = self.currentTime;
}
self.advancedFinishDuring = ((-_lastPresentPosition.x) / CGRectGetWidth(self.frame)) * kGeneralCacheCompleteTime;
}
至此進(jìn)度控制就可以的需求就可以使用以上的關(guān)鍵方法完成了, 查看完成的邏輯可以下載demo查看
Demo地址 如果有幫助送顆星吧

不足
- 進(jìn)度控制部分的超時(shí)跟maskLayer位置的對(duì)應(yīng)關(guān)系比較生硬
個(gè)人對(duì)動(dòng)畫(huà)理解還不深,代碼笨拙。還請(qǐng)朋友多批評(píng)指教!