iOS自定義動畫-仿支付寶記賬本

CALayer大部分屬性都可以添加CAAnimation動畫,動畫添加到layer上之后就會自動開始執(zhí)行,但這僅限于CALayer及其子類已有的屬性,如果是自己添加的屬性,是不會自動產(chǎn)生動畫的,如果需要動畫效果,就需要自定義動畫了。

如何創(chuàng)建自定義動畫?

大體來說有兩種方法,一種是使用系統(tǒng)的繪制方法畫出動畫,一種是利用定時器自己繪制動畫。

方法一

1、創(chuàng)建自定義Layer類繼承自CAShapeLayer
2、給Layer添加需要執(zhí)行動畫的屬性,在.m文件中使用@dynamic聲明動畫屬性
3、重寫幾個系統(tǒng)方法:

// 告訴CALayer自定義屬性需要進行重繪
+ (BOOL)needsDisplayForKey:(NSString *)key {
    if ([key isEqualToString:@"startAngle"]||[key isEqualToString:@"endAngle"]) {
        return YES;
    }
    return [super needsDisplayForKey:key];
}
// 如果自定義屬性值改變,就生成動畫,系統(tǒng)會自動調(diào)用display方法重繪
- (id<CAAction>)actionForKey:(NSString *)event {
    if ([event isEqualToString:@"startAngle"])
    {
        CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:event];
        anim.duration = 1;
        anim.fromValue = @([self.presentationLayer startAngle]);
        anim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault];
        return anim;
    } else if ([event isEqualToString:@"endAngle"]) {
        CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:event];
        anim.duration = 1;
        anim.fromValue = @([self.presentationLayer endAngle]);
        anim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault];
        return anim;
    }
    return [super actionForKey:event];
}

重寫上面兩個方法之后,在外部修改動畫屬性時就會觸發(fā)界面重繪。系統(tǒng)會優(yōu)先調(diào)用-display方法進行重繪,如果沒有重寫這個方法,系統(tǒng)會調(diào)用-drawInContext:進行重繪。要注意:

不在-drawInContext中繪圖的時候,要在繪圖代碼前后加上:
UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, 0);
image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
在-drawInContext中繪制的時候不需要寫。

繪制代碼:
- (void)drawInContext:(CGContextRef)ctx {
CGPoint center = CGPointMake(self.bounds.size.width / 2, self.bounds.size.height / 2);
CGFloat presentStartAngle = [[self.presentationLayer valueForKey:@"startAngle"] doubleValue];
CGFloat presentEndAngle = [[self.presentationLayer valueForKey:@"endAngle"] doubleValue];
CGContextSetLineWidth(ctx, 40);
CGContextSetStrokeColorWithColor(ctx, [UIColor purpleColor].CGColor);
CGContextAddArc(ctx, center.x, center.y, 100, presentStartAngle, presentEndAngle, 0);
CGContextStrokePath(ctx);
}

- (void)display {
    CGPoint center = CGPointMake(self.bounds.size.width / 2, self.bounds.size.height / 2);
    CGFloat presentStartAngle = [[self.presentationLayer valueForKey:@"startAngle"] doubleValue];
    CGFloat presentEndAngle = [[self.presentationLayer valueForKey:@"endAngle"] doubleValue];

    UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, 0);
    CGContextRef ctx = UIGraphicsGetCurrentContext();

    CGContextSetLineWidth(ctx, 40);
    [self.color setStroke];
    CGContextAddArc(ctx, center.x, center.y, 100, presentStartAngle, presentEndAngle, 0);
    CGContextStrokePath(ctx);

    self.contents = (id)UIGraphicsGetImageFromCurrentImageContext().CGImage;
    UIGraphicsEndImageContext();
}

創(chuàng)建這個Layer對象的時候,需要給它設(shè)置frame,如果不設(shè)置frame,會出現(xiàn)繪制失敗。所以最好用-initWithFrame:方法進行創(chuàng)建。

方法二

