【问题标题】:CABasicAnimation drawing a path does not scale when superView's scale changesCABasicAnimation 绘制路径在 superView 的缩放变化时不缩放
【发布时间】:2016-10-03 08:52:00
【问题描述】:

我有一个将矩形绘制到特定百分比的动画。

为此,我正在绘制CAShapeLayer

func drawTheLayer() -> CAShapeLayer {

    let lineWidth: CGFloat = borderWidth * bounds.size.width / standardSizeWidth
    let cornerRadiusResized: CGFloat = cornerRadiusRanged * bounds.size.width / standardSizeWidth
    let insetRect = CGRectInset(bounds, lineWidth/2.0, lineWidth/2.0)
    let apath = ShapeDraw.createRoundedCornerPath(insetRect, cornerRadius: cornerRadiusResized, percent: percentageRanged)
    let apathLayer = CAShapeLayer()

    apathLayer.frame = bounds
    apathLayer.bounds = insetRect
    apathLayer.path = apath
    apathLayer.strokeColor = AppColor.OnColor.CGColor
    apathLayer.fillColor = nil
    apathLayer.lineWidth = lineWidth
    apathLayer.lineJoin = kCALineJoinRound
    apathLayer.lineCap = kCALineCapRound
    apathLayer.geometryFlipped = true

    let flipTransform = CGAffineTransformMakeScale(1, -1)

    apathLayer.setAffineTransform(flipTransform)
    return apathLayer

}

动画绘图:

func animateToLevel() {
    if percentage <= 0 {
        return
    }

    pathLayer?.removeFromSuperlayer()
    pathLayer?.removeAllAnimations()

    animating = true
    let apathLayer = drawTheLayer()
    layer.addSublayer(apathLayer)
    let pathAnimation = CABasicAnimation(keyPath: "strokeEnd")
    pathAnimation.delegate = self
    pathAnimation.duration = 0.5
    pathAnimation.fromValue = 0.0
    pathAnimation.setValue("levelAnimation", forKey: "animationId")
    apathLayer.addAnimation(pathAnimation, forKey: nil)

    pathLayer = apathLayer
}

与此动画无关,可能会发生另一个动画。矩形superView 的缩放比例。当我开始绘制路径是根据小尺寸的 superView 绘制时出现问题,然后当框架变大时,绘制动画的路径保持不变,这是预期的,但有没有一个合乎逻辑的不老套的解决方案?还是我必须在drawRect 中进行操作

对于 superview,动画正在改变 UIView 动画中的 UILayout 高度常量:

heightOfContainerConstraint.constant = 100 // or 400 when it is expanded
UIView.animateWithDuration(animated ? animationDuration : 0) {
    self.view.layoutIfNeeded()
}

圆角创建代码:

import UIKit

