制作圆形动画的正确方法是什么?
What is the right way of creating circle animation?
我刚看到这张图片,我很感兴趣,如何在 Swift 中创建这种类型的动画:
因此,我有很多圆形的灰色牙齿,当我设置角度时,例如 45 度,它将在 0..45 度以内将这些灰色牙齿填充为蓝色。
你可以向我解释正确的做法,或者你可以展示不同的片段(这会很棒)。稍后我会搜索或阅读它。
提前致谢!
如果你只需要个别'teeth'改变颜色,而不是使用牙齿作为遮罩进行实体填充,你可以使用Core Graphics而不是Core Animation(尽管通常首选Core Animation)。所以为了做到这一点,我们应该做以下事情:
- 子类
UIView
插入我们的绘图代码
- 创建路径对象数组,包裹在
UIBezierPath
中
- 设置一个计时器来更新进度值和
setNeedsDisplay
- 在
drawRect:
中,绘制路径并根据进度为每个路径分配填充
首先,让我们定义我们将在这个 UIView
子类中使用的变量。
class TeethLoaderView : UIView {
let numberOfTeeth = UInt(60) // Number of teeth to render
let teethSize = CGSize(width:8, height:45) // The size of each individual tooth
let animationDuration = NSTimeInterval(5.0) // The duration of the animation
let highlightColor = UIColor(red: 29.0/255.0, green: 175.0/255.0, blue: 255.0/255.0, alpha: 1) // The color of a tooth when it's 'highlighted'
let inactiveColor = UIColor(red: 233.0/255.0, green: 235.0/255.0, blue: 236.0/255.0, alpha: 1) // The color of a tooth when it isn't 'hightlighted'
var progress = NSTimeInterval(0.0) // The progress of the loader
var paths = [UIBezierPath]() // The array containing the UIBezier paths
var displayLink = CADisplayLink() // The display link to update the progress
var teethHighlighted = UInt(0) // Number of teeth highlighted
...
现在让我们添加一个函数来创建我们的路径。
func getPaths(size:CGSize, teethCount:UInt, teethSize:CGSize, radius:CGFloat) -> [UIBezierPath] {
let halfHeight = size.height*0.5;
let halfWidth = size.width*0.5;
let deltaAngle = CGFloat(2*M_PI)/CGFloat(teethCount); // The change in angle between paths
// Create the template path of a single shape.
let p = CGPathCreateWithRect(CGRectMake(-teethSize.width*0.5, radius, teethSize.width, teethSize.height), nil);
var pathArray = [UIBezierPath]()
for i in 0..<teethCount { // Copy, translate and rotate shapes around
let translate = CGAffineTransformMakeTranslation(halfWidth, halfHeight);
var rotate = CGAffineTransformRotate(translate, deltaAngle*CGFloat(i))
let pathCopy = CGPathCreateCopyByTransformingPath(p, &rotate)!
pathArray.append(UIBezierPath(CGPath: pathCopy)) // Populate the array
}
return pathArray
}
这很简单。我们只是为单个 'tooth' 创建一个路径,然后根据我们需要多少颗牙齿复制这条路径,为每个牙齿平移和旋转路径。
接下来我们要设置我们的视图。我要为计时器设置 CADisplayLink
,以便动画在所有设备上以相同的速度执行。
override init(frame: CGRect) {
super.init(frame: frame)
commonSetup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonSetup()
}
private func commonSetup() {
self.backgroundColor = UIColor.whiteColor()
paths = getPaths(frame.size, teethCount: numberOfTeeth, teethSize: teethSize, radius: ((frame.width*0.5)-teethSize.height))
displayLink = CADisplayLink(target: self, selector: #selector(displayLinkDidFire));
displayLink.addToRunLoop(NSRunLoop.mainRunLoop(), forMode: NSRunLoopCommonModes)
}
这里我们只是设置背景颜色,以及设置我们的计时器和初始化我们将要使用的路径。接下来我们要设置一个函数来在 CADisplayLink
触发时更改视图的进度。
func displayLinkDidFire() {
progress += displayLink.duration/animationDuration
if (progress > 1) {
progress -= 1
}
let t = teethHighlighted
teethHighlighted = UInt(round(progress*NSTimeInterval(numberOfTeeth))) // Calculate the number of teeth to highlight
if (t != teethHighlighted) { // Only call setNeedsDisplay if the teethHighlighted changed
setNeedsDisplay()
}
}
这里没有什么复杂的,我们只是更新进度和 teethHighlighted
并调用 setNeedsDisplay()
重绘视图,如果 teethHighlighted
改变了。
最后,我们要绘制视图。
override func drawRect(rect: CGRect) {
let ctx = UIGraphicsGetCurrentContext()
CGContextScaleCTM(ctx, -1, -1) // Flip the context to the correct orientation
CGContextTranslateCTM(ctx, -rect.size.width, -rect.size.height)
for (index, path) in paths.enumerate() { // Draw each 'tooth'
CGContextAddPath(ctx, path.CGPath);
let fillColor = (UInt(index) <= teethHighlighted) ? highlightColor:inactiveColor;
CGContextSetFillColorWithColor(ctx, fillColor.CGColor)
CGContextFillPath(ctx)
}
}
如果你想走核心动画之路,
最终结果
好吧,本着“要么变大要么回家”的精神(而且因为我这样做真的很有趣),我创建了一个 Core Animation 版本 。 它的代码少了很多,动画也更流畅,所以我实际上更喜欢使用它。
首先,让我们再次对 UIView
进行子类化(这不是绝对必要的,但最好将所有内容都包含在一个视图中)并定义我们的变量:
class TeethLoaderViewCA : UIView {
let numberOfTeeth = UInt(60) // Number of teetch to render
let teethSize = CGSize(width:8, height:45) // The size of each individual tooth
let animationDuration = NSTimeInterval(5.0) // The duration of the animation
let highlightColor = UIColor(red: 29.0/255.0, green: 175.0/255.0, blue: 255.0/255.0, alpha: 1) // The color of a tooth when it's 'highlighted'
let inactiveColor = UIColor(red: 233.0/255.0, green: 235.0/255.0, blue: 236.0/255.0, alpha: 1) // The color of a tooth when it isn't 'hightlighted'
let shapeLayer = CAShapeLayer() // The teeth shape layer
let drawLayer = CAShapeLayer() // The arc fill layer
let anim = CABasicAnimation(keyPath: "strokeEnd") // The stroke animation
...
这与 Core Graphics 版本大部分相同,但有几个 Core Animation 对象并且没有时序逻辑。接下来,我们几乎可以复制我们在其他版本中创建的 getPaths
函数,除了一些调整。
func getPathMask(size:CGSize, teethCount:UInt, teethSize:CGSize, radius:CGFloat) -> CGPathRef? {
let halfHeight = size.height*0.5
let halfWidth = size.width*0.5
let deltaAngle = CGFloat(2*M_PI)/CGFloat(teethCount); // The change in angle between paths
// Create the template path of a single shape.
let p = CGPathCreateWithRect(CGRectMake(-teethSize.width*0.5, radius, teethSize.width, teethSize.height), nil)
let returnPath = CGPathCreateMutable()
for i in 0..<teethCount { // Copy, translate and rotate shapes around
let translate = CGAffineTransformMakeTranslation(halfWidth, halfHeight)
var rotate = CGAffineTransformRotate(translate, deltaAngle*CGFloat(i))
CGPathAddPath(returnPath, &rotate, p)
}
return CGPathCreateCopy(returnPath)
}
这一次,所有的路径都被归为一个大路径,函数 returns 那条路径。
最后,我们只需创建图层对象并设置动画即可。
private func commonSetup() {
// set your background color
self.backgroundColor = UIColor.whiteColor()
// Get the group of paths we created.
shapeLayer.path = getPathMask(frame.size, teethCount: numberOfTeeth, teethSize: teethSize, radius: ((frame.width*0.5)-teethSize.height))
let halfWidth = frame.size.width*0.5
let halfHeight = frame.size.height*0.5
let halfDeltaAngle = CGFloat(M_PI/Double(numberOfTeeth))
// Creates an arc path, with a given offset to allow it to be presented nicely
drawLayer.path = UIBezierPath(arcCenter: CGPointMake(halfWidth, halfHeight), radius: halfWidth, startAngle: CGFloat(-M_PI_2)-halfDeltaAngle, endAngle: CGFloat(M_PI*1.5)+halfDeltaAngle, clockwise: true).CGPath
drawLayer.frame = frame
drawLayer.fillColor = inactiveColor.CGColor
drawLayer.strokeColor = highlightColor.CGColor
drawLayer.strokeEnd = 0
drawLayer.lineWidth = halfWidth
drawLayer.mask = shapeLayer
layer.addSublayer(drawLayer)
// Optional, but looks nice
anim.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
}
我们在这里所做的就是将路径组分配给 CAShapeLayer
,我们将其用作 drawLayer
的遮罩,我们将在视图周围设置动画(使用拱形路径上的一笔)。
最终结果
我刚看到这张图片,我很感兴趣,如何在 Swift 中创建这种类型的动画:
因此,我有很多圆形的灰色牙齿,当我设置角度时,例如 45 度,它将在 0..45 度以内将这些灰色牙齿填充为蓝色。
你可以向我解释正确的做法,或者你可以展示不同的片段(这会很棒)。稍后我会搜索或阅读它。
提前致谢!
如果你只需要个别'teeth'改变颜色,而不是使用牙齿作为遮罩进行实体填充,你可以使用Core Graphics而不是Core Animation(尽管通常首选Core Animation)。所以为了做到这一点,我们应该做以下事情:
- 子类
UIView
插入我们的绘图代码 - 创建路径对象数组,包裹在
UIBezierPath
中
- 设置一个计时器来更新进度值和
setNeedsDisplay
- 在
drawRect:
中,绘制路径并根据进度为每个路径分配填充
首先,让我们定义我们将在这个 UIView
子类中使用的变量。
class TeethLoaderView : UIView {
let numberOfTeeth = UInt(60) // Number of teeth to render
let teethSize = CGSize(width:8, height:45) // The size of each individual tooth
let animationDuration = NSTimeInterval(5.0) // The duration of the animation
let highlightColor = UIColor(red: 29.0/255.0, green: 175.0/255.0, blue: 255.0/255.0, alpha: 1) // The color of a tooth when it's 'highlighted'
let inactiveColor = UIColor(red: 233.0/255.0, green: 235.0/255.0, blue: 236.0/255.0, alpha: 1) // The color of a tooth when it isn't 'hightlighted'
var progress = NSTimeInterval(0.0) // The progress of the loader
var paths = [UIBezierPath]() // The array containing the UIBezier paths
var displayLink = CADisplayLink() // The display link to update the progress
var teethHighlighted = UInt(0) // Number of teeth highlighted
...
现在让我们添加一个函数来创建我们的路径。
func getPaths(size:CGSize, teethCount:UInt, teethSize:CGSize, radius:CGFloat) -> [UIBezierPath] {
let halfHeight = size.height*0.5;
let halfWidth = size.width*0.5;
let deltaAngle = CGFloat(2*M_PI)/CGFloat(teethCount); // The change in angle between paths
// Create the template path of a single shape.
let p = CGPathCreateWithRect(CGRectMake(-teethSize.width*0.5, radius, teethSize.width, teethSize.height), nil);
var pathArray = [UIBezierPath]()
for i in 0..<teethCount { // Copy, translate and rotate shapes around
let translate = CGAffineTransformMakeTranslation(halfWidth, halfHeight);
var rotate = CGAffineTransformRotate(translate, deltaAngle*CGFloat(i))
let pathCopy = CGPathCreateCopyByTransformingPath(p, &rotate)!
pathArray.append(UIBezierPath(CGPath: pathCopy)) // Populate the array
}
return pathArray
}
这很简单。我们只是为单个 'tooth' 创建一个路径,然后根据我们需要多少颗牙齿复制这条路径,为每个牙齿平移和旋转路径。
接下来我们要设置我们的视图。我要为计时器设置 CADisplayLink
,以便动画在所有设备上以相同的速度执行。
override init(frame: CGRect) {
super.init(frame: frame)
commonSetup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonSetup()
}
private func commonSetup() {
self.backgroundColor = UIColor.whiteColor()
paths = getPaths(frame.size, teethCount: numberOfTeeth, teethSize: teethSize, radius: ((frame.width*0.5)-teethSize.height))
displayLink = CADisplayLink(target: self, selector: #selector(displayLinkDidFire));
displayLink.addToRunLoop(NSRunLoop.mainRunLoop(), forMode: NSRunLoopCommonModes)
}
这里我们只是设置背景颜色,以及设置我们的计时器和初始化我们将要使用的路径。接下来我们要设置一个函数来在 CADisplayLink
触发时更改视图的进度。
func displayLinkDidFire() {
progress += displayLink.duration/animationDuration
if (progress > 1) {
progress -= 1
}
let t = teethHighlighted
teethHighlighted = UInt(round(progress*NSTimeInterval(numberOfTeeth))) // Calculate the number of teeth to highlight
if (t != teethHighlighted) { // Only call setNeedsDisplay if the teethHighlighted changed
setNeedsDisplay()
}
}
这里没有什么复杂的,我们只是更新进度和 teethHighlighted
并调用 setNeedsDisplay()
重绘视图,如果 teethHighlighted
改变了。
最后,我们要绘制视图。
override func drawRect(rect: CGRect) {
let ctx = UIGraphicsGetCurrentContext()
CGContextScaleCTM(ctx, -1, -1) // Flip the context to the correct orientation
CGContextTranslateCTM(ctx, -rect.size.width, -rect.size.height)
for (index, path) in paths.enumerate() { // Draw each 'tooth'
CGContextAddPath(ctx, path.CGPath);
let fillColor = (UInt(index) <= teethHighlighted) ? highlightColor:inactiveColor;
CGContextSetFillColorWithColor(ctx, fillColor.CGColor)
CGContextFillPath(ctx)
}
}
如果你想走核心动画之路,
最终结果
好吧,本着“要么变大要么回家”的精神(而且因为我这样做真的很有趣),我创建了一个 Core Animation 版本
首先,让我们再次对 UIView
进行子类化(这不是绝对必要的,但最好将所有内容都包含在一个视图中)并定义我们的变量:
class TeethLoaderViewCA : UIView {
let numberOfTeeth = UInt(60) // Number of teetch to render
let teethSize = CGSize(width:8, height:45) // The size of each individual tooth
let animationDuration = NSTimeInterval(5.0) // The duration of the animation
let highlightColor = UIColor(red: 29.0/255.0, green: 175.0/255.0, blue: 255.0/255.0, alpha: 1) // The color of a tooth when it's 'highlighted'
let inactiveColor = UIColor(red: 233.0/255.0, green: 235.0/255.0, blue: 236.0/255.0, alpha: 1) // The color of a tooth when it isn't 'hightlighted'
let shapeLayer = CAShapeLayer() // The teeth shape layer
let drawLayer = CAShapeLayer() // The arc fill layer
let anim = CABasicAnimation(keyPath: "strokeEnd") // The stroke animation
...
这与 Core Graphics 版本大部分相同,但有几个 Core Animation 对象并且没有时序逻辑。接下来,我们几乎可以复制我们在其他版本中创建的 getPaths
函数,除了一些调整。
func getPathMask(size:CGSize, teethCount:UInt, teethSize:CGSize, radius:CGFloat) -> CGPathRef? {
let halfHeight = size.height*0.5
let halfWidth = size.width*0.5
let deltaAngle = CGFloat(2*M_PI)/CGFloat(teethCount); // The change in angle between paths
// Create the template path of a single shape.
let p = CGPathCreateWithRect(CGRectMake(-teethSize.width*0.5, radius, teethSize.width, teethSize.height), nil)
let returnPath = CGPathCreateMutable()
for i in 0..<teethCount { // Copy, translate and rotate shapes around
let translate = CGAffineTransformMakeTranslation(halfWidth, halfHeight)
var rotate = CGAffineTransformRotate(translate, deltaAngle*CGFloat(i))
CGPathAddPath(returnPath, &rotate, p)
}
return CGPathCreateCopy(returnPath)
}
这一次,所有的路径都被归为一个大路径,函数 returns 那条路径。
最后,我们只需创建图层对象并设置动画即可。
private func commonSetup() {
// set your background color
self.backgroundColor = UIColor.whiteColor()
// Get the group of paths we created.
shapeLayer.path = getPathMask(frame.size, teethCount: numberOfTeeth, teethSize: teethSize, radius: ((frame.width*0.5)-teethSize.height))
let halfWidth = frame.size.width*0.5
let halfHeight = frame.size.height*0.5
let halfDeltaAngle = CGFloat(M_PI/Double(numberOfTeeth))
// Creates an arc path, with a given offset to allow it to be presented nicely
drawLayer.path = UIBezierPath(arcCenter: CGPointMake(halfWidth, halfHeight), radius: halfWidth, startAngle: CGFloat(-M_PI_2)-halfDeltaAngle, endAngle: CGFloat(M_PI*1.5)+halfDeltaAngle, clockwise: true).CGPath
drawLayer.frame = frame
drawLayer.fillColor = inactiveColor.CGColor
drawLayer.strokeColor = highlightColor.CGColor
drawLayer.strokeEnd = 0
drawLayer.lineWidth = halfWidth
drawLayer.mask = shapeLayer
layer.addSublayer(drawLayer)
// Optional, but looks nice
anim.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
}
我们在这里所做的就是将路径组分配给 CAShapeLayer
,我们将其用作 drawLayer
的遮罩,我们将在视图周围设置动画(使用拱形路径上的一笔)。