【问题标题】:How to draw a gradient line (fading in/out) with Core Graphics/iPhone?如何使用 Core Graphics/iPhone 绘制渐变线(淡入/淡出)?
【发布时间】:2009-08-20 03:52:54
【问题描述】:

我知道如何画一条简单的线:

CGContextSetRGBStrokeColor(context, 1.0, 1.0, 1.0, 1.0);
CGContextMoveToPoint(context, x, y);
CGContextAddLineToPoint(context, x2, y2);
CGContextStrokePath(context);

而且我知道如何做一个渐变矩形,例如:

CGColorSpaceRef myColorspace=CGColorSpaceCreateDeviceRGB();
size_t num_locations = 2;
CGFloat locations[2] = { 1.0, 0.0 };
CGFloat components[8] = { 0.0, 0.0, 0.0, 1.0,    1.0, 1.0, 1.0, 1.0 };

CGGradientRef myGradient = CGGradientCreateWithColorComponents(myColorspace, components, locations, num_locations);

CGPoint myStartPoint, myEndPoint;
myStartPoint.x = 0.0;
myStartPoint.y = 0.0;
myEndPoint.x = 0.0;
myEndPoint.y = 10.0;
CGContextDrawLinearGradient (context, myGradient, myStartPoint, myEndPoint, 0);

但是我怎么能用渐变画一条线,例如从黑色淡入到白色(也可能在另一边淡出到黑色)?

【问题讨论】:

  • 快速说明——此处当前选择的答案不正确。 this answer 所示,可以使用渐变描边任意路径

标签: iphone core-graphics draw gradient


【解决方案1】:

可以使用渐变或任何其他填充效果(例如图案)来描边任意路径

如您所见,描边路径不会使用当前渐变进行渲染。只有填充的路径使用渐变(当您将它们变成剪辑然后绘制渐变时)。

然而,Core Graphics 有一个非常酷的程序 CGContextReplacePathWithStrokedPath,它可以将您打算描边的路径转换为填充时等效

在幕后,CGContextReplacePathWithStrokedPath 在您的笔划路径周围构建一个边缘多边形,并将其切换为您定义的路径。我推测Core Graphics 渲染引擎可能在调用CGContextStrokePath 时这样做无论如何

这是 Apple 的相关文档:

Quartz 使用当前图形上下文的参数创建一个描边路径。创建新路径,以便填充它绘制与抚摸原始路径相同的像素。您可以像使用任何上下文的路径一样使用此路径。例如,您可以通过调用此函数然后调用函数 CGContextClip 来剪辑到路径的描边版本。

因此,将路径转换为可以填充的内容,将其转换为剪辑,然后然后绘制渐变。效果就像你用渐变描边了路径一样。

代码

看起来像这样……

    // Get the current graphics context.
    //
    const CGContextRef context = UIGraphicsGetCurrentContext();

    // Define your stroked path. 
    //
    // You can set up **anything** you like here.
    //
    CGContextAddRect(context, yourRectToStrokeWithAGradient);

    // Set up any stroking parameters like line.
    //
    // I'm setting width. You could also set up a dashed stroke
    // pattern, or whatever you like.
    //
    CGContextSetLineWidth(context, 1);

    // Use the magical call.
    //
    // It turns your _stroked_ path in to a **fillable** one.
    //
    CGContextReplacePathWithStrokedPath(context);

    // Use the current _fillable_ path in to define a clipping region.
    //
    CGContextClip(context);

    // Draw the gradient.
    //
    // The gradient will be clipped to your original path.
    // You could use other fill effects like patterns here.
    //
    CGContextDrawLinearGradient(
      context, 
      yourGradient, 
      gradientTop, 
      gradientBottom, 
      0
    );

补充说明

上面的部分文档值得强调:

Quartz使用当前图形上下文的参数创建一个描边路径

明显的参数是线宽。但是,使用了all线条绘制状态,例如笔划图案、斜接限制、线连接、大写、虚线图案等。这使得该方法非常强大。

有关更多详细信息,请参阅 this answerthis S.O. question

【讨论】:

    【解决方案2】:

    经过几次尝试,我现在确定渐变不会影响笔画,所以我认为用CGContextStrokePath() 绘制渐变线是不可能的。对于水平线和垂直线,解决方案是改用CGContextAddRect(),幸运的是,这正是我所需要的。我换了

    CGContextMoveToPoint(context, x, y);
    CGContextAddLineToPoint(context, x2, y2);
    CGContextStrokePath(context);
    

    CGContextSaveGState(context);
    CGContextAddRect(context, CGRectMake(x, y, width, height));
    CGContextClip(context);
    CGContextDrawLinearGradient (context, gradient, startPoint, endPoint, 0);
    CGContextRestoreGState(context);
    

    一切正常。感谢 Brad Larson 的关键提示。

    【讨论】:

    • 我发现了完全相同的东西。看起来渐变需要首先将剪辑路径(或矩形)应用于图形上下文。也许这是因为渐变是在封闭区域而不是在路径(或矩形)边框本身上呈现的?
    • 这不能渲染斜线?
    【解决方案3】:

    画完线后就可以调用

    CGContextClip(context);
    

    将进一步的绘图剪辑到您的线条区域。如果您绘制渐变,它现在应该包含在线条区域内。请注意,如果您只想显示渐变而不是下方的线条,则需要为线条使用清晰的颜色。

    有可能线条太细而无法显示渐变,在这种情况下,您可以使用CGContextAddRect() 定义较粗的区域。

    我在回答here 中提供了一个使用此上下文剪辑的更详细示例。

    【讨论】:

    • 我试过你的建议:CGContextSetLineWidth(context, 10.0); // 应该足够粗 CGContextSetRGBStrokeColor(context, 0.0, 0.0, 0.0, 0.0); CGContextMoveToPoint(上下文, 50,50); CGContextAddLineToPoint(上下文, 100,100); CGContextStrokePath(上下文); CGContextClip(上下文); CGContextDrawLinearGradient (context, myGradient, myStartPoint, myEndPoint, 0);但这会引发 : doClip: empty path。文档说 CGContextStrokePath(context);清除路径 - 没有此方法不会引发错误,但根本不会绘制任何内容。有什么想法吗?
    【解决方案4】:

    您可以使用核心动画层。您可以通过设置其路径属性为您的线条使用CAShaperLayer,然后您可以使用CAGradientLayer 作为您的形状图层的图层蒙版,这将导致线条褪色。

    将您的 CGContext... 调用替换为对 CGPath... 的调用以创建线路路径。使用该路径在图层上设置路径字段。然后在渐变层中,指定要使用的颜色(可能是黑到白),然后将蒙版设置为线条层,如下所示:

     [gradientLayer setMask:lineLayer];
    

    渐变层最酷的地方在于,它允许您指定渐变将停止的位置列表,以便您可以淡入和淡出。它仅支持线性渐变,但听起来可能适合您的需求。

    如果您需要澄清,请告诉我。

    编辑:现在我想到了,只需创建一个 CAGradientLayer,它就是您想要的线的宽度/高度。指定渐变颜色(黑色到白色或黑色到清晰的颜色)以及 startPoint 和 endtPoints,它应该可以满足您的需求。

    【讨论】:

    • 你说得对,这完全符合我的需要,但不幸的是,这仅适用于 iPhone OS 版本 3,我想至少使用 2.2。还是谢谢...
    • 我猜你的意思是 [gradientLayer setMask:LineLayer]; - 对?你用 CAShapeLayer 遮罩 CAGradientLayer。
    • 太棒了。这样做学到了很多东西。
    • @Palimondo 两者都是正确的,这取决于您是想要一条淡出的纯色线,还是想要一条一直保持纯色的渐变填充线。
    【解决方案5】:

    我创建了 Benjohn 答案的 Swift 版本。

    class MyView: UIView {
        override func draw(_ rect: CGRect) {
        super.draw(rect)
    
        guard let context = UIGraphicsGetCurrentContext() else {
            return
        }
    
        context.addPath(createPath().cgPath)
        context.setLineWidth(15)
        context.replacePathWithStrokedPath()
        context.clip()
    
        let gradient = CGGradient(colorsSpace: nil, colors: [UIColor.red.cgColor, UIColor.yellow.cgColor, UIColor.green.cgColor] as CFArray, locations: [0, 0.4, 1.0])!
        let radius:CGFloat = 200
        let center = CGPoint(x: bounds.width / 2, y: bounds.height / 2)
        context.drawRadialGradient(gradient, startCenter: center, startRadius: 10, endCenter: center, endRadius: radius, options: .drawsBeforeStartLocation)
        }
    }
    

    如果 createPath() 方法创建了一个三角形,你会得到这样的结果:

    希望这对任何人都有帮助!

    【讨论】:

      【解决方案6】:
      CGContextMoveToPoint(context, frame.size.width-200, frame.origin.y+10);
      CGContextAddLineToPoint(context, frame.size.width-200, 100-10);
      CGFloat colors[16] = { 0,0, 0, 0,
          0, 0, 0, .8,
          0, 0, 0, .8,
          0, 0,0 ,0 };
      CGColorSpaceRef baseSpace = CGColorSpaceCreateDeviceRGB();
      CGGradientRef gradient = CGGradientCreateWithColorComponents(baseSpace, colors, NULL, 4);
      
      CGContextSaveGState(context);
      CGContextAddRect(context, CGRectMake(frame.size.width-200,10, 1, 80));
      CGContextClip(context);
      CGContextDrawLinearGradient (context, gradient, CGPointMake(frame.size.width-200, 10), CGPointMake(frame.size.width-200,80), 0);
      CGContextRestoreGState(context);
      

      它对我有用。

      【讨论】:

        【解决方案7】:

        Swift 5 版本

        这段代码对我有用。

        override func draw(_ rect: CGRect) {
            super.draw(rect)
            
            guard let context = UIGraphicsGetCurrentContext() else {
                return
            }
            
            drawGradientBorder(rect, context: context)
        }
        
        fileprivate 
        func drawGradientBorder(_ rect: CGRect, context: CGContext) {
            context.saveGState()
            let path = ribbonPath()
            context.setLineWidth(5.0)
            context.addPath(path.cgPath)
            context.replacePathWithStrokedPath()
            context.clip()
            
            let baseSpace = CGColorSpaceCreateDeviceRGB()
            let gradient = CGGradient(colorsSpace: baseSpace, colors: [start.cgColor, end.cgColor] as CFArray, locations: [0.0 ,1.0])!
            context.drawLinearGradient(gradient, start: CGPoint(x: 0, y: rect.height/2), end: CGPoint(x: rect.width, y: rect.height/2), options: [])
            context.restoreGState()
        }
        
        fileprivate 
        func ribbonPath() -> UIBezierPath {
            let path = UIBezierPath()
            path.move(to: CGPoint.zero)
            path.addLine(to: CGPoint(x: bounds.maxX - gradientWidth, y: 0))
            path.addLine(to: CGPoint(x: bounds.maxX - gradientWidth - ribbonGap, y: bounds.maxY))
            path.addLine(to: CGPoint(x: 0, y: bounds.maxY))
            path.close()
            return path
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2018-03-10
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多