class ShapeDraw {

static func createRoundedCornerPath(rect: CGRect, cornerRadius: CGFloat, percent: CGFloat) -> CGMutablePathRef {

    let piNumber: CGFloat = CGFloat(M_PI)

    // get the 4 corners of the rect
    let topLeft = CGPointMake(rect.origin.x, rect.origin.y)
    let topRight = CGPointMake(rect.origin.x + rect.size.width, rect.origin.y)
    let bottomRight = CGPointMake(rect.origin.x + rect.size.width, rect.origin.y + rect.size.height)
    let bottomLeft = CGPointMake(rect.origin.x, rect.origin.y + rect.size.height)

    // Set 4 corner arc starting angles
    let startAngleTopRight: CGFloat = 3 * piNumber/2
    let startAngleBottomRight: CGFloat = 0
    let startAngleBottomLeft: CGFloat = piNumber / 2
    let startAngleTopLeft: CGFloat = piNumber

    let slices = (CGRectGetWidth(rect) / cornerRadius) * 4
    let partValue: CGFloat = 100 / slices // %100 is total -> 1 piece is 100/16 percent

    let wayToGoLine = CGRectGetWidth(rect) - 2 * cornerRadius
    let linePartValue = partValue * (slices/4 - 2)

    var remainingPercent: CGFloat = percent
    let path = CGPathCreateMutable()
    // move to top left
    CGPathMoveToPoint(path, nil, topRight.x/2, topRight.y)

    // add top right half line
    remainingPercent = addLine(path, x: topRight.x/2 + (wayToGoLine/2 * getConstantForThis(remainingPercent, partValue: linePartValue/2)), y: topRight.y, remainingPercent: remainingPercent, currentPartPercent: linePartValue/2)

    // add top right curve
    let endingAngleTopRight = endingAngleForThis(startAngleTopRight, remainingPercent: remainingPercent, partValue: partValue)
    remainingPercent =  addArc(path, x: topRight.x - cornerRadius, y: topRight.y + cornerRadius, radius: cornerRadius, startAngle: startAngleTopRight, endingAngle: endingAngleTopRight, remainingPercent: remainingPercent, currentPartPercent: partValue * 2)

    // add right line
    remainingPercent = addLine(path, x: bottomRight.x, y: topRight.y + cornerRadius + (wayToGoLine * getConstantForThis(remainingPercent, partValue: linePartValue)), remainingPercent: remainingPercent, currentPartPercent: linePartValue)

    // add bottom right curve
    let endingAngleBottomRight = endingAngleForThis(startAngleBottomRight, remainingPercent: remainingPercent, partValue: partValue)
    remainingPercent = addArc(path, x: bottomRight.x - cornerRadius, y: bottomRight.y - cornerRadius, radius: cornerRadius, startAngle: startAngleBottomRight, endingAngle: endingAngleBottomRight, remainingPercent: remainingPercent, currentPartPercent: partValue * 2)

    // add bottom line
    remainingPercent = addLine(path, x: bottomRight.x - cornerRadius - (wayToGoLine * getConstantForThis(remainingPercent, partValue: linePartValue)), y: bottomLeft.y, remainingPercent: remainingPercent, currentPartPercent: linePartValue)

    // add bottom left curve
    let endingAngleBottomLeft = endingAngleForThis(startAngleBottomLeft, remainingPercent: remainingPercent, partValue: partValue)
    remainingPercent = addArc(path, x: bottomLeft.x + cornerRadius, y: bottomLeft.y - cornerRadius, radius: cornerRadius, startAngle: startAngleBottomLeft, endingAngle: endingAngleBottomLeft, remainingPercent: remainingPercent, currentPartPercent: partValue * 2)

    // add left line
    remainingPercent = addLine(path, x: topLeft.x, y: bottomLeft.y - cornerRadius - (wayToGoLine * getConstantForThis(remainingPercent, partValue: linePartValue)), remainingPercent: remainingPercent, currentPartPercent: linePartValue)

    // add top left curve
    let endingAngleTopLeft = endingAngleForThis(startAngleTopLeft, remainingPercent: remainingPercent, partValue: partValue)
    remainingPercent = addArc(path, x: topLeft.x + cornerRadius, y: topLeft.y + cornerRadius, radius: cornerRadius, startAngle: startAngleTopLeft, endingAngle: endingAngleTopLeft, remainingPercent: remainingPercent, currentPartPercent: partValue * 2)

    // add top left half line
    remainingPercent = addLine(path, x: topLeft.x + cornerRadius + (wayToGoLine/2 * getConstantForThis(remainingPercent, partValue: linePartValue/2)), y: topRight.y, remainingPercent: remainingPercent, currentPartPercent: linePartValue/2)

    return path
}

static func endingAngleForThis(startAngle: CGFloat, remainingPercent: CGFloat, partValue: CGFloat) -> CGFloat {
    return startAngle + (CGFloat(M_PI) * getConstantForThis(remainingPercent, partValue: partValue * 2) / 2)
}

static func getConstantForThis(percent: CGFloat, partValue: CGFloat) -> CGFloat {
    let percentConstant = percent - partValue > 0 ? 1 : percent / partValue
    return percentConstant
}

static func addLine(path: CGMutablePath?, x: CGFloat, y: CGFloat, remainingPercent: CGFloat, currentPartPercent: CGFloat) -> CGFloat {
    if remainingPercent > 0 {
        CGPathAddLineToPoint(path, nil, x, y)
        return remainingPercent - currentPartPercent
    }
    return 0
}

static func addArc(path: CGMutablePath?, x: CGFloat, y: CGFloat, radius: CGFloat, startAngle: CGFloat, endingAngle: CGFloat, remainingPercent: CGFloat, currentPartPercent: CGFloat) -> CGFloat {
    if remainingPercent > 0 {

        CGPathAddArc(path, nil, x, y, radius, startAngle, endingAngle, false)
        return remainingPercent - currentPartPercent
    }
    return 0
}

}

