一個(gè)簡(jiǎn)易遮罩進(jìn)度動(dòng)畫(huà)

需求

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

image

紅色的填充為當(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),一切一目了然

image

簡(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ì)maskViewlayermaskLayer層進(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):

  1. 設(shè)定超時(shí)機(jī)制
  2. 暫停動(dòng)畫(huà)
  3. 恢復(fù)動(dòng)畫(huà)
  4. 從任意進(jìn)度值初始化動(dòng)畫(huà)
  5. 從當(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地址 如果有幫助送顆星吧

demo

不足

  • 進(jìn)度控制部分的超時(shí)跟maskLayer位置的對(duì)應(yīng)關(guān)系比較生硬

個(gè)人對(duì)動(dòng)畫(huà)理解還不深,代碼笨拙。還請(qǐng)朋友多批評(píng)指教!

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

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

  • 1.自定義控件 a.繼承某個(gè)控件 b.重寫(xiě)initWithFrame方法可以設(shè)置一些它的屬性 c.在layouts...
    圍繞的城閱讀 3,694評(píng)論 2 4
  • 在iOS實(shí)際開(kāi)發(fā)中常用的動(dòng)畫(huà)無(wú)非是以下四種:UIView動(dòng)畫(huà),核心動(dòng)畫(huà),幀動(dòng)畫(huà),自定義轉(zhuǎn)場(chǎng)動(dòng)畫(huà)。 1.UIView...
    請(qǐng)叫我周小帥閱讀 3,313評(píng)論 1 23
  • 最近在做iOS界面轉(zhuǎn)場(chǎng)的動(dòng)畫(huà),寫(xiě)完轉(zhuǎn)場(chǎng)入口后基本元素還是回歸到我們常用的基本動(dòng)畫(huà)代碼,有關(guān)動(dòng)畫(huà)的帖子網(wǎng)絡(luò)上一搜一大...
    大雄記閱讀 5,634評(píng)論 0 18
  • 注意一個(gè)原理:html是根據(jù)標(biāo)簽去找相應(yīng)的內(nèi)容的,這就是DOM的意義,(用不用標(biāo)簽都可以實(shí)現(xiàn))為什么要用這個(gè)標(biāo)簽,...
    聆聽(tīng)者JYZ閱讀 372評(píng)論 0 0
  • 一、先建立工程目錄 在新建的工程目錄下,新建 src 和 dist 文件夾。 src:書(shū)寫(xiě)的ES6代碼文件都放在此...
    大青吶閱讀 782評(píng)論 0 0

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