最近比较忙碌,有一段时间没有更新自己的博客了。这一节为大家介绍的动画特效是: 仿支付宝转账动画。 先看看最终效果图。
处理这种多变的动画,我们的分析思路就是各个击破。在这里,我将这个动画分解为三组进行处理:
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];
}
至此,这个动画效果就算完成了。