【问题讨论】:

  • @AlessandroOrnano 我已经添加了代码,但我认为没有必要返回一个 CGMutablePathRef 并且函数本身并不重要。
  • 我还添加了屏幕gif,希望有人现在回复:D @AlessandroOrnano
  • 好,这是一个明确的问题。我不知道我是否理解正确,但我试图给出答案。

标签: swift calayer cashapelayer cabasicanimation


【解决方案1】:

本质上,我们有两个动画:

  • 按照矩形路径达到特定百分比
  • 矩形超视图的缩放或变化的边界动画

目标是:这些动画必须在同一时间(一组)一起工作,而不是按顺序工作。

下面的代码只是一个例子,并不遵循确切的属性或自定义目标,我想解释一下在这种情况下我会做什么

    // follow the rectangle path
    let pathAnimation = CABasicAnimation(keyPath: "strokeEnd")
    let cornerRadiusResized: CGFloat = cornerRadiusRanged * bounds.size.width / standardSizeWidth
    let apath = ShapeDraw.createRoundedCornerPath(insetRect, cornerRadius: cornerRadiusResized, percent: percentageRanged)
    pathAnimation.toValue = apath

    // scaling of the rectangle superview
    let newBounds = CGRectMake(self.view.bounds.origin.x, self.view.bounds.origin.y, self.view.bounds.width, self.view.bounds.height)
    let boundsAnimation = CABasicAnimation(keyPath: "bounds")
    boundsAnimation.toValue = NSValue(CGRect:newBounds)

    // The group
    var theGroup: CAAnimationGroup = CAAnimationGroup()
    theGroup.animations = [pathAnimation,boundsAnimation]
    theGroup.duration = 2.0
    theGroup.repeatCount = 1
    theGroup.fillMode = kCAFillModeForwards
    apathLayer.addAnimation(theGroup, forKey: "theGroup")

编辑

如果您需要第三个动画,就像您在 cmets 中所说的那样,以更改 UIButton 维度,您还可以添加:

var buttonAnimation:CABasicAnimation = CABasicAnimation(keyPath: "transform.scale")
pulseAnimation.duration = 2.0
pulseAnimation.toValue = NSNumber(float: 0.5)
pulseAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)

并将其添加到数组中:

theGroup.animations = [pathAnimation,buttonAnimation, boundsAnimation]

【讨论】:

  • 我之前尝试过,但我使用 createRoundedCornerPath 创建的路径在视图缩放时无法缩放。当我使用 dispatch_after(delayTime, dispatch_get_main_queue()) { 缩放后开始绘制动画。
  • 两个动画都必须在主队列中运行。它们按顺序运行还是只是持续时间的问题(不同的 NSTimeInterval)?您是否尝试过更改两个持续时间参数,例如 pathAnimation 比 boundAnimation 长?
  • 实际上有3个动画1.在按钮内绘制路径,2.按钮的缩放3.容器视图的缩放。如果我首先设置动画主队列 3 工作,那么 1 和 2 当然同时工作,因为它是一个组动画。它们都工作相同的持续时间,我已经检查了所有动画的 10.0 秒持续时间以便看得更好。
  • 你的情况如何?三个动画的同步性你解决了吗?
  • 很遗憾没有。因为视图的约束变化动画和按钮的画线动画不能同时处理。它根据已缩放的按钮大小绘制线条,这会导致 gif 中出现问题。当视图的边界发生变化时,我尝试创建一个自定义贝塞尔路径来更改路径的边界。但也没有用:/
猜你喜欢
  • 2012-08-31
  • 2012-09-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-06-14
  • 1970-01-01
相关资源
最近更新 更多