CAGradientLayer对角线渐变

编程入门 行业动态 更新时间:2024-10-28 08:28:20
本文介绍了CAGradientLayer对角线渐变的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧! 问题描述

我使用以下CAGradientLayer:

让图层= CAGradientLayer() layer.colors = [ UIColor.redColor()。CGColor, UIColor.greenColor()。CGColor, UIColor.blueColor()。CGColor ] layer.startPoint = CGPointMake(0,1) layer.endPoint = CGPointMake(1,0) layer.locations = [0.0,0.6,1.0]

但是当我为图层设置bounds属性时,它只是拉伸了一个方形渐变。我需要一个像Sketch 3 app图像的结果(见上文)。

我怎样才能实现这个目标?

解决方案

更新:在

代码

import UIKit ///最后更新时间:4/3/17。 ///有关详细信息,请参阅stackoverflow/a/43176174。 public enum LinearGradientFixer { public static func fixPoints(start:CGPoint,end:CGPoint,bounds:CGSize) - > (CGPoint,CGPoint){ //命名约定: // - a:指向a // - ab:从a到b //的线段 - abLine:穿过a和b //的线 - lineAB:穿过A和B 的线// lineSegmentAB:从A传递到B 的线段如果开始.x == end.x || start.y == end.y { // Apple的水平和垂直渐变实现很好返回(开始,结束)} // 1.转换为绝对坐标 let startEnd = LineSegment(start,end) let ab = startEnd.multiplied(multipliers:(x:bounds.width,y:bounds.height))设a = ab.p1 让b = ab.p2 // 2.计算垂直平分线让cd = ab.perpendicularBisector / / 3.缩放到方坐标 let multipliers = calculateMultipliers(bounds:bounds) let lineSegmentCD = cd.multiplied(multipliers:multipliers) // 4.创建缩放垂直bisector let lineSegmentEF = lineSegmentCD.perpendicularBisector // 5.将尺寸缩小回矩形 let ef = lineSegmentEF.divided(除数:乘数) // 6.扩展行让efLine = ef.line // 7.扩展两行从a和b并行到cd 让aParallelLine = Line(m:cd.slope,p:a) let bParallelLine = Line(m:cd.slope,p:b) // 8.找到这些行的交集 let g = efLine.intersection(with:aParallelLine) let h = efLine.intersection(with:bParallelLine) 如果设g = g,设h = h { // 9.转换为相对坐标 let gh = LineSegment(g,h) let result = gh.divided(divisors: (x:bounds.width,y:bounds.height)) return(result.p1,result.p2)} return(start,end)} private static func unitTest(){ let w = 320.0 let h = 60.0 let bounds = CGSize(width:w,height:h)设a = CGPoint(x:138.5,y:11.5)让b = CGPoint(x:151.5,y:53.5)让ab = LineSegment(a,b) let startEnd = ab.divided(divisors:(x:bounds.width,y:bounds.height)) let start = startEnd.p1 le t end = startEnd.p2 let points = fixPoints(start:start,end:end,bounds:bounds) let pointsSegment = LineSegment(points.0,points。 1) let result = pointsSegment.multiplied(multipliers:(x:bounds.width,y:bounds.height)) print(result.p1)// expected:(90.6119039567129, 26.3225059181603) print(result.p2)//预期:(199.388096043287,38.6774940818397)} } private func calculateMultipliers(bounds:CGSize) - > (x:CGFloat,y:CGFloat){ if bounds.height< = bounds.width { return(x:1,y:bounds.width / bounds.height)} else { return(x:bounds.height / bounds.width,y:1)} } private struct LineSegment { let p1 :CGPoint let p2:CGPoint init(_ p1:CGPoint,_ p2:CGPoint){ self.p1 = p1 self.p2 = p2 } init(p1:CGPoint,m:CGFloat,距离:CGFloat){ self.p1 = p1 let line = Line(m :m,p:p1) let measuringPoint = line.point(x:p1.x + 1) let measuringDeltaH = LineSegment(p1,measuringPoint).distance let deltaX = distance / measuringDeltaH self.p2 = line.point(x:p1.x + deltaX)} var length:CGFloat { let dx = p2.x - p1.x 让dy = p2.y - p1.y 返回sqrt(dx * dx + dy * dy)} var距离:CGFloat { 返回p1.x< = p2.x?长度:-length } var midpoint:CGPoint {返回CGPoint(x:(p1.x + p2.x)/ 2,y:(p1.y + p2.y) / 2)} var slope:CGFloat { return(p2.y-p1.y)/(p2.x-p1.x)} var perpendicularSlope:CGFloat { return -1 / slope } var line:Line { return Line(p1,p2)} var perpendicularBisector :LineSegment { let p1 = LineSegment(p1:midpoint,m:verticalSlope,distance:-distance / 2).p2 let p2 = LineSegment(p1:midpoint,m:perpendicularSlope,distance:distance / 2).p2 返回LineSegment(p1,p2)} func multiplied(乘数:(x:CGFloat,y:CGFloat)) - > LineSegment {返回LineSegment( CGPoint(x:p1.x * multipliers.x,y:p1.y * multipliers.y), CGPoint(x:p2.x *乘数。 x,y:p2.y * multipliers.y))} func divide(divisors:(x:CGFloat,y:CGFloat)) - > LineSegment { return multiplied(multipliers:(x:1 / divisors.x,y:1 / divisors.y))} } private struct Line { let m:CGFloat let b:CGFloat /// y = mx + b init(m:CGFloat,b:CGFloat){ self.m = m self.b = b } /// y-y1 = m(x-x1) init(m: CGFloat,p:CGPoint){ // y = m(x-x1)+ y1 // y = mx-mx1 + y1 // y = mx +(y1 - mx1 ) // b = y1 - mx1 self.m = m self.b = py - m * px } init(_ p1:CGPoint,_ p2:CGPoint){ self.init(m:LineSegment(p1,p2).slope,p:p1)} func y(x :CGFloat) - > CGFloat { return m * x + b } func point(x:CGFloat) - > CGPoint {返回CGPoint(x:x,y:y(x:x))} func十字路口(带线:线) - > CGPoint? { //第1行:y = mx + b //第2行:y = nx + c // mx + b = nx + c // mx- nx = cb // x(mn)= cb // x =(cb)/(mn)让n = line.m 让c = line.b 如果mn == 0 { //行是并行返回nil } 让x =(cb)/(mn)返回点(x:x)} }

