以非常高的速度绘制动态形状 iOS

Draw dynamic shapes iOS at very high rate

我正在尝试创建一种看起来像这样的“雷达”:

这个想法是蓝色部分将像指南针一样指示用户附近的东西。当用户远离 objective 时,形状会变窄、变长和蓝色,当用户靠近时变短、变粗和变红,如下图:

为了实现这一点,我画了几个圆圈(实际上我尝试了 IBDesignable 和 IBInspectable 但我一直收到“构建失败”但这是另一个主题)

我的问题是:是否可以绘制应该高速改变尺寸的“圆锥体”部分而不会有滞后的风险? 我可以使用 UIBezierPath / AddArc 或 Addline 方法来实现这一点,否则我将走向死胡同吗?

您可能应该使用一个或多个 CAShapeLayer 对象和核心动画。

您可以将 UIBezierPath 中的 CGPath 安装到形状图层中,并且可以对形状图层的更改进行动画处理,尽管要使动画正常工作,您需要动画开始和结束时形状中相同数量和类型的控制点。

有两种方法:

  1. 定义复杂的贝塞尔曲线形状和动画使用 CABasicAnimation:

    - (UIBezierPath *)pathWithCenter:(CGPoint)center angle:(CGFloat)angle radius:(CGFloat)radius {
        UIBezierPath *path = [UIBezierPath bezierPath];
    
        CGPoint point1 = CGPointMake(center.x - radius * sinf(angle), center.y - radius * cosf(angle));
    
        [path moveToPoint:center];
        [path addLineToPoint:point1];
    
        NSInteger curves = 8;
        CGFloat currentAngle = M_PI_2 * 3.0 - angle;
    
        for (NSInteger i = 0; i < curves; i++) {
            CGFloat nextAngle = currentAngle + angle * 2.0 / curves;
            CGPoint point2 = CGPointMake(center.x + radius * cosf(nextAngle), center.y + radius * sinf(nextAngle));
            CGFloat controlPointRadius = radius / cosf(angle / curves);
            CGPoint controlPoint = CGPointMake(center.x + controlPointRadius * cosf(currentAngle + angle / curves), center.y + controlPointRadius * sinf(currentAngle + angle / curves));
            [path addQuadCurveToPoint:point2 controlPoint:controlPoint];
            currentAngle = nextAngle;
            point1 = point2;
        }
        [path closePath];
        return path;
    }
    

    我只是将圆弧分割成一系列四边形曲线。我发现三次曲线可以更接近,但只有几条四曲线,我们得到一个非常好的近似值。

    然后使用 CABasicAnimation 对其进行动画处理:

    - (void)viewDidAppear:(BOOL)animated {
        [super viewDidAppear:animated];
    
        CAShapeLayer *layer = [CAShapeLayer layer];
        layer.fillColor = self.startColor.CGColor;
        layer.path = self.startPath.CGPath;
        [self.view.layer addSublayer:layer];
        self.radarLayer = layer;
    }
    
    - (IBAction)didTapButton:(id)sender {
        self.radarLayer.path = self.finalPath.CGPath;
        CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:@"path"];
        pathAnimation.fromValue = (id)self.startPath.CGPath;
        pathAnimation.toValue = (id)self.finalPath.CGPath;
        pathAnimation.removedOnCompletion = false;
    
        self.radarLayer.fillColor = self.finalColor.CGColor;
        CABasicAnimation *fillAnimation = [CABasicAnimation animationWithKeyPath:@"fillColor"];
        fillAnimation.fromValue = (id)self.startColor.CGColor;
        fillAnimation.toValue = (id)self.finalColor.CGColor;
        fillAnimation.removedOnCompletion = false;
    
        CAAnimationGroup *group = [CAAnimationGroup animation];
        group.animations = @[pathAnimation, fillAnimation];
        group.duration = 2.0;
        [self.radarLayer addAnimation:group forKey:@"bothOfMyAnimations"];
    }
    

    结果是:

  2. 另一种方法是使用简单的贝塞尔曲线,但使用显示动画 link:

    如果您想使用 CAShapeLayerCADisplayLink(类似于针对屏幕更新优化的计时器),您可以这样做:

    @interface ViewController ()
    
    @property (nonatomic, strong) CADisplayLink *displayLink;
    @property (nonatomic) CFAbsoluteTime startTime;
    @property (nonatomic) CFAbsoluteTime duration;
    @property (nonatomic, weak) CAShapeLayer *radarLayer;
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidAppear:(BOOL)animated {
        [super viewDidAppear:animated];
    
        CAShapeLayer *layer = [CAShapeLayer layer];
        [self.view.layer addSublayer:layer];
        self.radarLayer = layer;
        [self updateRadarLayer:layer percent:0.0];
    }
    
    - (IBAction)didTapButton:(id)sender {
        [self startDisplayLink];
    }
    
    - (void)updateRadarLayer:(CAShapeLayer *)radarLayer percent:(CGFloat)percent {
        CGFloat angle = M_PI_4 * (1.0 - percent / 2.0);
        CGFloat distance = 200 * (0.5 + percent / 2.0);
    
        UIBezierPath *path = [UIBezierPath bezierPath];
        [path moveToPoint:self.view.center];
        [path addArcWithCenter:self.view.center radius:distance startAngle:M_PI_2 * 3.0 - angle endAngle:M_PI_2 * 3.0 + angle clockwise:true];
        [path closePath];
    
        radarLayer.path = path.CGPath;
        radarLayer.fillColor = [self colorForPercent:percent].CGColor;
    }
    
    - (UIColor *)colorForPercent:(CGFloat)percent {
        return [UIColor colorWithRed:percent green:0 blue:1.0 - percent alpha:1];
    }
    
    - (void)startDisplayLink {
        self.startTime = CFAbsoluteTimeGetCurrent();
        self.duration = 2.0;
        self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(handleDisplayLink:)];
        [self.displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
    }
    
    - (void)handleDisplayLink:(CADisplayLink *)link {
        CGFloat percent = (CFAbsoluteTimeGetCurrent() - self.startTime) / self.duration;
        if (percent < 1.0) {
            [self updateRadarLayer:self.radarLayer percent:percent];
        } else {
            [self stopDisplayLink];
            [self updateRadarLayer:self.radarLayer percent:1.0];
        }
    }
    
    - (void)stopDisplayLink {
        [self.displayLink invalidate];
        self.displayLink = nil;
    }
    

    结果是: