【问题标题】:How to fill a CAShapeLayer with an angled gradient如何用有角度的渐变填充 CAShapeLayer
【发布时间】:2016-12-07 14:14:37
【问题描述】:

如何用渐变和 45 度角填充 CAShapeLayer()

例如,在图 1 中,以下代码绘制了一个正方形并将图层填充为蓝色 (UIColor.blueColor().CGColor)。

但是,如何使用 Image 2 中从蓝色到红色的 45 度角的渐变填充它(即 UIColor.blueColor().CGColorUIColor.redColor().CGColor)?

代码:

let path = UIBezierPath()
path.moveToPoint(CGPoint(x: 0, y: 0))
path.addLineToPoint(CGPoint(x: 0, y: 100))
path.addLineToPoint(CGPoint(x: 100, y: 100))
path.addLineToPoint(CGPoint(x: 100, y: 0))
path.closePath()

let shape = CAShapeLayer()
shape.path = path.CGPath
shape.fillColor = UIColor.blueColor().CGColor

【问题讨论】:

    标签: ios swift optimization uibezierpath cashapelayer


    【解决方案1】:

    为什么不使用具有startPointendPoint 属性的CAGradientLayer

    你可以这样做:

    import UIKit
    import PlaygroundSupport
    
    let frame = CGRect(x: 0, y: 0, width: 100, height: 100)
    let view = UIView(frame: frame)
    
    PlaygroundPage.current.liveView = view
    
    let path = UIBezierPath(ovalIn: frame)
    
    let shape = CAShapeLayer()
    shape.frame = frame
    shape.path = path.cgPath
    shape.fillColor = UIColor.blue.cgColor
    
    let gradient = CAGradientLayer()
    gradient.frame = frame
    gradient.colors = [UIColor.blue.cgColor,
                       UIColor.red.cgColor]
    gradient.startPoint = CGPoint(x: 0, y: 1)
    gradient.endPoint = CGPoint(x: 1, y: 0)
    gradient.mask = shape
    
    view.layer.addSublayer(gradient)
    

    注意:为圆形添加了贝塞尔路径,因为它可以在没有正方形遮罩的情况下工作。

    【讨论】:

    • 我喜欢显示代码和生成图像的操场截图。很好的接触(投票。)为什么不提供操场的整个代码呢?这样,读者可以通过简单的复制粘贴和过期来重现您正在运行的代码。
    • 我想过,但我认为这是一个应用程序,只是提供了要粘贴到应用程序中的部分。
    • 请记住,SO 帖子不仅仅对原始 SO 有用。其他人(比如我)过来并希望使用您发布的代码/从中学习。我想“很酷。一个带有渐变层的工作游乐场。添加一个旋转动画将渐变旋转成一个圆圈会很有趣......”但是您的代码是图像,而不是文本。我没有那么喜欢打字,所以修补的冲动消失了。
    • 已根据您的建议编辑
    • 谢谢,@Alistra 这看起来很有希望。我会看看并尽快尝试一下。 +1。
    【解决方案2】:

    轻松将渐变应用到 CALayer

    Swift 4.2,Xcode 10.0

    虽然上述解决方案仅适用于 45° 等微不足道的角度,但我的代码能够将渐变设置为任何给定角度。

    public extension CALayer {
    
        public func applyGradient(of colors: UIColor..., atAngle angle: CGFloat) -> CAGradientLayer {
            let gradient = CAGradientLayer()
            gradient.frame = frame
            gradient.colors = colors
            gradient.calculatePoints(for: angle)
            gradient.mask = self
            return gradient
        }
    
    }
    
    
    public extension CAGradientLayer {
    
        /// Sets the start and end points on a gradient layer for a given angle.
        ///
        /// - Important:
        /// *0°* is a horizontal gradient from left to right.
        ///
        /// With a positive input, the rotational direction is clockwise.
        ///
        ///    * An input of *400°* will have the same output as an input of *40°*
        ///
        /// With a negative input, the rotational direction is clockwise.
        ///
        ///    * An input of *-15°* will have the same output as *345°*
        ///
        /// - Parameters:
        ///     - angle: The angle of the gradient.
        ///
        public func calculatePoints(for angle: CGFloat) {
    
    
            var ang = (-angle).truncatingRemainder(dividingBy: 360)
    
            if ang < 0 { ang = 360 + ang }
    
            let n: CGFloat = 0.5
    
            let tanx: (CGFloat) -> CGFloat = { tan($0 * CGFloat.pi / 180) }
    
            switch ang {
    
            case 0...45, 315...360:
                let a = CGPoint(x: 0, y: n * tanx(ang) + n)
                let b = CGPoint(x: 1, y: n * tanx(-ang) + n)
                startPoint = a
                endPoint = b
    
            case 45...135:
                let a = CGPoint(x: n * tanx(ang - 90) + n, y: 1)
                let b = CGPoint(x: n * tanx(-ang - 90) + n, y: 0)
                startPoint = a
                endPoint = b
    
            case 135...225:
                let a = CGPoint(x: 1, y: n * tanx(-ang) + n)
                let b = CGPoint(x: 0, y: n * tanx(ang) + n)
                startPoint = a
                endPoint = b
    
            case 225...315:
                let a = CGPoint(x: n * tanx(-ang - 90) + n, y: 0)
                let b = CGPoint(x: n * tanx(ang - 90) + n, y: 1)
                startPoint = a
                endPoint = b
    
            default:
                let a = CGPoint(x: 0, y: n)
                let b = CGPoint(x: 1, y: n)
                startPoint = a
                endPoint = b
    
            }
        }
    
    }
    

    用法:

    let layer = CAShapeLayer()
    
    // Setup layer...
    
    // Gradient Direction: →
    let gradientLayer1 = layer.applyGradient(of: UIColor.yellow, UIColor.red, at: 0)
    
    // Gradient Direction: ↗︎
    let gradientLayer2 = layer.applyGradient(of: UIColor.purple, UIColor.yellow, UIColor.green, at: -45)
    
    // Gradient Direction: ←
    let gradientLayer3 = layer.applyGradient(of: UIColor.yellow, UIColor.blue, UIColor.green, at: 180)
    
    // Gradient Direction: ↓
    let gradientLayer4 = layer.applyGradient(of: UIColor.red, UIColor.blue, at: 450)
    

    数学解释

    所以实际上我最近花了很多时间试图自己回答这个问题。下面是一些示例角度,只是为了帮助理解和可视化顺时针旋转方向。

    如果您对我的计算方式感兴趣,我制作了一张表格,从 360° 基本上可视化我在做什么。

    【讨论】:

      【解决方案3】:

      我觉得是

      shape.startPoint = CGPoint(x: 1.0, y: 0.0)
      shape.endPoint = CGPoint(x: 0.0, y: 1.0)
      

      ,它是右下角的第一种颜色到左上角的第二种颜色。如果你想要右上角的第一种颜色和左下角的第二种颜色,那么你应该有

      shape.startPoint = CGPoint(x: 1.0, y: 1.0)
      shape.endPoint = CGPoint(x: 0.0, y: 0.0)
      

      左上角第一种颜色,右下角第二种颜色

      shape.startPoint = NSMakePoint(x: 0.0, y: 1.0)
      shape.endPoint = NSMakePoint(x: 1.0, y: 0.0)
      

      第一种颜色在左下角,第二种颜色在右上角

      shape.startPoint = CGPoint(x: 0.0, y: 0.0)
      shape.endPoint = CGPoint(x: 1.0, y: 1.0)
      

      【讨论】:

        【解决方案4】:

        如果您的形状位于屏幕的左上角,@Alistra 答案有效。如果您尝试移动形状的位置,您会注意到形状被截断(如果它甚至显示出来,取决于您的 x 和 y 值)

        要解决此问题,请为渐变层和形状层使用两个不同的框架。将形状图层的x,y 坐标设置为0,0。然后将渐变层的x,y 坐标设置为您希望它在屏幕上的位置。

            let gradientFrame = CGRect(x: 100,
                                       y: 150,
                                       width: 150,
                                       height: 150)
            let circleFrame = CGRect(x: 0,
                                     y: 0,
                                     width: 150,
                                     height: 150)
            let circle = CAShapeLayer()
            circle.frame = circleFrame
            circle.path = UIBezierPath(ovalIn: circleFrame).cgPath
        
            let gradient = CAGradientLayer()
            gradient.frame = gradientFrame
            gradient.startPoint = CGPoint(x: 0, y: 0)
            gradient.endPoint = CGPoint(x: 1, y: 1)
            gradient.colors = [UIColor.blue.cgColor,
                               UIColor.red.cgColor]
            gradient.mask = circle
            view.layer.addSublayer(gradient)
        

        【讨论】:

          【解决方案5】:

          如果您不想使用 CAGradientLayer,根据 Noah Wilder 的回答,对于 Objective-C:

          -(void)drawRect:(CGRect)rect {
          
          //create theoretical circle
          float w = self.frame.size.width;
          float h = self.frame.size.height;
          
          NSDictionary * points = [self pointsForAngle:angle width:w height:h];
          CGPoint start = [points[@"start"] CGPointValue];
          CGPoint end = [points[@"end"] CGPointValue];
          
          //1. create vars
          float increment = 1.0f / (colours.count-1);
          CGFloat * locations = (CGFloat *)malloc((int)colours.count*sizeof(CGFloat));
          CFMutableArrayRef mref = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
          
          //2. go through the colours, creating cgColors and locations
          for (int n = 0; n < colours.count; n++){
              CFArrayAppendValue(mref, (id)[colours[n] CGColor]);
              locations[n]=(n*increment);
          }
          
          //3. create gradient
          CGContextRef ref = UIGraphicsGetCurrentContext();
          CGColorSpaceRef spaceRef = CGColorSpaceCreateDeviceRGB();
          CGGradientRef gradientRef = CGGradientCreateWithColors(spaceRef, mref, locations);
          
          CGContextDrawLinearGradient(ref, gradientRef, start, end, kCGGradientDrawsAfterEndLocation);
          free(locations);
          CFRelease(mref);
          CGColorSpaceRelease(spaceRef);
          CGGradientRelease(gradientRef);
          }
          
          -(NSDictionary *)pointsForAngle:(float)angle width:(float)width height:(float)height{
              
              float n = 0.5f;
              
              CGPoint start = CGPointZero;
              CGPoint end = CGPointZero;
              
              if (angle >= 315 || angle < 45){
                  
                  start = CGPointMake(n * [self tanThis:angle] + n, 0);
                  end = CGPointMake(n * [self tanThis:-angle] + n, 1);
                  
              } else if (angle >= 45 && angle < 135){
                  
                  start = CGPointMake(0, n * [self tanThis:angle-90] + n);
                  end = CGPointMake(1, n * [self tanThis:-angle-90] + n);
                  
              } else if (angle >= 135 && angle < 225){
                  
                  start = CGPointMake(n * [self tanThis:-angle] + n, 1);
                  end = CGPointMake(n * [self tanThis:angle] + n, 0);
                  
              } else if (angle >= 225 && angle < 315){
                  
                  start = CGPointMake(1, n * [self tanThis:-angle-90] + n);
                  end = CGPointMake(0, n * [self tanThis:angle-90] + n);
          
              }
              
              start = CGPointMake(start.x * width, start.y * height);
              end = CGPointMake(end.x * width, end.y * height);
              return @{@"start":@(start), @"end":@(end)};
              
          }
          -(float)tanThis:(float)angle{
              return tan(angle * M_PI / 180);
          }
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2021-10-24
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2016-12-14
            • 2011-11-12
            • 1970-01-01
            相关资源
            最近更新 更多