美文网首页暂时没看却需要看的Animation程序员
CoreGraphics,CoreAnimation实战, 可交

CoreGraphics,CoreAnimation实战, 可交

作者: Syik | 来源:发表于2017-09-10 20:07 被阅读162次

    前言

    图表的绘制相信大家都用的很多, 也有现成的很好的框架, 但如果定制程度特别高, 特别是动画, 还是得自己来实现, 先看看准备实现的效果, 个人觉得还是有一些炫酷的.

    另外本文不会科普最基本的概念与Api, 直接从实战出发, 希望大家看完后都能写出各种炫酷的效果

    曲线图


    曲线图在平时用的应该是最多的, 曲线图会了, 折线图就更容易了.

    图上的效果大致分3步(下面的动画也一样):

    1.处理数据: 将得到的数据转换为点坐标数据, 这一步就不细说了

    2.绘制图形: 可以用Quartz2D或者UIKit中封装好的UIBezierPath

    3.设置动画: 主要利用到CoreAnimation中的"strokeEnd"动画

    下面就看具体代码吧:

    绘制图形
    /*
     pointArray是所有点的数组
     color是主题色
     compete绘制完成的回调
    */
    - (void)drawLayerWithPointArray:(NSMutableArray *)pointArray color:(UIColor *)color compete:(completeBlock)compete{
        
        //初始化下面渐变色路径
        UIBezierPath *fillPath = [UIBezierPath new];
        //初始化曲线的路径
        UIBezierPath *borderPath = [UIBezierPath new];
        
        //这里是我个人设置点数过多 忽略部分点, 让曲线更平滑, 按需删除
        NSInteger ignoreSpace = pointArray.count / 15;
        
        //记录上一个点
        __block CGPoint lastPoint;
        //记录上一个点的索引
        __block NSUInteger  lastIdx;
        //渐变色路径移动到左下角
        [fillPath moveToPoint:CGPointMake(0, _chart.height)];
        //遍历所有点, 移动Path绘制图形
        [pointArray enumerateObjectsUsingBlock:^(NSValue *obj, NSUInteger idx, BOOL * _Nonnull stop) {
            
            CGPoint point = obj.CGPointValue;
            
            if (idx == 0) { //第一个点
                
                [fillPath addLineToPoint:point];
                [borderPath moveToPoint:point];
                lastPoint = point;
                lastIdx = idx;
            } else if ((idx == pointArray.count - 1) || (point.y == 0) || (lastIdx + ignoreSpace + 1 == idx)) { //最后一个点最高点要画/当点数过多时 忽略部分点
                
                [fillPath addCurveToPoint:point controlPoint1:CGPointMake((lastPoint.x + point.x) / 2, lastPoint.y) controlPoint2:CGPointMake((lastPoint.x + point.x) / 2, point.y)]; //三次曲线
                [borderPath addCurveToPoint:point controlPoint1:CGPointMake((lastPoint.x + point.x) / 2, lastPoint.y) controlPoint2:CGPointMake((lastPoint.x + point.x) / 2, point.y)];
                lastPoint = point;
                lastIdx = idx;
            }
        }];
        //将渐变色区域封闭
        [fillPath addLineToPoint:CGPointMake(_chart.width, _chart.height)];
        [fillPath addLineToPoint:CGPointMake(0, _chart.height)];
        
        //初始化Path的载体分别显示路径及填充渐变色
        CAShapeLayer *shapeLayer = [CAShapeLayer layer];
        shapeLayer.path = fillPath.CGPath;
        [_chart.layer addSublayer:shapeLayer];
        
        CAShapeLayer *borderShapeLayer = [CAShapeLayer layer];
        borderShapeLayer.path = borderPath.CGPath;
        borderShapeLayer.lineWidth = 2.f;
        borderShapeLayer.strokeColor = color.CGColor;
        borderShapeLayer.fillColor = [UIColor clearColor].CGColor;
        [_chart.layer addSublayer:borderShapeLayer];
        
        //设置渐变色
        CAGradientLayer *gradientLayer = [CAGradientLayer layer];
        gradientLayer.frame = _chart.bounds;
        [gradientLayer setColors:[NSArray arrayWithObjects:(id)[[color colorWithAlphaComponent:0.5] CGColor], (id)[[UIColor clearColor] CGColor], nil]];
        [gradientLayer setStartPoint:CGPointMake(0.5, 0)];
        [gradientLayer setEndPoint:CGPointMake(0.5, 1)];
        [gradientLayer setMask:shapeLayer];
        [_chart.layer addSublayer:gradientLayer];
        
        compete(borderShapeLayer, shapeLayer, gradientLayer);
    }
    

    以上 一个曲线图就画完了, 下面看看怎么样让它动起来

    设置动画
    - (void)animation{
        //动画之前让曲线不隐藏
        _bulletBorderLayer.hidden = NO;
        
        //路径动画的KeyPath为@"strokeEnd"
        //根据需要的效果, 从0-1意味着画完整个曲线
        CABasicAnimation *animation1 = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
        animation1.fromValue = @(0);
        animation1.toValue = @(1);
        animation1.duration = 0.8;
        
        [_bulletBorderLayer addAnimation:animation1 forKey:nil];
        //动画需要0.8秒完成, 延迟0.8秒让渐变色动画, 当然也可以用代理
        [self performSelector:@selector(bulletLayerAnimation) withObject:nil afterDelay:0.8];
    }
    
    
    - (void)bulletLayerAnimation{
        //动画之前让渐变色不隐藏  
        _bulletLayer.hidden = NO;
        
        //渐变色看起来像是从上往下长出来, 实际只是透明度的变化
        CABasicAnimation *animation2 = [CABasicAnimation animationWithKeyPath:@"opacity"];
        animation2.fromValue = @(0);
        animation2.toValue = @(1);
        animation2.duration = 0.4;
        
        [_bulletLayer addAnimation:animation2 forKey:nil];
    }
    
    

    整个曲线图效果就完成了.

    柱状图

    柱状图其实更容易, 只是绘制这种柱状图稍微麻烦一点点而已,
    这里我没有用strokeEnd, 而是直接垂直方向高度变化, 需要注意的是图表的Y方向跟屏幕坐标系的Y方向是相仿的, 所以这里是位置动画加上垂直方向缩放动画的组动画, 也就是AnimationGroup

    绘制图形
    /*
     wordsArrayRandom是乱序过后的词语数组, 记录了每个词语的频次
    */
    CGFloat maxHeight = _chart.height; //确定最大高度
    CGFloat width = 2; //确定竖线宽度
    CGFloat margin = _chart.width / 9;
    NSInteger maxCount = wordsModel.count.integerValue;
    [wordsArrayRandom enumerateObjectsUsingBlock:^(BAWordsModel *wordsModel, NSUInteger idx, BOOL * _Nonnull stop) {
        
        //绘制
        CGPoint orginPoint = CGPointMake(margin * idx, maxHeight); //圆点, 在矩形下边中间
        CGFloat height = maxHeight * wordsModel.count.integerValue / maxCount; //高度
        
        //其实就是一个矩形加上一个圆形
        UIBezierPath *path = [UIBezierPath new];
        [path moveToPoint:orginPoint];
        [path addLineToPoint:CGPointMake(path.currentPoint.x - width / 2, path.currentPoint.y)];
        [path addLineToPoint:CGPointMake(path.currentPoint.x, path.currentPoint.y - height)];
        [path addLineToPoint:CGPointMake(path.currentPoint.x + width, path.currentPoint.y)];
        [path addLineToPoint:CGPointMake(path.currentPoint.x, orginPoint.y)];
        [path addLineToPoint:orginPoint];
        [path addArcWithCenter:CGPointMake(orginPoint.x, maxHeight - height) radius:width * 2 startAngle:0 endAngle:M_PI * 2 clockwise:YES];
        
        CAShapeLayer *shapeLayer = [CAShapeLayer layer];
        shapeLayer.path = path.CGPath;
        shapeLayer.hidden = YES;
        shapeLayer.fillColor = [BAWhiteColor colorWithAlphaComponent:0.8].CGColor;
        [_chart.layer addSublayer:shapeLayer];
        
        [_barLayerArray addObject:shapeLayer];
    }];
    

    绘制的代码我摘出了比较重要的部分, 全部的大家可以去下载Demo查看

    设置动画
    //每间隔0.1秒, 动画一个柱状图
    - (void)animation{
        for (NSInteger i = 0; i < 10; i++) {
            CAShapeLayer *layer = _barLayerArray[9 - i];
            [self performSelector:@selector(animateLayer:) withObject:layer afterDelay:i * 0.1];
        }
    }
    
    - (void)animateLayer:(CAShapeLayer *)layer{
        
        layer.hidden = NO;
        
        //垂直方向的缩放
        CABasicAnimation *animation1 = [CABasicAnimation animationWithKeyPath:@"transform.scale.y"];
        animation1.fromValue = @(0.0);
        animation1.toValue = @(1.0);
        
        //同时垂直方向坐标原点在变化
        CABasicAnimation *animation2 = [CABasicAnimation animationWithKeyPath:@"transform.translation.y"];
        animation2.fromValue = @(_chart.height);
        animation2.toValue = @(0.0);
        
        CAAnimationGroup *animationGroup = [CAAnimationGroup animation];
        animationGroup.duration = 0.3;
        animationGroup.animations = @[animation1, animation2];
        
        [layer addAnimation:animationGroup forKey:nil];
    }
    

    柱状图也完成了, 上面的都还好, 最后看看饼状图的绘制吧

    饼状图

    我们看到饼状图不光是需要绘制/动画/还需要一个交互.
    我们都知道CAShapeLayer是也是Layer, 本身是并不响应用户点击的, 所以这里需要手动处理, 还是一步步来说.

    绘制图形

    本身绘制饼状图不复杂, 但是要绘制连接线和小图标就麻烦了一点, 另外因为要整体移动饼状图, 所以每一个饼状图加上附带的图标得放到一个容器Layer里面

    /*
    思路: 外面的大圆跟里面小圆 其实是两条比较粗的曲线, 计算好尺寸之后拼接起来,
    外面的小图标(素材)与大圆之间需要计算角度与长度才能画线连接起来
    */
    - (void)drawPieChart{
        
        //设置大圆半径, 小圆半径, 中心点
        _pieRadius = self.height / 2 - 8 * BAPadding - 7;
        _inPieRadius = _pieRadius - 3 * BAPadding + 3.5;
        _pieCenter = CGPointMake(self.width / 2, self.height / 2 + 40);
        
        //外面饼状数组
        NSMutableArray *pieArray = [NSMutableArray array];
        //里面饼状数组
        NSMutableArray *inPieArray = [NSMutableArray array];
        //这个数组用来存放动画时间, 每一段的动画时间应该跟它所占比成比例
        NSMutableArray *durationArray = [NSMutableArray array];
        //容器数组, 动画时方便整体移动
        NSMutableArray *arcArray = [NSMutableArray array];
        
        //起始(终止)角度
        __block CGFloat endAngle = - M_PI / 2;
        //_giftValueArray几面已经是处理好的数据, 包括了每一块的价值
        [_giftValueArray enumerateObjectsUsingBlock:^(BAGiftValueModel *giftValueModel, NSUInteger idx, BOOL * _Nonnull stop) {
            
            //创建一个容器 放外部饼状图与内部饼状图, 为动画做准备
            CALayer *arcLayer = [CALayer layer];
            arcLayer.frame = self.bounds;
            [arcArray addObject:arcLayer];
            [self.layer addSublayer:arcLayer];
            
            //计算每个礼物的起始 终止角度
            CGFloat startAngle = endAngle;
            
            //caculateWithStartAngle是根据起始角度与最大价值算终止角度
            //_maxValue为之前计算好的总价值
            [giftValueModel caculateWithStartAngle:startAngle maxValue:_maxValue];
            endAngle = giftValueModel.endAngle;
            
            //1.2是总共的动画时间, 计算这一块动画所需要的时间
            CGFloat duration = 1.2 * giftValueModel.totalGiftValue / _maxValue;
            [durationArray addObject:@(duration)];
            
            //当前饼状图的颜色
            UIColor *pieColor = [BAWhiteColor colorWithAlphaComponent:giftValueModel.alpha];
            UIColor *inPieColor = [BAWhiteColor colorWithAlphaComponent:giftValueModel.alpha - 0.3];
            
            //画图 
            //外部饼状图路径
            UIBezierPath *piePath = [UIBezierPath bezierPath]; //内部圆环路径
            UIBezierPath *inPiePath = [UIBezierPath bezierPath];
            
            [piePath addArcWithCenter:_pieCenter radius:_pieRadius startAngle:startAngle endAngle:endAngle clockwise:YES];
            [inPiePath addArcWithCenter:_pieCenter radius:_inPieRadius startAngle:startAngle endAngle:endAngle clockwise:YES];
            
            CAShapeLayer *pieLayer = [CAShapeLayer layer];
            pieLayer.path = piePath.CGPath;
            pieLayer.lineWidth = 4 * BAPadding;
            pieLayer.strokeColor = pieColor.CGColor;
            pieLayer.fillColor = [UIColor clearColor].CGColor;
            pieLayer.hidden = YES;
            
            CAShapeLayer *inPieLayer = [CAShapeLayer layer];
            inPieLayer.path = inPiePath.CGPath;
            inPieLayer.lineWidth = 14;
            inPieLayer.strokeColor = inPieColor.CGColor;
            inPieLayer.fillColor = [UIColor clearColor].CGColor;
            inPieLayer.hidden = YES;
            
            [arcLayer addSublayer:pieLayer];
            [arcLayer addSublayer:inPieLayer];
            [pieArray addObject:pieLayer];
            [inPieArray addObject:inPieLayer];
            
            //显示各种bedge 并绘制连接线
            [self drawBedgeWithGiftValueModel:giftValueModel container:arcLayer];
        }];
        _pieArray = pieArray;
        _inPieArray = inPieArray;
        _durationArray = durationArray;
        _arcArray = arcArray;
    }
    
    
    - (void)drawBedgeWithGiftValueModel:(BAGiftValueModel *)giftValueModel container:(CALayer *)container{
        
        //根据不同的礼物类型显示不同的图片
        CALayer *iconLayer;
        switch (giftValueModel.giftType) {
                
            case BAGiftTypeCostGift:
                iconLayer = _costIcon;
                break;
                
            case BAGiftTypeDeserveLevel1:
                iconLayer = _deserve1Icon;
                
                break;
                
            case BAGiftTypeDeserveLevel2:
                iconLayer = _deserve2Icon;
                
                break;
                
            case BAGiftTypeDeserveLevel3:
                iconLayer = _deserve3Icon;
                
                break;
                
            case BAGiftTypeCard:
                iconLayer = _cardIcon;
                
                break;
                
            case BAGiftTypePlane:
                iconLayer = _planeIcon;
                
                break;
                
                
            case BAGiftTypeRocket:
                iconLayer = _rocketIcon;
                
                break;
                
            default:
                break;
        }
        [_bedgeArray addObject:iconLayer];
        
        CGFloat iconDistance = container.frame.size.height / 2 - 40; //图标到中心点的距离
        CGFloat iconCenterX;
        CGFloat iconCenterY;
        
        CGFloat borderDistance = _pieRadius + 2 * BAPadding;
        CGFloat lineBeginX;
        CGFloat lineBeginY;
        
        CGFloat iconBorderDistance = iconDistance - 12.5;
        CGFloat lineEndX;
        CGFloat lineEndY;
        
        CGFloat moveDistance = BAPadding; //动画移动的距离
        CGFloat moveX;
        CGFloat moveY;
        
        /*
        这里计算各种参数
        directAngle为之前计算起始终止角度时保存下来的饼状图朝向
        这个朝向需要在四个象限, 转换为锐角, 然后通过三角函数就可以算出连接线的起点终点, 图标的位置
        */
        CGFloat realDirectAngle; //锐角
        if (giftValueModel.directAngle > - M_PI / 2 && giftValueModel.directAngle < 0) { //-90° - 0°
           
            realDirectAngle = giftValueModel.directAngle - (- M_PI / 2);
            
            iconCenterX = _pieCenter.x + iconDistance * sin(realDirectAngle);
            iconCenterY = _pieCenter.y - iconDistance * cos(realDirectAngle);
            
            lineBeginX = _pieCenter.x + borderDistance * sin(realDirectAngle);
            lineBeginY = _pieCenter.y - borderDistance * cos(realDirectAngle);
            
            lineEndX = _pieCenter.x + iconBorderDistance * sin(realDirectAngle);
            lineEndY = _pieCenter.y - iconBorderDistance * cos(realDirectAngle);
            
            moveX = moveDistance * sin(realDirectAngle);
            moveY = - moveDistance * cos(realDirectAngle);
            
        } else if (giftValueModel.directAngle > 0 && giftValueModel.directAngle < M_PI / 2) { // 0° - 90°
           
            realDirectAngle = giftValueModel.directAngle;
            
            iconCenterX = _pieCenter.x + iconDistance * cos(realDirectAngle);
            iconCenterY = _pieCenter.y + iconDistance * sin(realDirectAngle);
            
            lineBeginX = _pieCenter.x + borderDistance * cos(realDirectAngle);
            lineBeginY = _pieCenter.y + borderDistance * sin(realDirectAngle);
            
            lineEndX = _pieCenter.x + iconBorderDistance * cos(realDirectAngle);
            lineEndY = _pieCenter.y + iconBorderDistance * sin(realDirectAngle);
    
            moveX = moveDistance * cos(realDirectAngle);
            moveY = moveDistance * sin(realDirectAngle);
            
        } else if (giftValueModel.directAngle > M_PI / 2 && giftValueModel.directAngle < M_PI) { // 90° - 180°
            
            realDirectAngle = giftValueModel.directAngle - M_PI / 2;
            
            iconCenterX = _pieCenter.x - iconDistance * sin(realDirectAngle);
            iconCenterY = _pieCenter.y + iconDistance * cos(realDirectAngle);
            
            lineBeginX = _pieCenter.x - borderDistance * sin(realDirectAngle);
            lineBeginY = _pieCenter.y + borderDistance * cos(realDirectAngle);
            
            lineEndX = _pieCenter.x - iconBorderDistance * sin(realDirectAngle);
            lineEndY = _pieCenter.y + iconBorderDistance * cos(realDirectAngle);
            
            moveX = - moveDistance * sin(realDirectAngle);
            moveY = moveDistance * cos(realDirectAngle);
            
        } else { //180° - -90°
            
            realDirectAngle = giftValueModel.directAngle - M_PI;
            
            iconCenterX = _pieCenter.x - iconDistance * cos(realDirectAngle);
            iconCenterY = _pieCenter.y - iconDistance * sin(realDirectAngle);
            
            lineBeginX = _pieCenter.x - borderDistance * cos(realDirectAngle);
            lineBeginY = _pieCenter.y - borderDistance * sin(realDirectAngle);
            
            lineEndX = _pieCenter.x - iconBorderDistance * cos(realDirectAngle);
            lineEndY = _pieCenter.y - iconBorderDistance * sin(realDirectAngle);
            
            moveX = - moveDistance * cos(realDirectAngle);
            moveY = - moveDistance * sin(realDirectAngle);
        }
        
        //画线
        UIBezierPath *linePath = [UIBezierPath bezierPath];
        [linePath moveToPoint:CGPointMake(lineBeginX, lineBeginY)];
        [linePath addLineToPoint:CGPointMake(lineEndX, lineEndY)];
        
        CAShapeLayer *lineLayer = [CAShapeLayer layer];
        lineLayer.path = linePath.CGPath;
        lineLayer.lineWidth = 1;
        lineLayer.strokeColor = [BAWhiteColor colorWithAlphaComponent:0.6].CGColor;
        lineLayer.fillColor = [UIColor clearColor].CGColor;
        lineLayer.hidden = YES;
        
        [_lineArray addObject:lineLayer];
        [container addSublayer:lineLayer];
        
        //保存移动的动画
        giftValueModel.translation = CATransform3DMakeTranslation(moveX, moveY, 0);
        
        iconLayer.frame = CGRectMake(iconCenterX - 13.75, iconCenterY - 13.75, 27.5, 27.5);
        [container addSublayer:iconLayer];
    }
    
    
    /**
     *  计算角度 与Y轴夹角 -90 - 270
     */
    - (CGFloat)angleForStartPoint:(CGPoint)startPoint EndPoint:(CGPoint)endPoint{
        
        CGPoint Xpoint = CGPointMake(startPoint.x + 100, startPoint.y);
        
        CGFloat a = endPoint.x - startPoint.x;
        CGFloat b = endPoint.y - startPoint.y;
        CGFloat c = Xpoint.x - startPoint.x;
        CGFloat d = Xpoint.y - startPoint.y;
        
        CGFloat rads = acos(((a*c) + (b*d)) / ((sqrt(a*a + b*b)) * (sqrt(c*c + d*d))));
        
        if (startPoint.y > endPoint.y) {
            rads = -rads;
        }
        if (rads < - M_PI / 2 && rads > - M_PI) {
            rads += M_PI * 2;
        }
        
        return rads;
    }
    
    //两点之间距离
    - (CGFloat)distanceForPointA:(CGPoint)pointA pointB:(CGPoint)pointB{
        CGFloat deltaX = pointB.x - pointA.x;
        CGFloat deltaY = pointB.y - pointA.y;
        return sqrt(deltaX * deltaX + deltaY * deltaY );
    }
    

    上面画整体的过程有点小复杂, 因为涉及了各种角度转换 计算, 以及为之后动画 交互做准备, 做好了前面的准备, 再进行动画跟交互处理就容易不少.

    设置动画

    动画的过程其实是饼状图按顺序一个个执行前面画曲线所用的strokeEnd动画, 然后我们小图标以及我们画的连接线透明度动画展现.

    - (void)animation{
        NSInteger i = 0;
        CGFloat delay = 0;
        //遍历所有的饼状图, 按顺序执行动画
        for (CAShapeLayer *pieLayer in _pieArray) {
            CAShapeLayer *inPieLayer = _inPieArray[i];
            CGFloat duration = [_durationArray[i] floatValue];
            [self performSelector:@selector(animationWithAttribute:) withObject:@{@"layer" : pieLayer, @"duration" : @(duration)} afterDelay:delay inModes:@[NSRunLoopCommonModes]];
            [self performSelector:@selector(animationWithAttribute:) withObject:@{@"layer" : inPieLayer, @"duration" : @(duration)} afterDelay:delay inModes:@[NSRunLoopCommonModes]];
            delay += duration;
            i++;
        }
        
        [self performSelector:@selector(animationWithBedge) withObject:nil afterDelay:delay];
    }
    
    //根据传入的时间以及饼状图路径动画
    - (void)animationWithAttribute:(NSDictionary *)attribute{
        CAShapeLayer *layer = attribute[@"layer"];
        CGFloat duration = [attribute[@"duration"] floatValue];
    
        layer.hidden = NO;
        
        CABasicAnimation *animation1 = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
        animation1.fromValue = @(0);
        animation1.toValue = @(1);
        animation1.duration = duration;
        
        [layer addAnimation:animation1 forKey:nil];
    }
    
    //透明度渐变展示各种小图标
    - (void)animationWithBedge{
        NSInteger i = 0;
        for (CAShapeLayer *lineLayer in _lineArray) {
            CALayer *bedgeLayer = _bedgeArray[i];
            
            lineLayer.hidden = NO;
            bedgeLayer.hidden = NO;
            
            CABasicAnimation *animation1 = [CABasicAnimation animationWithKeyPath:@"opacity"];
            animation1.fromValue = @(0);
            animation1.toValue = @(1);
            animation1.duration = 0.4;
            
            [lineLayer addAnimation:animation1 forKey:nil];
            [bedgeLayer addAnimation:animation1 forKey:nil];
            i++;
        }
    }
    
    
    
    处理交互

    交互的思路其实很清晰, 判断一个饼状图被点击了有2个条件:

    1.点击的点与圆心之间的连线与-90°(之前设定的基准)之间的夹角是否在之前计算的饼状图起始终止角度之间.
    2.点击的点与圆心的距离是否大于内圆的半径(最内), 小于外圆的半径(最外).

    我们发现其实这些之前已经计算好了, 所以直接计算这个点的参数

    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
        CGPoint touchPoint = [[touches anyObject] locationInView:self];
        
        [self dealWithTouch:touchPoint];
    }
    
    
    - (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
        CGPoint touchPoint = [[touches anyObject] locationInView:self];
        
        [self dealWithTouch:touchPoint];
    }
    
    - (void)dealWithTouch:(CGPoint)touchPoint{
        
        CGFloat touchAngle = [self angleForStartPoint:_pieCenter EndPoint:touchPoint];
        CGFloat touchDistance = [self distanceForPointA:touchPoint pointB:_pieCenter];
        //判断是否点击了鱼丸
        if (touchDistance < _inPieRadius - BAPadding) {
            
            if (self.isFishBallClicked) {
                _giftPieClicked(BAGiftTypeNone);
            } else {
                _giftPieClicked(BAGiftTypeFishBall);
            }
            [self animationFishBall];
            
            return;
        }
        
        //求点击位置与-90°的夹角 与 之前的圆弧对比
        if (touchDistance > _inPieRadius - BAPadding && touchDistance < _pieRadius + 2 * BAPadding) {
            
            [_giftValueArray enumerateObjectsUsingBlock:^(BAGiftValueModel *giftValueModel, NSUInteger idx, BOOL * _Nonnull stop) {
                
                if (giftValueModel.startAngle < touchAngle && giftValueModel.endAngle > touchAngle) {
                    
                    //isMovingOut用来标记是否已经移动出去了
                    if (giftValueModel.isMovingOut) {
                        _giftPieClicked(BAGiftTypeNone);
                    } else {
                        _giftPieClicked(giftValueModel.giftType);
                    }
                    
                    [self animationMove:_arcArray[idx] giftValueModel:giftValueModel];
                    *stop = YES;
                }
            }];
        }
    }
    
    //将传入的饼状图移动, 并且遍历所有饼状图, 联动收回之前的饼状图
    - (void)animationMove:(CALayer *)arcLayer giftValueModel:(BAGiftValueModel *)giftValueModel{
    
        if (giftValueModel.isMovingOut) {
            arcLayer.transform = CATransform3DIdentity;
            giftValueModel.movingOut = NO;
        } else {
            arcLayer.transform = giftValueModel.translation;
            giftValueModel.movingOut = YES;
        
            [_arcArray enumerateObjectsUsingBlock:^(CALayer *arc, NSUInteger idx, BOOL * _Nonnull stop) {
                BAGiftValueModel *giftValue = _giftValueArray[idx];
                if (![arcLayer isEqual:arc] && giftValue.isMovingOut) {
                    [self animationMove:arc giftValueModel:giftValue];
                }
            }];
            
            if (self.isFishBallClicked) {
                [self animationFishBall];
            }
        }
    }
    

    结语

    至此, 所有炫酷的动态可交互图表就已经完成了, 其实这个App里面细节动画处理还挺多的, 例如滑动时背景渐变色的角度改变, 渐变色的动画, 包括一个有点酷的引导页, 启动页.

    项目已上线: 叫直播伴侣, 可以下载下来玩玩,
    另外代码也是开源的:
    https://github.com/syik/BulletAnalyzer 觉得有意思的可以打赏一个Star~

    项目中有一个有意思的功能, 中文语义近似的分析可以看看我的上一篇文章.

    发现大家对动画更感兴趣, 下一篇讲讲动态的启动页与炫酷的引导页动画.

    相关文章

      网友评论

      • 小czy:您的demo点击保存按钮会闪退
        Syik:@小czy 好的 我看看
        小czy:@Syik 这个崩溃我自己改了 加了个权限就好了
        Syik:App已经上线了, 可以下个线上的试试

      本文标题:CoreGraphics,CoreAnimation实战, 可交

      本文链接:https://www.haomeiwen.com/subject/mrutsxtx.html