【问题标题】:Core Graphics angle gradient for gauge仪表的核心图形角度梯度
【发布时间】:2017-02-04 02:11:29
【问题描述】:

我正在尝试将角度渐变应用于使用我在自定义 UIView 类中编写的代码创建的破折号,如下所示。虽然它需要调整,但我对它迄今为止产生的结果感到满意。

给定视图初始化中的输入参数(如下),以及 iPad Air2 纵向模式下的 768 * 768 帧,它会生成以下仪表:

First gauge

我想做的是使每个破折号逐步通过用户定义的渐变,例如从绿色到红色,很像这样(在 Photoshop 中拼凑):

Gauge with colours

我搜索了高低,找不到任何东西来实现这一点。唯一接近的东西使用不同的绘图方法,我想保持我的绘图程序。

就我而言,我应该能够调用:

CGContextSetStrokeColorWithColor(myContext, [这里是渐变色])

在绘图循环中,就是这样,但我不知道如何创建相关的颜色数组/渐变,并根据该数组的索引更改线条绘制颜色。

任何帮助将不胜感激。

- (void)drawRect:(CGRect)rect {

    myContext = UIGraphicsGetCurrentContext();
    UIImage *gaugeImage = [self radials:300 andSteps:3 andLineWidth:10.0];
    UIImageView *gaugeImageView = [[UIImageView alloc] initWithImage:gaugeImage];
    [self addSubview:gaugeImageView];

}

-(UIImage *)radials:(NSInteger)degrees andSteps:(NSInteger)steps andLineWidth:(CGFloat)lineWidth{

    UIGraphicsBeginImageContext(self.bounds.size);
    myContext = UIGraphicsGetCurrentContext();
    CGContextSetLineWidth(myContext, lineWidth);
    CGContextSetStrokeColorWithColor(myContext, [[UIColor blackColor] CGColor]);

    CGPoint center = CGPointMake(self.bounds.origin.x+(self.bounds.size.width/2), self.bounds.origin.y+(self.bounds.size.height/2));
    CGFloat r1 = center.x * 0.87f;
    CGFloat r2 = center.x * 0.95f;

    CGContextTranslateCTM(myContext, center.x, center.y);
    CGContextBeginPath(myContext);

    CGFloat offset  = 0;

    if(degrees < 360){
        offset = (360-degrees) / 2;
    }

    for(int lp = offset + 0 ; lp < offset + degrees+1 ; lp+=steps){

        CGFloat theta = lp * (2 * M_PI / 360);
        CGContextMoveToPoint(myContext, 0, 0);

        r1 = center.x * 0.87f;
        if(lp % 10 == 0){
            r1 = center.x * 0.81f;
        }

        CGContextMoveToPoint(myContext, sin(theta) * r1, cos(theta) * r1);
        CGContextAddLineToPoint(myContext, sin(theta) * r2, cos(theta) * r2);
        CGContextStrokePath(myContext);
    }

    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return image;
}

【问题讨论】:

  • 不要这样滥用drawRectdrawRect 用于绘图——而不是用于添加子视图。请记住,drawRect 被多次调用,而您只是一次又一次地添加该子视图,这太荒谬了。
  • 点了!我的错!

标签: ios core-graphics gradient angle gauge


【解决方案1】:

所以,你想要这样的东西:

首先,有几个温和的建议:

  1. 不要在drawRect: 中添加子视图。如果drawRect: 被第二次调用,例如视图的大小发生变化,该怎么办?

    下面是View Programming Guide for iOS 对实现drawRect: 所说的话:

    drawRect: 方法的实现应该只做一件事:绘制您的内容。此方法不适用于更新应用程序的数据结构或执行任何与绘图无关的任务。它应该配置绘图环境,绘制你的内容,并尽快退出。如果您的drawRect: 方法可能被频繁调用,您应该尽一切可能优化您的绘图代码,并在每次调用该方法时尽可能少地绘制。

    如果您需要添加或删除子视图,您应该在视图初始化时执行此操作,或者最迟在layoutSubviews 中执行。

  2. 根本不需要在图像中绘制或使用图像视图。 drawRect: 的全部意义在于绘制到当前的图形上下文中,UIKit 已经设置了它来定位视图的后备存储。

除了这些建议之外,Core Graphics 中不支持角度渐变。但是,对于您的图形,您可以分别设置每个刻度线的颜色并获得一个很好的近似值,这就是我创建上面图像的方式。使用+[UIColor colorWithHue:saturation:brightness:alpha:] 创建颜色,根据刻度角计算色调参数。

如果您将绘图代码分解到一个单独的类中,则可以很容易地使用它直接绘制到视图(在drawRect: 中),或者根据需要绘制到图像。界面如下:

@interface RainbowGaugeAppearance: NSObject

@property (nonatomic) CGFloat startDegrees;
@property (nonatomic) CGFloat endDegrees;
@property (nonatomic) CGFloat degreesPerMajorTick;
@property (nonatomic) int subdivisionsPerMajorTick;
@property (nonatomic) CGFloat tickThickness;
@property (nonatomic) CGFloat startHue;
@property (nonatomic) CGFloat endHue;
@property (nonatomic) CGFloat outerRadiusFraction;
@property (nonatomic) CGFloat minorInnerRadiusFraction;
@property (nonatomic) CGFloat majorInnerRadiusFraction;

- (instancetype _Nonnull)init;
- (void)drawInRect:(CGRect)rect;
@end

以及实现:

@implementation RainbowGaugeAppearance

static CGFloat radiansForDegrees(CGFloat degrees) { return degrees * M_PI / 180; }

