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ā)時獲取當(dāng)前動畫值,畫出當(dāng)前的位置

重寫-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獲取當(dāng)前動畫執(zhí)行到的位置,獲取之后就可以繪制layer了。這樣每秒繪制60次就形成了流暢的動畫效果。


支付寶的記賬本里有一個非常酷炫的自定義控件,現(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ù)。

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

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