UIBezierPath + CAShapeLayer – animation圈起来填满
我正在尝试对CAShapeLayer的path进行animation处理,这样我就可以得到一个圆圈“填充”到特定数量的效果。
问题
它“起作用”,但不是我想的那么顺利,我想介绍一些缓解。 但是因为我分别为每个animation制作了一个animation,所以现在我不确定这是可能的。 所以我正在寻找可以解决这个问题的替代scheme!
视觉解释
开始 – 这里是我试图实现的animation的一些“框架”(见图像 – 从0%到25%的圆圈填充)
码
我创造了绿色的中风(外面):
let ovalPath = UIBezierPath(ovalInRect: CGRectMake(20, 20, 240, 240)) let circleStrokeLayer = CAShapeLayer() circleStrokeLayer.path = ovalPath.CGPath circleStrokeLayer.lineWidth = 20 circleStrokeLayer.fillColor = UIColor.clearColor().CGColor circleStrokeLayer.strokeColor = colorGreen.CGColor containerView.layer.addSublayer(circleStrokeLayer)
我创build了填充形状(内部)的初始path:
let filledPathStart = UIBezierPath(arcCenter: CGPoint(x: 140, y: 140), radius: 120, startAngle: startAngle, endAngle: Math().percentToRadians(0), clockwise: true) filledPathStart.addLineToPoint(CGPoint(x: 140, y: 140)) filledLayer = CAShapeLayer() filledLayer.path = filledPathStart.CGPath filledLayer.fillColor = colorGreen.CGColor containerView.layer.addSublayer(filledLayer)
然后使用for循环和CABasicAnimations数组进行animation处理。
var animations: [CABasicAnimation] = [CABasicAnimation]() func animate() { for val in 0...25 { let morph = CABasicAnimation(keyPath: "path") morph.duration = 0.01 let filledPathEnd = UIBezierPath(arcCenter: CGPoint(x: 140, y: 140), radius: 120, startAngle: startAngle, endAngle: Math().percentToRadians(CGFloat(val)), clockwise: true) filledPathEnd.addLineToPoint(CGPoint(x: 140, y: 140)) morph.delegate = self morph.toValue = filledPathEnd.CGPath animations.append(morph) } applyNextAnimation() } func applyNextAnimation() { if animations.count == 0 { return } let nextAnimation = animations[0] animations.removeAtIndex(0) filledLayer.addAnimation(nextAnimation, forKey: nil) } override func animationDidStop(anim: CAAnimation, finished flag: Bool) { filledLayer.path = (anim as! CABasicAnimation).toValue as! CGPath applyNextAnimation() }
不要尝试创build一大堆单独的animation。 CAShapeLayer具有strokeStart和strokeEnd属性。 animation。
诀窍是在形状图层中安装一个圆的圆弧,然后创build一个CABasicAnimation,将shapeEnd的animation从0变为1(使animation从0%到100%填充)或任何需要的值。
您可以在该animation上应用所需的时间。
我有一个项目(在Objective-C中,恐怕)在Github上,包括使用这种技术的“时钟擦拭”animation。 以下是它的外观:
(这是一个GIF,看起来有些粗糙,实际的iOSanimation非常stream畅。)
链接如下。 在自述文件中查找“时钟擦除”animation。
IOS的CAAnimation组,演示
时钟擦除animation在图像视图的图层上安装形状图层作为蒙版。 你可以直接绘制你的形状图层,如果这是你想要做的。
所以我拿了邓肯C在他的链接GitHub项目(在Objective-C中),并将其应用到我的场景(也转换为Swift) – 它工作的很棒!
这是最终的解决scheme:
// --- // In some ViewController // --- let animatedCircle = AnimatedCircle() self.addSubview(animatedCircle) animatedCircle.runAnimation() // --- // The Animated Circle Class // --- import UIKit import Darwin let pi: CGFloat = CGFloat(M_PI) let startAngle: CGFloat = (3.0 * pi) / 2.0 let colorGray = UIColor(red: 0.608, green: 0.608, blue: 0.608, alpha: 1.000) let colorGreen = UIColor(red: 0.482, green: 0.835, blue: 0.000, alpha: 1.000) // ---- // Math class to handle fun circle forumals // ---- class Math { func percentToRadians(percentComplete: CGFloat) -> CGFloat { let degrees = (percentComplete/100) * 360 return startAngle + (degrees * (pi/180)) } } class AnimatedCircle: UIView { let percentComplete: CGFloat = 25 var containerView: UIView! var filledLayer: CAShapeLayer! init() { super.init(frame: CGRect(x: 0, y: 0, width: 260, height: 260)) let endAngle = Math().percentToRadians(percentComplete) // ---- // Create oval bezier path and layer // ---- let ovalPath = UIBezierPath(ovalInRect: CGRectMake(10, 10, 240, 240)) let circleStrokeLayer = CAShapeLayer() circleStrokeLayer.path = ovalPath.CGPath circleStrokeLayer.lineWidth = 20 circleStrokeLayer.fillColor = UIColor.clearColor().CGColor circleStrokeLayer.strokeColor = colorGreen.CGColor // ---- // Create filled bezier path and layer // ---- let filledPathStart = UIBezierPath(arcCenter: CGPoint(x: 140, y: 140), radius: 120, startAngle: startAngle, endAngle: endAngle, clockwise: true) filledPathStart.addLineToPoint(CGPoint(x: 140, y: 140)) filledLayer = CAShapeLayer() filledLayer.path = filledPathStart.CGPath filledLayer.fillColor = colorGreen.CGColor // ---- // Add any layers to container view // ---- containerView = UIView(frame: self.frame) containerView.backgroundColor = UIColor.whiteColor() containerView.layer.addSublayer(circleStrokeLayer) containerView.layer.addSublayer(filledLayer) // Set the frame of the filledLayer to match our view filledLayer.frame = containerView.frame } func runAnimation() { let endAngle = Math().percentToRadians(percentComplete) let maskLayer = CAShapeLayer() let maskWidth: CGFloat = filledLayer.frame.size.width let maskHeight: CGFloat = filledLayer.frame.size.height let centerPoint: CGPoint = CGPointMake(maskWidth / 2, maskHeight / 2) let radius: CGFloat = CGFloat(sqrtf(Float(maskWidth * maskWidth + maskHeight * maskHeight)) / 2) maskLayer.fillColor = UIColor.clearColor().CGColor maskLayer.strokeColor = UIColor.blackColor().CGColor maskLayer.lineWidth = radius let arcPath: CGMutablePathRef = CGPathCreateMutable() CGPathMoveToPoint(arcPath, nil, centerPoint.x, centerPoint.y - radius / 2) CGPathAddArc(arcPath, nil, centerPoint.x, centerPoint.y, radius / 2, 3*pi/2, endAngle, false) maskLayer.path = arcPath maskLayer.strokeEnd = 0 filledLayer.mask = maskLayer filledLayer.mask!.frame = filledLayer.frame let anim = CABasicAnimation(keyPath: "strokeEnd") anim.duration = 3 anim.delegate = self anim.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) anim.fillMode = kCAFillModeForwards anim.removedOnCompletion = false anim.autoreverses = false anim.toValue = 1.0 maskLayer.addAnimation(anim, forKey: "strokeEnd") } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } }
谢谢,邓肯C – 这是超级有用的。
以下是使用您的build议的进度圈的示例。 这个视图画出了一个随着时间的推移而产生animation的进度圈,从一个圆圈开始,擦除自己,揭示圈子背后的景观。 有@IBInspectable属性,可以修改填充圆而不是擦除。
这是一个项目的链接。 该视图在CircleTimer.view中。 如果运行该项目,则可以input秒数,并在指定时间内看到圆圈擦除。
https://github.com/riley2012/circle-timer-ios
这是做animation的方法:
open func runMaskAnimation(duration: CFTimeInterval) { if let parentLayer = filledLayer { let maskLayer = CAShapeLayer() maskLayer.frame = parentLayer.frame let circleRadius = timerFillDiameter * 0.5 let circleHalfRadius = circleRadius * 0.5 let circleBounds = CGRect(x: parentLayer.bounds.midX - circleHalfRadius, y: parentLayer.bounds.midY - circleHalfRadius, width: circleRadius, height: circleRadius) maskLayer.fillColor = UIColor.clear.cgColor maskLayer.strokeColor = UIColor.black.cgColor maskLayer.lineWidth = circleRadius let path = UIBezierPath(roundedRect: circleBounds, cornerRadius: circleBounds.size.width * 0.5) maskLayer.path = path.reversing().cgPath maskLayer.strokeEnd = 0 parentLayer.mask = maskLayer let animation = CABasicAnimation(keyPath: "strokeEnd") animation.duration = duration animation.fromValue = 1.0 animation.toValue = 0.0 maskLayer.add(animation, forKey: "strokeEnd") }
}
- Swift – UITableViewCellselect多个select的背景颜色
- global_queue与qos_class_user_interactive
- 如何在swift ios中将常规string占位符添加到翻译后的复数.stringdict
- 在OnTouchUp和OnTouchDown的Swift UIButton背景更改?
- 什么是“L”的Swift数据types?
- dynamic创build菜单,但使用相同的视图控制器
- 将数据源分隔到Swift中的另一个类
- Swift – 由currentCalendar()当前位置的Weekday
- 针对CKReference的CloudKit预订通知未按预期工作