iOS – 圆形渐变

我正在画一个圆形的渐变。

let backgroundView:UIView = UIView() let backgroundLayer:CAShapeLayer = CAShapeLayer() let gradient:CAGradientLayer = CAGradientLayer() ... backgroundLayer.frame = CGRectMake(0, 0, backgroundDiameter, backgroundDiameter) backgroundLayer.backgroundColor = UIColor.clearColor().CGColor backgroundLayer.strokeColor = backgroundStrokeColor backgroundLayer.fillColor = backgroundFillColor gradient.colors = [UIColor(red: 0.5, green: 0.5, blue: 0.9, alpha: 1.0).CGColor, UIColor(red: 0.9, green: 0.9, blue: 0.3, alpha: 1.0).CGColor] gradient.locations = [0.01, 0.8] gradient.frame = backgroundLayer.frame backgroundView.frame = CGRectMake(0, 0, backgroundDiameter, backgroundDiameter) backgroundView.backgroundColor = UIColor.clearColor() backgroundView.center = ringControlCenter backgroundLayer.insertSublayer(gradient, atIndex: 1) backgroundLayer.path = CGPathCreateWithEllipseInRect(backgroundLayer.frame, nil) backgroundView.layer.addSublayer(backgroundLayer) self.addSubview(backgroundView) 

但是,渐变似乎不受以下方面的影响:

 backgroundLayer.path = CGPathCreateWithEllipseInRect(backgroundLayer.frame, nil) 

而且还有其初始的形状。 有没有一种方法来使用椭圆形图层来遮罩渐变而不使用CGContext *指令?

谢谢,

MG

