如何在ios中绘制多种颜色的动画路径?

时间:2015-06-09 16:34:58

标签: ios swift animation drawing

我需要在我的iOS应用中绘制类似下图的内容,但弧线可能包含更多颜色:

Path to draw

我知道如何绘制它,但我正在寻找一种方法来绘制路径的绘图。

有一个类似的问题here,但圈子没有动画。 This是另一个问题,它解释了如何为圆圈设置动画,并且在我的情况下它可以正常工作,除了它不会处理路径中的多种颜色。

如何做到这一点?

3 个答案:

答案 0 :(得分:7)

我找到了一个非常有效的通用解决方案。由于无法绘制不同颜色的独特路径,因此我只绘制了不带动画的所有不同颜色的路径,这些路径构成了我想要的路径。之后,我在反方向绘制了一条覆盖所有路径的独特路径,并将动画应用于此路径。

例如,在上面的例子中,我使用以下代码绘制两个弧:

class CircleView: UIView {

    let borderWidth: CGFloat = 20

    let startAngle = CGFloat(Double.pi)
    let middleAngle = CGFloat(Double.pi + Double.pi / 2)
    let endAngle = CGFloat(2 * Double.pi)
    var primaryColor = UIColor.red
    var secondaryColor = UIColor.blue
    var currentStrokeValue = CGFloat(0)

    override func draw(_ rect: CGRect) {
        let center = CGPoint(x: self.frame.width / 2, y: self.frame.height / 2)
        let radius = CGFloat(self.frame.width / 2 - borderWidth)
        let path1 = UIBezierPath(arcCenter: center, radius: radius, startAngle: startAngle, endAngle: middleAngle, clockwise: true)
        let path2 = UIBezierPath(arcCenter: center, radius: radius, startAngle: middleAngle, endAngle: endAngle, clockwise: true)
        path1.lineWidth = borderWidth
        primaryColor.setStroke()
        path1.stroke()
        path2.lineWidth = borderWidth
        secondaryColor.setStroke()
        path2.stroke()
    }
}

之后,我获得路径path3,然后将其添加到将添加到视图的图层中:

var path3 = UIBezierPath(arcCenter: center, radius: radius, startAngle: endAngle, endAngle: startAngle, clockwise: true)

注意在此路径中它以相反的顺序覆盖前两个路径,因为它的startAngle等于endAngle的值,其endAngle等于startAngle并且设置了顺时针属性到true。这条路是我要去动画的路径。

例如,如果我想显示整个(虚构)路径的40%(由不同颜色的路径组成的路径),我将其翻译为显示我的封面路径的{60%path3 。我们可以在问题中提供的链接中找到path3动画的方式。

答案 1 :(得分:0)

[self.collectionView reloadData];
for (int i=0; i<self.dataSource_mix.count; i++) { //The datasource here is only for example, please use the corresponding one. 
    YourModel *model = self.dataSource_mix[i];
    NSIndexPath *indexPath = [NSIndexPath indexPathForRow:i inSection:0];
    if (model.selected) {
        [collectionView selectItemAtIndexPath:indexPath animated:NO scrollPosition:UICollectionViewScrollPositionNone];
    }
    else{
        [collectionView deselectItemAtIndexPath:indexPath animated:NO scrollPosition:UICollectionViewScrollPositionNone];
    }
}

答案 2 :(得分:0)

@Reynaldo Aguilar的答案很好。您可以通过创建遮罩层并对框架进行动画处理来实现相同的目的。这种方法的好处是可以在任何(和多色)背景下使用。这种方法的缺点是,如果生产线无法正常运行,则可能无法正常工作,因为在这种情况下,可能同时(当您只希望其中一个要隐藏)隐藏/显示多个点。

为便于使用,您可以将UIView子类化,并覆盖其draw和init方法,例如添加行。

INIT:

init(frame: CGRect, path: [UIBezierPath], strokeColor: [UIColor], fillColor: [UIColor], lineWidth: [CGFloat]) {
    // Initialize the view
    super.init(frame: frame)

    self.paths = path
    self.strokeColors = strokeColor
    self.fillColors = fillColor
    self.lineWidths = lineWidth

    self.backgroundColor = UIColor.clear // Background will always be clear by default
}

DRAW:

override func draw(_ rect: CGRect) {
    super.draw(rect)

    guard paths.count == strokeColors.count && strokeColors.count == fillColors.count && fillColors.count == lineWidths.count else {
        print("ERROR: ARRAYS DON'T MATCH") // Stronger error handling recommended
        return
    }

    for psfl in 0..<paths.count {
        // Fill path if appropriate
        self.fillColors[psfl].setFill()
        self.paths[psfl].fill()

        self.strokeColors[psfl].setStroke()
        self.paths[psfl].lineWidth = self.lineWidths[psfl]
        self.paths[psfl].stroke()
    }
}

然后,您可以像这样使遮罩层动画化

动画:

func animate(startingRect: CGRect, duration: Double, animationKey: String) {
    // Create a path based on the starting rect
    let maskPath = UIBezierPath(rect: startingRect)
    // Create a path based on the final rect
    let finalPath = UIBezierPath(rect: self.frame)

    // Create a shapelayer for the animation block
    let maskLayer = CAShapeLayer()
    maskLayer.frame = startingRect

    // Add the mask layer to the custom view
    self.layer.mask = maskLayer

    // Animation block
    let animation = CABasicAnimation(keyPath: "path")
    animation.delegate = self // (Optionaly) set the delegate so we can remove the mask when the animation completes
    animation.fromValue = maskLayer.path
    animation.toValue = finalPath.cgPath
    animation.duration = duration
    maskLayer.add(animation, forKey: animationKey) // Add the animation to the mask layer

    // Necessary for the animation to work properly
    CATransaction.begin()
    CATransaction.setDisableActions(true)

    maskLayer.path = maskPath.cgPath
    CATransaction.commit()
}

有了子类,实现起来很容易。对其进行初始化,将其添加为所需的子视图,然后在希望动画开始时调用animate()。如果希望隐藏图形开始,还可以摆弄其他东西,例如在初始化和动画过程中使用Alpha。