前言
许久不更博客,心痒难耐。
正好Quartz内容也有些忘却,索性写个绘图动画熟熟手。
之前见过一下扇形加载动画,具体哪个应用已经不记得,当时觉得很有意思,正好在这里实现一番。大概效果如下:
思路与算法
从图中不难看出,整个动画每转一圈会前进一部分,循环往复。
将每次旋转单独拆分的话,又分为三个部分:
- 扇形扩展
- 整体前进
- 扇形收缩
于是我们可以得出:整个扇形为一个以时间为未知数的周期函数,同时也是一个分段函数。
我们可以将圆分为12格,单位时间前进一格,每次旋转后整体前进1格,则循环长度为13格。
设扇形整体为8格,则循环一周需要整体前进12-8=4格,那么一次循环就需要整体前进13-8=5格。于是,一次循环总格数为8+5+8=21格。
那么,分段函数的两个节点就是8(扇形长度),13(循环长度)。
代码实现
创建一个包含时间属性的CALayer子类。时间需要定义为双精度或浮点型,若定义整型会造成断断续续的感觉。
@interface IPZArcLayer : CALayer
@property CGFloat time;
@end
实现动画过程
#import "IPZArcLayer.h"
#define IPZOriginAngle -M_PI_2
@implementation IPZArcLayer
@dynamic time;
+(BOOL)needsDisplayForKey:(NSString *)key{
if ([@"time" isEqualToString:key]) {
return true;
}
return [super needsDisplayForKey:key];
}
- (id<CAAction>)actionForKey:(NSString *)key {
if ([key isEqualToString:@"time"]) {
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:key];
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
animation.fromValue = @([[self presentationLayer] time]);
return animation;
}
return [super actionForKey:key];
}
-(void)drawInContext:(CGContextRef)ctx{
CGFloat time = [[self presentationLayer] time];
CGFloat originAngle=IPZOriginAngle+ (int)(time/21)*M_PI*2/12;
CGFloat startAngle=0;
CGFloat endAngle=0;
CGFloat period=[self getYushuByA:time andB:21];
if (period <8 ) {
startAngle=originAngle;
endAngle=originAngle+period*2*M_PI/12;
}else if (period<13){
startAngle=originAngle+[self getYushuByA:period andB:8]*2*M_PI/12;
endAngle=originAngle+period*2*M_PI/12;
}else{
startAngle=originAngle+([self getYushuByA:period andB:13]+5)*2*M_PI/12;
endAngle=originAngle+M_PI*2/12;
}
CGContextSetRGBFillColor(ctx, 0/255.0, 165/255.0, 221/255.0, 1);
CGContextBeginPath(ctx);
CGContextMoveToPoint(ctx, CGRectGetMidX(self.bounds) , CGRectGetMidY(self.bounds));
CGContextAddArc(ctx, CGRectGetMidX(self.bounds) , CGRectGetMidY(self.bounds) , 50, startAngle,endAngle , 0);
CGContextClosePath(ctx);
CGContextFillPath(ctx);
}
///双精度取余数
- (CGFloat)getYushuByA:(CGFloat)beichushu andB:(int)chushu{
int bs=(int)(beichushu)/chushu;
CGFloat yushu=beichushu-bs*chushu;
return yushu;
}
@end
在UIView中添加IPZArcLayer,并通过间断赋值实现动画效果。
-(void)awakeFromNib{
[super awakeFromNib];
IPZArcLayer *layer=[IPZArcLayer new];
layer.frame=CGRectMake(0, 0, 200, 200);
[self.layer addSublayer:layer];
_arcLayer=layer;
[NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(updateProgress:) userInfo:nil repeats:true];
}
- (void)updateProgress:(id)sender{
_time++;
_arcLayer.time=_time;
}