无论矩形如何,证明它都有效尺寸

我尝试了这个视图 size = 320x60 , gradient = [red @ 0,绿色@ 0.5,蓝色@ 1] , startPoint =(0,1), endPoint =(1 ,0)。

草图3:

使用上述代码实际生成的iOS屏幕截图:

请注意绿线的角度看起来100%准确。不同之处在于红色和蓝色的混合方式。我不知道是不是因为我正在错误地计算起点/终点,或者它只是Apple混合渐变与Sketch混合渐变的方式不同。

I use the following CAGradientLayer:

let layer = CAGradientLayer() layer.colors = [ UIColor.redColor().CGColor, UIColor.greenColor().CGColor, UIColor.blueColor().CGColor ] layer.startPoint = CGPointMake(0, 1) layer.endPoint = CGPointMake(1, 0) layer.locations = [0.0, 0.6, 1.0]

But when I set bounds property for the layer, it just stretches a square gradient. I need a result like in Sketch 3 app image (see above).

How can I achieve this?

解决方案

Update: Use context.drawLinearGradient() instead of CAGradientLayer in a manner similar to the following. It will draw gradients that are consistent with Sketch/Photoshop.

If you absolutely must use CAGradientLayer, then here is the math you'll need to use...

It took some time to figure out, but from careful observation, I found out that Apple's implementation of gradients in CAGradientLayer is pretty odd:

  • First it converts the view to a square.
  • Then it applies the gradient using start/end points.
  • The middle gradient will indeed form a 90 degree angle in this resolution.
  • Finally, it squishes the view down to the original size.
  • This means that the middle gradient will no longer form a 90 degree angle in the new size. This contradicts the behavior of virtually every other paint application: Sketch, Photoshop, etc.

    If you want to implement start/end points as it works in Sketch, you'll need to translate the start/end points to account for the fact that Apple is going to squish the view.

    Steps to perform (Diagrams)

    Code

    import UIKit /// Last updated 4/3/17. /// See stackoverflow/a/43176174 for more information. public enum LinearGradientFixer { public static func fixPoints(start: CGPoint, end: CGPoint, bounds: CGSize) -> (CGPoint, CGPoint) { // Naming convention: // - a: point a // - ab: line segment from a to b // - abLine: line that passes through a and b // - lineAB: line that passes through A and B // - lineSegmentAB: line segment that passes from A to B if start.x == end.x || start.y == end.y { // Apple's implementation of horizontal and vertical gradients works just fine return (start, end) } // 1. Convert to absolute coordinates let startEnd = LineSegment(start, end) let ab = startEnd.multiplied(multipliers: (x: bounds.width, y: bounds.height)) let a = ab.p1 let b = ab.p2 // 2. Calculate perpendicular bisector let cd = ab.perpendicularBisector // 3. Scale to square coordinates let multipliers = calculateMultipliers(bounds: bounds) let lineSegmentCD = cd.multiplied(multipliers: multipliers) // 4. Create scaled perpendicular bisector let lineSegmentEF = lineSegmentCD.perpendicularBisector // 5. Unscale back to rectangle let ef = lineSegmentEF.divided(divisors: multipliers) // 6. Extend line let efLine = ef.line // 7. Extend two lines from a and b parallel to cd let aParallelLine = Line(m: cd.slope, p: a) let bParallelLine = Line(m: cd.slope, p: b) // 8. Find the intersection of these lines let g = efLine.intersection(with: aParallelLine) let h = efLine.intersection(with: bParallelLine) if let g = g, let h = h { // 9. Convert to relative coordinates let gh = LineSegment(g, h) let result = gh.divided(divisors: (x: bounds.width, y: bounds.height)) return (result.p1, result.p2) } return (start, end) } private static func unitTest() { let w = 320.0 let h = 60.0 let bounds = CGSize(width: w, height: h) let a = CGPoint(x: 138.5, y: 11.5) let b = CGPoint(x: 151.5, y: 53.5) let ab = LineSegment(a, b) let startEnd = ab.divided(divisors: (x: bounds.width, y: bounds.height)) let start = startEnd.p1 let end = startEnd.p2 let points = fixPoints(start: start, end: end, bounds: bounds) let pointsSegment = LineSegment(points.0, points.1) let result = pointsSegment.multiplied(multipliers: (x: bounds.width, y: bounds.height)) print(result.p1) // expected: (90.6119039567129, 26.3225059181603) print(result.p2) // expected: (199.388096043287, 38.6774940818397) } } private func calculateMultipliers(bounds: CGSize) -> (x: CGFloat, y: CGFloat) { if bounds.height <= bounds.width { return (x: 1, y: bounds.width/bounds.height) } else { return (x: bounds.height/bounds.width, y: 1) } } private struct LineSegment { let p1: CGPoint let p2: CGPoint init(_ p1: CGPoint, _ p2: CGPoint) { self.p1 = p1 self.p2 = p2 } init(p1: CGPoint, m: CGFloat, distance: CGFloat) { self.p1 = p1 let line = Line(m: m, p: p1) let measuringPoint = line.point(x: p1.x + 1) let measuringDeltaH = LineSegment(p1, measuringPoint).distance let deltaX = distance/measuringDeltaH self.p2 = line.point(x: p1.x + deltaX) } var length: CGFloat { let dx = p2.x - p1.x let dy = p2.y - p1.y return sqrt(dx * dx + dy * dy) } var distance: CGFloat { return p1.x <= p2.x ? length : -length } var midpoint: CGPoint { return CGPoint(x: (p1.x + p2.x)/2, y: (p1.y + p2.y)/2) } var slope: CGFloat { return (p2.y-p1.y)/(p2.x-p1.x) } var perpendicularSlope: CGFloat { return -1/slope } var line: Line { return Line(p1, p2) } var perpendicularBisector: LineSegment { let p1 = LineSegment(p1: midpoint, m: perpendicularSlope, distance: -distance/2).p2 let p2 = LineSegment(p1: midpoint, m: perpendicularSlope, distance: distance/2).p2 return LineSegment(p1, p2) } func multiplied(multipliers: (x: CGFloat, y: CGFloat)) -> LineSegment { return LineSegment( CGPoint(x: p1.x * multipliers.x, y: p1.y * multipliers.y), CGPoint(x: p2.x * multipliers.x, y: p2.y * multipliers.y)) } func divided(divisors: (x: CGFloat, y: CGFloat)) -> LineSegment { return multiplied(multipliers: (x: 1/divisors.x, y: 1/divisors.y)) } } private struct Line { let m: CGFloat let b: CGFloat /// y = mx+b init(m: CGFloat, b: CGFloat) { self.m = m self.b = b } /// y-y1 = m(x-x1) init(m: CGFloat, p: CGPoint) { // y = m(x-x1) + y1 // y = mx-mx1 + y1 // y = mx + (y1 - mx1) // b = y1 - mx1 self.m = m self.b = p.y - m*p.x } init(_ p1: CGPoint, _ p2: CGPoint) { self.init(m: LineSegment(p1, p2).slope, p: p1) } func y(x: CGFloat) -> CGFloat { return m*x + b } func point(x: CGFloat) -> CGPoint { return CGPoint(x: x, y: y(x: x)) } func intersection(with line: Line) -> CGPoint? { // Line 1: y = mx + b // Line 2: y = nx + c // mx+b = nx+c // mx-nx = c-b // x(m-n) = c-b // x = (c-b)/(m-n) let n = line.m let c = line.b if m-n == 0 { // lines are parallel return nil } let x = (c-b)/(m-n) return point(x: x) } }

    Proof it works regardless of rectangle size

    I tried this with a view size=320x60, gradient=[red@0,green@0.5,blue@1], startPoint = (0,1), and endPoint = (1,0).

    Sketch 3:

    Actual generated iOS screenshot using the code above:

    Note that the angle of the green line looks 100% accurate. The difference lies in how the red and blue are blended. I can't tell if that's because I'm calculating the start/end points incorrectly, or if it's just a difference in how Apple blends gradients vs. how Sketch blends gradients.

    更多推荐

    CAGradientLayer对角线渐变

    本文发布于:2023-11-30 10:21:21,感谢您对本站的认可!
    本文链接:https://www.elefans.com/category/jswz/34/1649636.html
    版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
    本文标签:对角线   CAGradientLayer

    发布评论

    评论列表 (有 0 条评论)
    草根站长

    >www.elefans.com

    编程频道|电子爱好者 - 技术资讯及电子产品介绍!