理論上講重繪方法每秒會被調(diào)用60次,但真機實測發(fā)現(xiàn)只有50多次,并且調(diào)用次數(shù)會隨著重繪代碼的增多而減少,這會造成動畫幀數(shù)下降,使動畫看起來不夠流暢。

第二種方法是創(chuàng)建定時器,每秒觸發(fā)60次,每次觸發(fā)都進行重繪,步驟:
1、創(chuàng)建Layer類繼承自CAShapeLayer,重寫-initWithLayer:方法
2、創(chuàng)建UIView類用來放置Layer,在view被添加到父視圖之后給它上面的Layer創(chuàng)建動畫
3、實現(xiàn)動畫代理方法,動畫開始時開啟定時器,定時觸發(fā)重繪
4、動畫屬性的值會隨動畫的執(zhí)行不斷變化,定時器觸發(fā)時獲取當前動畫值,畫出當前的位置

重寫-initWithLayer:
- (instancetype)initWithLayer:(id)layer {
if (self = [super initWithLayer:layer]) {
if ([layer isKindOfClass:[CircleLayer class]]) {
self.startAngle = [(CircleLayer *)layer startAngle];
self.endAngle = [(CircleLayer *)layer endAngle];
}
}
return self;
}
CAAnimation生成關(guān)鍵幀是通過拷貝CALayer進行的,在拷貝時,只能拷貝原有的(系統(tǒng)的,非自定義的)屬性,不能拷貝自定義的屬性或持有的對象等等,因此需要重載initWithLayer來手動拷貝我們需要拷貝的東西。

創(chuàng)建動畫:
- (void)createAnimationWithKeyPath:(NSString *)key fromValue:(NSNumber *)from toValue:(NSNumber *)to func:(NSString *)func layer:(CALayer *)layer {
CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:key];
NSNumber *currentAngle = [layer.presentationLayer valueForKey:key];
if (!currentAngle) {
currentAngle = from;
}
anim.fromValue = currentAngle;
anim.toValue = to;
anim.delegate = self;
anim.timingFunction = [CAMediaTimingFunction functionWithName:func];
[layer addAnimation:anim forKey:key];
// 設(shè)置結(jié)束值,這樣動畫結(jié)束之后就會停留在結(jié)束位置,而不會返回初始位置,一定要在添加動畫之后設(shè)置
[layer setValue:to forKey:key];
}

給動畫設(shè)置初始值和結(jié)束值,設(shè)置好動畫執(zhí)行方式,它就會在“暗地里”執(zhí)行,執(zhí)行期間可以通過layer.presentationLayer獲取當前動畫執(zhí)行到的位置,獲取之后就可以繪制layer了。這樣每秒繪制60次就形成了流暢的動畫效果。


支付寶的記賬本里有一個非??犰诺淖远x控件,現(xiàn)在就模仿一下它的動畫效果:

仿支付寶記賬本.gif

代碼如下:
- (instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
_animations = [[NSMutableArray alloc] init];
_pieCenter = CGPointMake(frame.size.width/2, frame.size.height/2);
_animationDuration = 3;
_startPieAngle = 0;
_pieLineWidth = 40;
_pieRadius = MIN(frame.size.width/2 - _pieLineWidth, frame.size.width/2 - _pieLineWidth);
_selectedIndex = -1;
_selectedOffsetRadius = 7.0;
}
return self;
}

- (void)reloadData {
    [CATransaction begin];
    [CATransaction setAnimationDuration:_animationDuration];

    CGFloat p = 2 * M_PI;
    NSArray *end = @[@(p/5),@(p/4),@(p/3),@(p/2),@(p/1)];
    NSArray *start = @[@(0),@(p/5),@(p/4),@(p/3),@(p/2)];

    for (int i = 0; i < 5; i ++) {
        CircleLayer *layer = [CircleLayer layer];
        [self.layer addSublayer:layer];
        CGFloat startAngle = [start[i] doubleValue];
        CGFloat endAngle = [end[i] doubleValue];
        layer.startAngle = startAngle;
        layer.endAngle = endAngle;
        layer.lineWidth = 30;
        layer.fillColor = [UIColor clearColor].CGColor;
        layer.strokeColor = [UIColor colorWithHue:((i/8)%20)/20.0+0.02 saturation:(i%8+3)/10.0 brightness:91/100.0 alpha:1].CGColor;
        [self createAnimationWithKeyPath:@"startAngle" fromValue:@0 toValue:@(startAngle) layer:layer];
        [self createAnimationWithKeyPath:@"endAngle" fromValue:@0 toValue:@(endAngle) layer:layer];
    }

    [CATransaction commit];
}

