以非常高的速度绘制动态形状 iOS
Draw dynamic shapes iOS at very high rate
我正在尝试创建一种看起来像这样的“雷达”:
这个想法是蓝色部分将像指南针一样指示用户附近的东西。当用户远离 objective 时,形状会变窄、变长和蓝色,当用户靠近时变短、变粗和变红,如下图:
为了实现这一点,我画了几个圆圈(实际上我尝试了 IBDesignable 和 IBInspectable 但我一直收到“构建失败”但这是另一个主题)
我的问题是:是否可以绘制应该高速改变尺寸的“圆锥体”部分而不会有滞后的风险?
我可以使用 UIBezierPath / AddArc 或 Addline 方法来实现这一点,否则我将走向死胡同吗?
您可能应该使用一个或多个 CAShapeLayer
对象和核心动画。
您可以将 UIBezierPath
中的 CGPath
安装到形状图层中,并且可以对形状图层的更改进行动画处理,尽管要使动画正常工作,您需要动画开始和结束时形状中相同数量和类型的控制点。
有两种方法:
定义复杂的贝塞尔曲线形状和动画使用 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"];
}
结果是:
另一种方法是使用简单的贝塞尔曲线,但使用显示动画 link:
如果您想使用 CAShapeLayer
和 CADisplayLink
(类似于针对屏幕更新优化的计时器),您可以这样做:
@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;
}
结果是:
我正在尝试创建一种看起来像这样的“雷达”:
这个想法是蓝色部分将像指南针一样指示用户附近的东西。当用户远离 objective 时,形状会变窄、变长和蓝色,当用户靠近时变短、变粗和变红,如下图:
为了实现这一点,我画了几个圆圈(实际上我尝试了 IBDesignable 和 IBInspectable 但我一直收到“构建失败”但这是另一个主题)
我的问题是:是否可以绘制应该高速改变尺寸的“圆锥体”部分而不会有滞后的风险? 我可以使用 UIBezierPath / AddArc 或 Addline 方法来实现这一点,否则我将走向死胡同吗?
您可能应该使用一个或多个 CAShapeLayer
对象和核心动画。
您可以将 UIBezierPath
中的 CGPath
安装到形状图层中,并且可以对形状图层的更改进行动画处理,尽管要使动画正常工作,您需要动画开始和结束时形状中相同数量和类型的控制点。
有两种方法:
定义复杂的贝塞尔曲线形状和动画使用
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"]; }
结果是:
另一种方法是使用简单的贝塞尔曲线,但使用显示动画 link:
如果您想使用
CAShapeLayer
和CADisplayLink
(类似于针对屏幕更新优化的计时器),您可以这样做:@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; }
结果是: