创建圆形动画的正确方法是什么?

时间:2016-01-22 22:17:01

标签: ios swift animation drawing core-graphics

我刚看到这张图片,对我来说很有趣,如何在Swift中创建这种类型的动画:

enter image description here

所以,我的圆圈里有很多灰色牙齿,当我设定角度时,例如45度,它会将这些灰色的牙齿填充到0..45度内的蓝色。

你可以向我解释正确的做法,或者你可以展示不同的片段(这会很棒)。之后我会搜索或阅读它。

提前致谢!

2 个答案:

答案 0 :(得分:5)

如果你只需要个人的牙齿'要改变颜色,而不是使用牙齿作为固体填充的蒙版,您可以使用Core Graphics而不是Core Animation(尽管通常首选Core Animation)。所以为了做到这一点,我们应该做到以下几点:

  1. 要插入我们的绘图代码的子类UIView
  2. 创建包含在UIBezierPath
  3. 中的路径对象数组
  4. 设置计时器以更新进度值并setNeedsDisplay
  5. drawRect:中,绘制路径并根据进度为每个路径指定填充
  6. 首先,让我们定义我们将在这个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
    }
    

    这很简单。我们只是为单个牙齿创造了一条道路。然后复制这条路径,看看我们需要多少牙齿,为每一颗牙齿平移和旋转路径。

    接下来我们要设置我们的视图。我为计时器转到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)
        }
    }
    

    如果您想沿着核心动画路径前进,I adapted this code into a Core Animation layer

    最终结果

    enter image description here

    完整项目:https://github.com/hamishknight/Circle-Loader

答案 1 :(得分:4)

嗯,本着&#34; 大或回家的精神&#34; (因为我实际上有一些乐趣),我创建了一个核心动画版本of my Core Graphics answer代码相当少,动画更流畅,所以我实际上更喜欢使用它。

首先,让我们再次对UIView进行子类化(这不是必须的,但在单个视图中包含所有内容很不错)并定义我们的变量:< / p>

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

这次,所有路径都被分组为一个大路径,函数返回该路径。

最后,我们只需要创建我们的图层对象&amp;设置动画。

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上的掩码,我们将围绕视图制作动画(使用拱形路径上的一个笔划。)

最终结果

enter image description here

完整项目:https://github.com/hamishknight/Circle-Loader