如何使用倾斜的渐变填充CAShapeLayer

16
如何用渐变色填充 CAShapeLayer() 并使其倾斜45度?
例如,在Image 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

渐变正方形旁的纯色正方形

5个回答

37

为什么不使用具有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)

enter image description here

注意:添加了一个贝塞尔曲线路径来绘制圆形,因为对于正方形而言,它可以不使用蒙版。

1
我喜欢游乐场截图,它展示了代码和结果图像。很不错的设计(已投票)。但为什么不提供整个游乐场的代码呢?这样读者就可以通过简单的复制粘贴来重现您的运行代码并进行实验。 - Duncan C
我考虑过这个问题,但是我认为这是为一个应用程序而设计的,所以只提供了在应用程序中粘贴的部分。 - Alistra
请记住,Stack Overflow的帖子不仅对原始的SO有用。其他人(比如我)也会来到这里并希望使用/学习你发布的代码。我认为"很酷。有一个带渐变层的工作场所。添加一个旋转动画让渐变在圆形中旋转将是很有趣的..."但是你的代码是一张图片,而不是文本。我不想打那么多字,所以我的修改欲望就消失了。 - Duncan C
Edited to your suggestion - Alistra
谢谢,@Alistra 这看起来很有前途。我会尽快查看并尝试一下。+1。 - user4806509
@DuncanC的改来改去的兴致已经消失了,所以你写下了那条评论 :) - kot331107

10

轻松将渐变应用到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)

数学解释

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

Example Angles

如果您对我的思路感兴趣,我制作了一张表格来直观地展示我从 360° 所做的事情。

Table


2

我认为这是

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

这段文本涉及到IT技术方面的内容,需要将底部右侧的第一种颜色转变为顶部左侧的第二种颜色。如果您想要将顶部右侧的第一种颜色和底部左侧的第二种颜色进行替换,则应该执行以下操作:

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)

1

@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)

0

如果您不想使用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);
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接