- (void)createAnimationWithKeyPath:(NSString *)key fromValue:(NSNumber *)from toValue:(NSNumber *)to layer:(CALayer *)layer {
    CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:key];
    NSNumber *currentAngle = [layer.presentationLayer valueForKey:key];
    if (!currentAngle) {
        currentAngle = from;
    }
    anim.fromValue = currentAngle;
    anim.toValue = to;
    anim.delegate = self;
    anim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
    [layer addAnimation:anim forKey:key];
    [layer setValue:to forKey:key];
}

- (void)animationDidStart:(CAAnimation *)anim {
    if (!_animationTimer) {
        static float timeInterval = 1.0/60.0;
        _animationTimer= [NSTimer timerWithTimeInterval:timeInterval target:self selector:@selector(timerFired) userInfo:nil repeats:YES];
        [[NSRunLoop mainRunLoop] addTimer:_animationTimer forMode:NSRunLoopCommonModes];
    }
    [_animations addObject:anim];
}

- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {
    [_animations removeObject:anim];
    if (_animations.count == 0) {
        [_animationTimer invalidate];
        _animationTimer = nil;
    }
}

- (void)timerFired {
    NSArray *sliceLayerArray = self.layer.sublayers;
    [sliceLayerArray enumerateObjectsUsingBlock:^(CircleLayer *layer, NSUInteger idx, BOOL *stop) {
        CGFloat currentStartAngle = [[layer.presentationLayer valueForKey:@"startAngle"] doubleValue];
        CGFloat currentEndAngle = [[layer.presentationLayer valueForKey:@"endAngle"] doubleValue];
    
        UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:_pieCenter radius:_pieRadius startAngle:currentStartAngle endAngle:currentEndAngle clockwise:1];
        layer.path = path.CGPath;
    }];
}

添加到ViewController上之后,調(diào)用reloadData就會開始動畫。

仿寫的支付寶記賬本控件效果如下:

6.gif

完整demo請參考我的GitHub。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,748評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,165評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 175,595評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,633評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,435評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,943評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,035評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,175評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,713評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,599評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,788評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,303評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,034評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,412評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,664評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,408評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,747評論 2 370

推薦閱讀更多精彩內(nèi)容

  • 在iOS中隨處都可以看到絢麗的動畫效果,實現(xiàn)這些動畫的過程并不復雜,今天將帶大家一窺ios動畫全貌。在這里你可以看...
    每天刷兩次牙閱讀 8,537評論 6 30
  • 在iOS中隨處都可以看到絢麗的動畫效果,實現(xiàn)這些動畫的過程并不復雜,今天將帶大家一窺iOS動畫全貌。在這里你可以看...
    F麥子閱讀 5,130評論 5 13
  • 轉(zhuǎn)載:http://www.lxweimin.com/p/32fcadd12108 每個UIView有一個伙伴稱為l...
    F麥子閱讀 6,255評論 0 13
  • 每個UIView有一個伙伴稱為layer,一個CALayer。UIView實際上并沒有把自己畫到屏幕上;它繪制本身...
    shenzhenboy閱讀 3,131評論 0 17
  • 上周與同學們的同題作文賽,沒有來得及與大家分享,現(xiàn)在終于抽出一點兒時間了。 上課鈴響了,首先主動分享習作的是肖立峰...
    石榴slr閱讀 774評論 0 3