最近比较忙碌,有一段时间没有更新自己的博客了。这一节为大家介绍的动画特效是: 仿支付宝转账动画。 先看看最终效果图。
处理这种多变的动画,我们的分析思路就是各个击破。在这里,我将这个动画分解为三组进行处理:
1. 一小段弧线的运动,在效果图中转了3圈。
2. 绘制一个完整的圆,在效果图中转了1圈。
3. 白色的打钩动画。
关于第2点,我在之前的博客中已经进行了详细的说明工作,我就不做解释了,如果大家感兴趣的话,请参照:CALayer的needsDisplayForKey方法使用说明 这篇博客。
现在我们来分析第一点:
我们知道,绘制圆弧的方法如下:
CGContextAddArc(CGContextRef __nullable c, CGFloat x, CGFloat y, CGFloat radius, CGFloat startAngle, CGFloat endAngle, int clockwise)
第一个参数是绘制的上下文。
第二,三个参数是圆心。
第四个参数是圆的半径。
第五、六个参数是绘制圆的起始角度和终止角度。
第六个参数是决定绘制方向(逆时针还是顺时针)。
我们这里所关注的是 startAngle和endAngle, 绘制的起始点是正上方,所以刚开始的时候,startAngle和endAngle均为 -90°。
注意观察可以看出,startAngle在前半圆的运动过程中比 endAngle块,所以在这个过程中,弧线的长度越来越大。startAngle在后半圆的运动过程中比 endAngle慢,所以endAngle会慢慢赶上startAngle, 当绘制完整个圆的时候,两者又在正上方那点重合了。分析图如下:
有了这一层分析,就可以紧接着分析两个小球的运动函数了。假设红色小球在运动过程中是一直保持匀速状态。那么黄色小球在前半段的运动过程中速度相对而言慢一点,而在后半段的运动过程中速度相对而言快一点。体现在数学函数上面就是,前半段,黄色小球的直线斜率小于红色小球,而后半段,黄色小球的直线斜率大于红色小球。
函数分析示意图:
有了上面的分析步奏,我们就可以书写代码,进行绘制了。
- (void)drawInContext:(CGContextRef)ctx { CGContextSetLineWidth(ctx, 5.0f); CGContextSetLineCap(ctx, kCGLineCapRound); CGContextSetStrokeColorWithColor(ctx, [UIColor blueColor].CGColor); if (self.roundTwo) { CGContextAddArc(ctx, CGRectGetWidth(self.bounds) * 0.5, CGRectGetHeight(self.bounds) * 0.5, CGRectGetWidth(self.bounds) * 0.5 - 5, -M_PI_2, 3 * M_PI / 2 * self.progress, 0); } else { // 注意这里其实角度和终止角度的计算 CGFloat startAngle, endAngle; if (self.progress <= 0.5) { startAngle = 5 * M_PI / 3 * self.progress - M_PI_2; endAngle = 2 * M_PI * self.progress - M_PI_2; } else { startAngle = 7 * M_PI / 3 * self.progress - 5 * M_PI / 6; endAngle = 2 * M_PI * self.progress - M_PI_2; } CGContextAddArc(ctx, CGRectGetWidth(self.bounds) * 0.5, CGRectGetHeight(self.bounds) * 0.5, CGRectGetWidth(self.bounds) * 0.5 - 5, startAngle, endAngle, 0); } CGContextStrokePath(ctx); }
从图中的效果图,我们可以看出,打钩动画就是一段路径。只要我们实现准备好相关的路径信息,就可以方便的绘制出来了。分析图如下:
绘制打钩的代码如下:
- (void)startCheck { CGFloat viewHeight = self.frame.size.height; CGFloat viewWidth = self.frame.size.width; UIBezierPath *strokePath = [UIBezierPath bezierPath]; [strokePath moveToPoint:CGPointMake(viewWidth * 0.25, viewHeight * 0.65)]; [strokePath addLineToPoint:CGPointMake(viewWidth * 0.4, viewHeight * 0.75)]; [strokePath addLineToPoint:CGPointMake(viewWidth * 0.75, viewHeight * 0.25)]; CAShapeLayer *checkLayer = [CAShapeLayer layer]; checkLayer.frame = self.bounds; // 这样就可以相对于父layer计算path了。 checkLayer.strokeColor = [UIColor whiteColor].CGColor; checkLayer.fillColor = [UIColor clearColor].CGColor; checkLayer.lineWidth = 10; checkLayer.lineCap = kCALineCapRound; checkLayer.lineJoin = kCALineJoinRound; checkLayer.path = strokePath.CGPath; [self addSublayer:checkLayer]; CABasicAnimation *checkAnim = [CABasicAnimation animationWithKeyPath:@"strokeEnd"]; checkAnim.fromValue = @(0.0); checkAnim.toValue = @(1.0); checkAnim.duration = 1.0f; checkAnim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; checkAnim.delegate = self; [checkAnim setValue:@"check_animation" forKey:@"check_name"]; [checkLayer addAnimation:checkAnim forKey:nil]; }