- (instancetype _Nonnull)init {
    if (self = [super init]) {
        _startDegrees = 120;
        _endDegrees = _startDegrees + 300;
        _degreesPerMajorTick = 30;
        _subdivisionsPerMajorTick = 10;
        _tickThickness = 4;
        _outerRadiusFraction = 0.95;
        _minorInnerRadiusFraction = 0.87;
        _majorInnerRadiusFraction = 0.81;
        _startHue = 1/ 3.0;
        _endHue = 0;
    }
    return self;
}

- (void)drawInRect:(CGRect)rect {
    CGContextRef gc = UIGraphicsGetCurrentContext();
    CGContextSaveGState(gc); {
        CGContextTranslateCTM(gc, CGRectGetMidX(rect), CGRectGetMidY(rect));
        CGContextSetLineWidth(gc, self.tickThickness);
        CGContextSetLineCap(gc, kCGLineCapButt);

        CGFloat outerRadius = _outerRadiusFraction / 2 * rect.size.width;
        CGFloat minorInnerRadius = _minorInnerRadiusFraction / 2 * rect.size.width;
        CGFloat majorInnerRadius = _majorInnerRadiusFraction / 2 * rect.size.width;

        CGFloat degreesPerTick = _degreesPerMajorTick / _subdivisionsPerMajorTick;
        for (int i = 0; ; ++i) {
            CGFloat degrees = _startDegrees + i * degreesPerTick;
            if (degrees > _endDegrees) { break; }

            CGFloat t = (degrees - _startDegrees) / (_endDegrees - _startDegrees);
            CGFloat hue = _startHue + t * (_endHue - _startHue);
            CGContextSetStrokeColorWithColor(gc, [UIColor colorWithHue:hue saturation:0.8 brightness:1 alpha:1].CGColor);

            CGFloat sine = sin(radiansForDegrees(degrees));
            CGFloat cosine = cos(radiansForDegrees(degrees));
            CGFloat innerRadius = (i % _subdivisionsPerMajorTick == 0) ? majorInnerRadius : minorInnerRadius;
            CGContextMoveToPoint(gc, outerRadius * cosine, outerRadius * sine);
            CGContextAddLineToPoint(gc, innerRadius * cosine, innerRadius * sine);
            CGContextStrokePath(gc);
        }
    } CGContextRestoreGState(gc);
}

@end

然后使用它来绘制视图是微不足道的:

@implementation RainbowGaugeView {
    RainbowGaugeAppearance *_appearance;
}

- (RainbowGaugeAppearance *_Nonnull)appearance {
    if (_appearance == nil) { _appearance = [[RainbowGaugeAppearance alloc] init]; }
    return _appearance;
}

- (void)drawRect:(CGRect)rect {
    [self.appearance drawInRect:self.bounds];
}

@end

【讨论】:

  • 太棒了。同样,建议不要让 cmets 像“谢谢”,但我还能说什么!
  • 如果此处发布的答案对您有所帮助,请点赞。同时点击最有帮助的答案旁边的复选标记以接受它。
【解决方案2】:

就我而言,我应该可以简单地调用 CGContextSetStrokeColorWithColor

然而,现实对“就你而言”并不感兴趣。您正在描述角度渐变。现实情况是,没有用于创建角度渐变的内置 Core Graphics 工具。

但是,您可以使用 AngleGradientLayer 等好的库轻松完成此操作。然后绘制角度渐变并将您的仪表图用作蒙版是一件简单的事情。

通过这种方式,我得到了这个——不是在 Photoshop 中混搭,而是在 iOS 中使用 AngleGradientLayer 完全实时完成,再加上刚刚复制并粘贴的 radials:andSteps:andLineWidth: 方法并用于生成面具:

这是我必须编写的唯一代码。一、生成角度渐变层:

+ (Class)layerClass {
    return [AngleGradientLayer class];
}
- (instancetype)initWithCoder:(NSCoder *)coder {
    self = [super initWithCoder:coder];
    if (self) {
        AngleGradientLayer *l = (AngleGradientLayer *)self.layer;
        l.colors = [NSArray arrayWithObjects:
           (id)[UIColor colorWithRed:1 green:0 blue:0 alpha:1].CGColor,
           (id)[UIColor colorWithRed:0 green:1 blue:0 alpha:1].CGColor,
           nil];
        l.startAngle = M_PI/2.0;
    }
    return self;
}

第二,掩码(这部分在 Swift 中,但这无关紧要):

let im = self.v.radials(300, andSteps: 3, andLineWidth: 10)
let iv = UIImageView(image:im)
self.v.mask = iv

【讨论】:

  • 优秀。所以说不要使用像“谢谢”这样的 cmets,但我不知道为什么,所以感谢您提供简单的解决方案和课程。
  • AngleGradientLayer 的问题在于它在 RGB 空间中插入了一条线性路径。这就是为什么这里的黄色和橙色看起来如此泥泞。通过 HSB 空间进行插值通常会产生更好的结果。
  • 另外,Max,请注意,如果调整仪表视图的大小,您将必须更新 mask 视图的框架,因为自动布局和自动调整大小都不会影响视图的 mask
  • @robmayoff 这是自从他们引入视图蒙版以来我一直想知道的事情。它们很酷,至少它们可以轻松自绘,但不参与子视图的布局,它们真的比图层蒙版更好吗?
  • 是的,这对 Apple 来说确实是一个令人讨厌的疏忽。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-11-18
  • 2012-10-03
  • 1970-01-01
  • 1970-01-01
  • 2014-04-02
  • 2023-03-14
  • 2019-03-20
相关资源
最近更新 更多