我已经在Swift 3.x中翻译了Leo库 ,换句话说,好的WCGradientCircleLayer是在Objective-C中编写的 (它的工作原理和预期的一样)

 import UIKit class WCGraintCircleLayer: CALayer { override init () { super.init() } convenience init(bounds:CGRect,position:CGPoint,fromColor:UIColor,toColor:UIColor,linewidth:CGFloat,toValue:CGFloat) { self.init() self.bounds = bounds self.position = position let colors : [UIColor] = self.graint(fromColor: fromColor, toColor:toColor, count:4) for i in 0..<colors.count-1 { let graint = CAGradientLayer() graint.bounds = CGRect(origin:CGPoint.zero, size: CGSize(width:bounds.width/2,height:bounds.height/2)) let valuePoint = self.positionArrayWith(bounds: self.bounds)[i] graint.position = valuePoint print("iesimo graint position: \(graint.position)") let fromColor = colors[i] let toColor = colors[i+1] let colors : [CGColor] = [fromColor.cgColor,toColor.cgColor] let stopOne: CGFloat = 0.0 let stopTwo: CGFloat = 1.0 let locations : [CGFloat] = [stopOne,stopTwo] graint.colors = colors graint.locations = locations as [NSNumber]? // with Swift 2 and Swift 3 you can cast directly a `CGFloat` value to `NSNumber` and back graint.startPoint = self.startPoints()[i] graint.endPoint = self.endPoints()[i] self.addSublayer(graint) //Set mask let shapelayer = CAShapeLayer() let rect = CGRect(origin:CGPoint.zero,size:CGSize(width:self.bounds.width - 2 * linewidth,height: self.bounds.height - 2 * linewidth)) shapelayer.bounds = rect shapelayer.position = CGPoint(x:self.bounds.width/2,y: self.bounds.height/2) shapelayer.strokeColor = UIColor.blue.cgColor shapelayer.fillColor = UIColor.clear.cgColor shapelayer.path = UIBezierPath(roundedRect: rect, cornerRadius: rect.width/2).cgPath shapelayer.lineWidth = linewidth shapelayer.lineCap = kCALineCapRound shapelayer.strokeStart = 0.010 let finalValue = (toValue*0.99) shapelayer.strokeEnd = finalValue//0.99; self.mask = shapelayer } } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } func layerWithWithBounds(bounds:CGRect, position:CGPoint, fromColor:UIColor, toColor:UIColor, linewidth : CGFloat,toValue:CGFloat) -> WCGraintCircleLayer { let layer = WCGraintCircleLayer(bounds: bounds,position: position,fromColor:fromColor, toColor: toColor,linewidth: linewidth,toValue:toValue ) return layer } func graint(fromColor:UIColor, toColor:UIColor, count:Int) -> [UIColor]{ var fromR:CGFloat = 0.0,fromG:CGFloat = 0.0,fromB:CGFloat = 0.0,fromAlpha:CGFloat = 0.0 fromColor.getRed(&fromR,green: &fromG,blue: &fromB,alpha: &fromAlpha) var toR:CGFloat = 0.0,toG:CGFloat = 0.0,toB:CGFloat = 0.0,toAlpha:CGFloat = 0.0 toColor.getRed(&toR,green: &toG,blue: &toB,alpha: &toAlpha) var result : [UIColor]! = [UIColor]() for i in 0...count { let oneR:CGFloat = fromR + (toR - fromR)/CGFloat(count) * CGFloat(i) let oneG : CGFloat = fromG + (toG - fromG)/CGFloat(count) * CGFloat(i) let oneB : CGFloat = fromB + (toB - fromB)/CGFloat(count) * CGFloat(i) let oneAlpha : CGFloat = fromAlpha + (toAlpha - fromAlpha)/CGFloat(count) * CGFloat(i) let oneColor = UIColor.init(red: oneR, green: oneG, blue: oneB, alpha: oneAlpha) result.append(oneColor) print(oneColor) } return result } func positionArrayWith(bounds:CGRect) -> [CGPoint]{ let first = CGPoint(x:(bounds.width/4)*3,y: (bounds.height/4)*1) let second = CGPoint(x:(bounds.width/4)*3,y: (bounds.height/4)*3) let third = CGPoint(x:(bounds.width/4)*1,y: (bounds.height/4)*3) let fourth = CGPoint(x:(bounds.width/4)*1,y: (bounds.height/4)*1) print([first,second,third,fourth]) return [first,second,third,fourth] } func startPoints() -> [CGPoint] { return [CGPoint.zero,CGPoint(x:1,y:0),CGPoint(x:1,y:1),CGPoint(x:0,y:1)] } func endPoints() -> [CGPoint] { return [CGPoint(x:1,y:1),CGPoint(x:0,y:1),CGPoint.zero,CGPoint(x:1,y:0)] } func midColorWithFromColor(fromColor:UIColor, toColor:UIColor, progress:CGFloat) -> UIColor { var fromR:CGFloat = 0.0,fromG:CGFloat = 0.0,fromB:CGFloat = 0.0,fromAlpha:CGFloat = 0.0 fromColor.getRed(&fromR,green: &fromG,blue: &fromB,alpha: &fromAlpha) var toR:CGFloat = 0.0,toG:CGFloat = 0.0,toB:CGFloat = 0.0,toAlpha:CGFloat = 0.0 toColor.getRed(&toR,green: &toG,blue: &toB,alpha: &toAlpha) let oneR = fromR + (toR - fromR) * progress let oneG = fromG + (toG - fromG) * progress let oneB = fromB + (toB - fromB) * progress let oneAlpha = fromAlpha + (toAlpha - fromAlpha) * progress let oneColor = UIColor.init(red: oneR, green: oneG, blue: oneB, alpha: oneAlpha) return oneColor } // This is what you call if you want to draw a full circle. func animateCircle(duration: TimeInterval) { animateCircleTo(duration: duration, fromValue: 0.010, toValue: 0.99) } // This is what you call to draw a partial circle. func animateCircleTo(duration: TimeInterval, fromValue: CGFloat, toValue: CGFloat){ // We want to animate the strokeEnd property of the circleLayer let animation = CABasicAnimation(keyPath: "strokeEnd") animation.isRemovedOnCompletion = true // Set the animation duration appropriately animation.duration = duration // Animate from 0.010 (no circle) to 0.99 (full circle) animation.fromValue = 0.010 animation.toValue = toValue // Do an easeout. Don't know how to do a spring instead //animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut) animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut) // Set the circleLayer's strokeEnd property to 0.99 now so that it's the // right value when the animation ends. let circleMask = self.mask as! CAShapeLayer circleMask.strokeEnd = toValue // Do the actual animation circleMask.removeAllAnimations() circleMask.add(animation, forKey: "animateCircle") } } 

这是一个小例子, 如何在Swift 3.x中使用

 let gradientRingLayer = WCGraintCircleLayer(bounds: CGRect(origin: CGPoint.zero,size:CGSize(width: 150, height: 150)), position:CGPoint(x: 200, y: 300),fromColor:UIColor.blue, toColor:UIColor.white, linewidth:4.0, toValue:0) self.view.layer.addSublayer(gradientRingLayer) let duration = 3.0 gradientRingLayer.animateCircleTo(duration: duration, fromValue: 0, toValue: 0.99) 

这是Swift 2.x中的代码:

 import UIKit class WCGraintCircleLayer: CALayer { override init () { super.init() } convenience init(bounds:CGRect,position:CGPoint,fromColor:UIColor,toColor:UIColor,linewidth:CGFloat,toValue:CGFloat) { self.init() self.bounds = bounds self.position = position let colors : [UIColor] = self.graintFromColor(fromColor, toColor:toColor, count:4) for i in 0..<colors.count-1 { let graint = CAGradientLayer() graint.bounds = CGRectMake(0,0,CGRectGetWidth(bounds)/2,CGRectGetHeight(bounds)/2) let valuePoint = self.positionArrayWithMainBounds(self.bounds)[i] graint.position = valuePoint print("iesimo graint position: \(graint.position)") let fromColor = colors[i] let toColor = colors[i+1] let colors : [CGColorRef] = [fromColor.CGColor,toColor.CGColor] let stopOne: CGFloat = 0.0 let stopTwo: CGFloat = 1.0 let locations : [CGFloat] = [stopOne,stopTwo] graint.colors = colors graint.locations = locations graint.startPoint = self.startPoints()[i] graint.endPoint = self.endPoints()[i] self.addSublayer(graint) //Set mask let shapelayer = CAShapeLayer() let rect = CGRectMake(0,0,CGRectGetWidth(self.bounds) - 2 * linewidth, CGRectGetHeight(self.bounds) - 2 * linewidth) shapelayer.bounds = rect shapelayer.position = CGPointMake(CGRectGetWidth(self.bounds)/2, CGRectGetHeight(self.bounds)/2) shapelayer.strokeColor = UIColor.blueColor().CGColor shapelayer.fillColor = UIColor.clearColor().CGColor shapelayer.path = UIBezierPath(roundedRect: rect, cornerRadius: CGRectGetWidth(rect)/2).CGPath shapelayer.lineWidth = linewidth shapelayer.lineCap = kCALineCapRound shapelayer.strokeStart = 0.010 let finalValue = (toValue*0.99) shapelayer.strokeEnd = finalValue//0.99; self.mask = shapelayer } } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } func layerWithWithBounds(bounds:CGRect, position:CGPoint, fromColor:UIColor, toColor:UIColor, linewidth : CGFloat,toValue:CGFloat) -> WCGraintCircleLayer { let layer = WCGraintCircleLayer(bounds: bounds,position: position,fromColor:fromColor, toColor: toColor,linewidth: linewidth,toValue:toValue ) return layer } func graintFromColor(fromColor:UIColor, toColor:UIColor, count:Int) -> [UIColor]{ var fromR:CGFloat = 0.0,fromG:CGFloat = 0.0,fromB:CGFloat = 0.0,fromAlpha:CGFloat = 0.0 fromColor.getRed(&fromR,green: &fromG,blue: &fromB,alpha: &fromAlpha) var toR:CGFloat = 0.0,toG:CGFloat = 0.0,toB:CGFloat = 0.0,toAlpha:CGFloat = 0.0 toColor.getRed(&toR,green: &toG,blue: &toB,alpha: &toAlpha) var result : [UIColor]! = [UIColor]() for i in 0...count { let oneR:CGFloat = fromR + (toR - fromR)/CGFloat(count) * CGFloat(i) let oneG : CGFloat = fromG + (toG - fromG)/CGFloat(count) * CGFloat(i) let oneB : CGFloat = fromB + (toB - fromB)/CGFloat(count) * CGFloat(i) let oneAlpha : CGFloat = fromAlpha + (toAlpha - fromAlpha)/CGFloat(count) * CGFloat(i) let oneColor = UIColor.init(red: oneR, green: oneG, blue: oneB, alpha: oneAlpha) result.append(oneColor) print(oneColor) } return result } func positionArrayWithMainBounds(bounds:CGRect) -> [CGPoint]{ let first = CGPointMake((CGRectGetWidth(bounds)/4)*3, (CGRectGetHeight(bounds)/4)*1) let second = CGPointMake((CGRectGetWidth(bounds)/4)*3, (CGRectGetHeight(bounds)/4)*3) let third = CGPointMake((CGRectGetWidth(bounds)/4)*1, (CGRectGetHeight(bounds)/4)*3) let fourth = CGPointMake((CGRectGetWidth(bounds)/4)*1, (CGRectGetHeight(bounds)/4)*1) print([first,second,third,fourth]) return [first,second,third,fourth] } func startPoints() -> [CGPoint] { return [CGPointMake(0,0),CGPointMake(1,0),CGPointMake(1,1),CGPointMake(0,1)] } func endPoints() -> [CGPoint] { return [CGPointMake(1,1),CGPointMake(0,1),CGPointMake(0,0),CGPointMake(1,0)] } func midColorWithFromColor(fromColor:UIColor, toColor:UIColor, progress:CGFloat) -> UIColor { var fromR:CGFloat = 0.0,fromG:CGFloat = 0.0,fromB:CGFloat = 0.0,fromAlpha:CGFloat = 0.0 fromColor.getRed(&fromR,green: &fromG,blue: &fromB,alpha: &fromAlpha) var toR:CGFloat = 0.0,toG:CGFloat = 0.0,toB:CGFloat = 0.0,toAlpha:CGFloat = 0.0 toColor.getRed(&toR,green: &toG,blue: &toB,alpha: &toAlpha) let oneR = fromR + (toR - fromR) * progress let oneG = fromG + (toG - fromG) * progress let oneB = fromB + (toB - fromB) * progress let oneAlpha = fromAlpha + (toAlpha - fromAlpha) * progress let oneColor = UIColor.init(red: oneR, green: oneG, blue: oneB, alpha: oneAlpha) return oneColor } // This is what you call if you want to draw a full circle. func animateCircle(duration: NSTimeInterval) { animateCircleTo(duration, fromValue: 0.010, toValue: 0.99) } // This is what you call to draw a partial circle. func animateCircleTo(duration: NSTimeInterval, fromValue: CGFloat, toValue: CGFloat){ // We want to animate the strokeEnd property of the circleLayer let animation = CABasicAnimation(keyPath: "strokeEnd") animation.removedOnCompletion = true // Set the animation duration appropriately animation.duration = duration // Animate from 0.010 (no circle) to 0.99 (full circle) animation.fromValue = 0.010 animation.toValue = toValue // Do an easeout. Don't know how to do a spring instead //animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut) animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut) // Set the circleLayer's strokeEnd property to 0.99 now so that it's the // right value when the animation ends. let circleMask = self.mask as! CAShapeLayer circleMask.strokeEnd = toValue // Do the actual animation circleMask.removeAllAnimations() circleMask.addAnimation(animation, forKey: "animateCircle") } 

}

这是一个小例子, 如何在Swift 2.x中使用

 let gradientRingLayer = WCGraintCircleLayer(bounds: CGRectMake(0, 0, 150, 150), position:CGPointMake(200,300) ,fromColor:UIColor.blueColor(), toColor:UIColor.whiteColor(),linewidth:4.0, toValue:0) self.view.layer.addSublayer(gradientRingLayer) let duration = 3.0 gradientRingLayer.animateCircleTo(duration, fromValue: 0, toValue: 0.99) 

这是快速复制/粘贴远程pastebin代码

静态图像

也可用于animation

动态图像

我碰巧访问这个问题,并想发布我的答案。

唯一的,你需要做的是使用CAShapeLayer来设置Mask

 [graintLayer setMask:shapeLayer] 

我写了一个关于如何构buildCircle Graint Layer的简单的库

这个图书馆在这里

这是截图

如果您的backgroundLayer应该充当渐变图层的遮罩,请使用

 gradient.mask = backgroundLayer 

并将渐变添加到视图的图层

 backgroundView.layer.addSublayer(gradient) 

否则创build一个新的CAShapeLayer作为渐变的maskLayer

这是狮子座的图书馆转换为Swift 3.感谢Alessandro Ornano的Swift 2.3翻译。

码:

 import UIKit class WCGraintCircleLayer: CALayer { override init () { super.init() } convenience init(bounds:CGRect,position:CGPoint,fromColor:UIColor,toColor:UIColor,linewidth:CGFloat,toValue:CGFloat) { self.init() self.bounds = bounds self.position = position let colors : [UIColor] = self.graintFromColor(fromColor: fromColor, toColor:toColor, count:4) for i in 0..<colors.count-1 { let graint = CAGradientLayer() graint.bounds = CGRect(x: 0, y: 0, width: bounds.width/2, height: bounds.height/2) let valuePoint = self.positionArrayWithMainBounds(bounds: self.bounds)[i] graint.position = valuePoint print("iesimo graint position: \(graint.position)") let fromColor = colors[i] let toColor = colors[i+1] let colors : [CGColor] = [fromColor.cgColor,toColor.cgColor] let stopOne: CGFloat = 0.0 let stopTwo: CGFloat = 1.0 let locations : [CGFloat] = [stopOne,stopTwo] graint.colors = colors graint.locations = locations as [NSNumber]? graint.startPoint = self.startPoints()[i] graint.endPoint = self.endPoints()[i] self.addSublayer(graint) //Set mask let shapelayer = CAShapeLayer() let rect = CGRect(x: 0, y: 0, width: bounds.width - 2 * linewidth, height: bounds.height - 2 * linewidth) shapelayer.bounds = rect shapelayer.position = CGPoint(x: bounds.width/2, y: bounds.height/2) shapelayer.strokeColor = UIColor.blue.cgColor shapelayer.fillColor = UIColor.clear.cgColor shapelayer.path = UIBezierPath(roundedRect: rect, cornerRadius: rect.width/2).cgPath shapelayer.lineWidth = linewidth shapelayer.lineCap = kCALineCapRound shapelayer.strokeStart = 0.010 let finalValue = (toValue*0.99) shapelayer.strokeEnd = finalValue//0.99; self.mask = shapelayer } } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } func layerWithWithBounds(bounds:CGRect, position:CGPoint, fromColor:UIColor, toColor:UIColor, linewidth : CGFloat,toValue:CGFloat) -> WCGraintCircleLayer { let layer = WCGraintCircleLayer(bounds: bounds,position: position,fromColor:fromColor, toColor: toColor,linewidth: linewidth,toValue:toValue ) return layer } func graintFromColor(fromColor:UIColor, toColor:UIColor, count:Int) -> [UIColor]{ var fromR:CGFloat = 0.0,fromG:CGFloat = 0.0,fromB:CGFloat = 0.0,fromAlpha:CGFloat = 0.0 fromColor.getRed(&fromR,green: &fromG,blue: &fromB,alpha: &fromAlpha) var toR:CGFloat = 0.0,toG:CGFloat = 0.0,toB:CGFloat = 0.0,toAlpha:CGFloat = 0.0 toColor.getRed(&toR,green: &toG,blue: &toB,alpha: &toAlpha) var result : [UIColor]! = [UIColor]() for i in 0...count { let oneR:CGFloat = fromR + (toR - fromR)/CGFloat(count) * CGFloat(i) let oneG : CGFloat = fromG + (toG - fromG)/CGFloat(count) * CGFloat(i) let oneB : CGFloat = fromB + (toB - fromB)/CGFloat(count) * CGFloat(i) let oneAlpha : CGFloat = fromAlpha + (toAlpha - fromAlpha)/CGFloat(count) * CGFloat(i) let oneColor = UIColor.init(red: oneR, green: oneG, blue: oneB, alpha: oneAlpha) result.append(oneColor) print(oneColor) } return result } func positionArrayWithMainBounds(bounds:CGRect) -> [CGPoint]{ let first = CGPoint(x: (bounds.width/4)*3, y: (bounds.height/4)*1) let second = CGPoint(x: (bounds.width/4)*3, y: (bounds.height/4)*3) let third = CGPoint(x: (bounds.width/4)*1, y: (bounds.height/4)*3) let fourth = CGPoint(x: (bounds.width/4)*1, y: (bounds.height/4)*1) print([first,second,third,fourth]) return [first,second,third,fourth] } func startPoints() -> [CGPoint] { return [CGPoint(x: 0, y: 0),CGPoint(x: 1, y: 0),CGPoint(x: 1, y: 1),CGPoint(x: 0, y: 1)] } func endPoints() -> [CGPoint] { return [CGPoint(x: 1, y: 1),CGPoint(x: 0, y: 1),CGPoint(x: 0, y: 0),CGPoint(x: 1, y: 0)] } func midColorWithFromColor(fromColor:UIColor, toColor:UIColor, progress:CGFloat) -> UIColor { var fromR:CGFloat = 0.0,fromG:CGFloat = 0.0,fromB:CGFloat = 0.0,fromAlpha:CGFloat = 0.0 fromColor.getRed(&fromR,green: &fromG,blue: &fromB,alpha: &fromAlpha) var toR:CGFloat = 0.0,toG:CGFloat = 0.0,toB:CGFloat = 0.0,toAlpha:CGFloat = 0.0 toColor.getRed(&toR,green: &toG,blue: &toB,alpha: &toAlpha) let oneR = fromR + (toR - fromR) * progress let oneG = fromG + (toG - fromG) * progress let oneB = fromB + (toB - fromB) * progress let oneAlpha = fromAlpha + (toAlpha - fromAlpha) * progress let oneColor = UIColor.init(red: oneR, green: oneG, blue: oneB, alpha: oneAlpha) return oneColor } // This is what you call if you want to draw a full circle. func animateCircle(duration: TimeInterval) { animateCircleTo(duration: duration, fromValue: 0.010, toValue: 0.99) } // This is what you call to draw a partial circle. func animateCircleTo(duration: TimeInterval, fromValue: CGFloat, toValue: CGFloat){ // We want to animate the strokeEnd property of the circleLayer let animation = CABasicAnimation(keyPath: "strokeEnd") animation.isRemovedOnCompletion = true // Set the animation duration appropriately animation.duration = duration // Animate from 0.010 (no circle) to 0.99 (full circle) animation.fromValue = 0.010 animation.toValue = toValue // Do an easeout. Don't know how to do a spring instead //animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut) animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut) // Set the circleLayer's strokeEnd property to 0.99 now so that it's the // right value when the animation ends. let circleMask = self.mask as! CAShapeLayer circleMask.strokeEnd = toValue // Do the actual animation circleMask.removeAllAnimations() circleMask.add(animation, forKey: "animateCircle") } } 

如何使用:

 let gradientRingLayer = WCGraintCircleLayer(bounds: CGRect(x: 0, y: 0, width: 150, height: 150), position:CGPoint(x: 200, y: 300),fromColor:UIColor.blue, toColor:UIColor.white, linewidth:4.0, toValue:0) self.view.layer.addSublayer(gradientRingLayer) let duration = 3.0 gradientRingLayer.animateCircleTo(duration: duration, fromValue: 0, toValue: 0.99)