1. 程式人生 > >iOS 折線圖動畫

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];
}