iOS 折線圖動畫
最近做了一個小demo,裡面需要動態繪製一個折線圖,研究了一下用CABasicAnimation實現了。demo在這,歡迎大神指點。
1.使用步驟
我簡單封裝了一下,現在完全可以當一個簡單的view來使用。
首先,建立view:
AFView *af_view = [[AFView alloc] initWithFrame:CGRectMake(20, 50, 280, 280)];
然後設定圖示的x、y軸最大值及最小值:
[af_view setMin_X:1]; [af_view setMax_X:7]; [af_view setMin_Y:0]; [af_view setMax_Y:100];
然後設定x、y軸座標值:
[af_view setX_labels:@[@"1",@"2",@"3",@"4",@"5",@"6",@"7"]];
[af_view setY_labels:@[@"0",@"20",@"40",@"60",@"80",@"100"]];
設定折點座標:
NSArray *data = [NSArray arrayWithObjects: [NSValue valueWithCGPoint:CGPointMake(1, 40)], [NSValue valueWithCGPoint:CGPointMake(2, 0)], [NSValue valueWithCGPoint:CGPointMake(3, 30)], [NSValue valueWithCGPoint:CGPointMake(4, 0)], [NSValue valueWithCGPoint:CGPointMake(5, 100)], [NSValue valueWithCGPoint:CGPointMake(6, 0)], [NSValue valueWithCGPoint:CGPointMake(7, 100)], nil]; [af_view setData:data];
把view新增到controller即可:
[self.view addSubview:af_view];
最後繪製好的圖如圖1所示:
圖1
繪製的過程是先繪製座標軸,再依次繪製各折點間的連線。
2.實現方法
簡單寫一下實現方法,全做拋磚引玉,還望大神指點。
座標軸實現方法:
CGFloat num = ((CGFloat)x_labels.count)-1.0; CGFloat x_interval = 0.8*w/num; UIView *v = [[UIView alloc] initWithFrame:CGRectMake(0, 0.9*h, w, 0.1*h)]; v.alpha = 0; for (int i=0; i<x_labels.count; i++) { NSString *str = x_labels[i]; UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(0.025*w+i*x_interval, 0, 0.2*w, 0.1*h)]; label.backgroundColor = [UIColor clearColor]; label.textAlignment = NSTextAlignmentCenter; label.font = [UIFont systemFontOfSize:w/28.0]; label.text = str; [v addSubview:label]; } [self addSubview:v]; [UIView animateWithDuration:1.5f animations:^{ v.alpha = 1; }];
這裡用的是最基本的UIView動畫,透明度從0變至1,比較簡單。
折線實現方法:
CGFloat startTime = 2.0f;
for (int i=1; i<data.count; i++)
{
CGPoint s = [data[i-1] CGPointValue];
CGPoint e = [data[i] CGPointValue];
s.x = (s.x - min_X)*0.8*w/(max_X - min_X) + 0.125*w;
s.y = 0.875*h - (s.y - min_Y)*0.8*h/(max_Y - min_Y);
e.x = (e.x - min_X)*0.8*w/(max_X - min_X) + 0.125*w;
e.y = 0.875*h - (e.y - min_Y)*0.8*h/(max_Y - min_Y);//計算原始座標對應折線圖上的繪製座標。
AFDescribeObj *desObj = [[AFDescribeObj alloc] initWithType:AFAnimationStrokeEnd inView:self];
[desObj setStartTime:startTime andDuration:0.5f];
[desObj addLineFrom:s toPoint:e color:[UIColor whiteColor]];
startTime += 0.5f;
}
這裡用到了我自己封裝的一個動畫物件AFDescribeObj。這個類其實很簡單,主要就是兩個:
(1)建立CABasicAnimation物件
- (void)setStartTime:(CGFloat)af_startTime andDuration:(CGFloat)af_duration
{
startTime = af_startTime;
duration = af_duration;
basicAnimation = [CABasicAnimation animationWithKeyPath:types[type]];
basicAnimation.delegate = containerView;
basicAnimation.duration = af_duration+af_startTime;
basicAnimation.fromValue = [NSNumber numberWithFloat:-af_startTime/af_duration];
basicAnimation.toValue = [NSNumber numberWithFloat:1.0];
}
這裡的fromValue,toValue,byValue我是試了半天才明白。官方文件上的介紹是這個:
/* The objects defining the property values being interpolated between.
* All are optional, and no more than two should be non-nil. The object
* type should match the type of the property being animated (using the
* standard rules described in CALayer.h). The supported modes of
* animation are:
*
* - both `fromValue' and `toValue' non-nil. Interpolates between
* `fromValue' and `toValue'.
*
* - `fromValue' and `byValue' non-nil. Interpolates between
* `fromValue' and `fromValue' plus `byValue'.
*
* - `byValue' and `toValue' non-nil. Interpolates between `toValue'
* minus `byValue' and `toValue'.
*
* - `fromValue' non-nil. Interpolates between `fromValue' and the
* current presentation value of the property.
*
* - `toValue' non-nil. Interpolates between the layer's current value
* of the property in the render tree and `toValue'.
*
* - `byValue' non-nil. Interpolates between the layer's current value
* of the property in the render tree and that plus `byValue'. */
我覺得似乎什麼也沒有表述明白……單就strokeEnd動畫而言,我的理解是這樣的,
duration--很直白,就是這個動畫經歷多久;
fromVlaue,byValue,toValue--各階段狀態值,但是這個狀態指的是一個百分比,比如說從(0,0)繪製一條直線至(100,100),如果from=0,to=1,那麼就是動畫一開始就從(0,0)開始繪製一條直線,至(100,100),經歷duration這麼久。如果from=1,to=0,那麼這條直線一開始就是存在的,然後會慢慢的從(100,100)消失,直到(0,0),不過動畫一旦結束,不管toValue設定的是多少,都會回到1,也就是100%的狀態。也就是說如果是from=0,to=0.5,你會看到先是從(0,0)慢慢畫一條線到(50,50),然後突然蹦到(100,100)。
所以我這裡利用這個實現了過一段時間再開始繪製動畫,也就是設定了一個startTime。
(2)畫線
- (void)addLineFrom:(CGPoint)from toPoint:(CGPoint)to color:(UIColor *)color
{
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:from];
[path addLineToPoint:to];
CAShapeLayer *pathLayer = [CAShapeLayer layer];
pathLayer.frame = containerView.bounds;
pathLayer.path = path.CGPath;
pathLayer.strokeColor = color.CGColor;
pathLayer.lineWidth = 2.0f;
pathLayer.lineJoin = kCALineJoinBevel;
[pathLayer addAnimation:basicAnimation forKey:types[type]];
NSMutableArray *ary = [NSMutableArray arrayWithArray:shapeLayers];
[ary addObject:pathLayer];
shapeLayers = [NSArray arrayWithArray:ary];
[containerView.layer addSublayer:pathLayer];
}