【问题标题】:CABasicAnimation runs after Implicit AnimationCABasicAnimation 在隐式动画之后运行
【发布时间】:2016-07-19 23:20:14
【问题描述】:

我想要一个层的行为如下:

相反,它的行为是这样的:

卡片翻转动画是由两个CABasicAnimations 应用在一个CAAnimationGroup 中创建的。发生不正确的旋转效果是因为CALayer 属性更改的隐式动画首先运行,然后我在CABasicAnimation 中指定的动画运行。如何阻止隐式动画运行,以便仅运行我指定的动画?

以下是相关代码:

class ViewController: UIViewController {

  var simpleLayer = CALayer()

  override func viewDidLoad() {
    super.viewDidLoad()

    let tap = UITapGestureRecognizer(target: self, action: #selector(handleTap))
    self.view.addGestureRecognizer(tap)

    simpleLayer.frame = CGRect(origin: CGPoint(x: view.bounds.width / 2 - 50, y: view.bounds.height / 2 - 50), size: CGSize(width: 100, height: 100))
    simpleLayer.backgroundColor = UIColor.blackColor().CGColor
    view.layer.addSublayer(simpleLayer)
  }

  func handleTap() {
    let xRotation = CABasicAnimation(keyPath: "transform.rotation.x")
    xRotation.toValue = 0
    xRotation.byValue = M_PI

    let yRotation = CABasicAnimation(keyPath: "transform.rotation.y")
    yRotation.toValue = 0
    yRotation.byValue = M_PI

    simpleLayer.setValue(M_PI, forKeyPath: "transform.rotation.y")
    simpleLayer.setValue(M_PI, forKeyPath: "transform.rotation.x")

    let group = CAAnimationGroup()
    group.animations = [xRotation, yRotation]
    group.duration = 0.6
    group.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
    simpleLayer.addAnimation(group, forKey: nil)
  }
}

【问题讨论】:

  • 如果setValue(_:forKeyPath:) 被Core Animation 监控以激活隐式动画,我实际上并不肯定。如果你用CATransaction that disables actions 包围这两个函数调用,这能解决问题吗?
  • 谢谢你,@LucasTizma!那是正确的代码。我在下面的答案中对其进行了全面描述。
  • 我收到了对原始问题的反对票,所以现在的值为“-1”。我不知道为什么我收到了反对票。如果反对者可以发表评论,我很乐意解决他或她的担忧。这并不像看起来那样具体。这个问题普遍适用。除非您添加代码以禁用隐式动画,否则 CAAnimationGroup 不会按预期运行。

标签: ios swift core-animation calayer cabasicanimation


【解决方案1】:

@LucasTizma 有正确的答案。

CATransaction.begin(); CATransaction.setDisableActions(true)CATransaction.commit() 环绕您的动画。这将disable the implicit animation 并使CAAnimationGroup 正确动画。

这是最终结果:

这是 Swift 3 中重要的代码 sn-p:

CATransaction.begin()
CATransaction.setDisableActions(true)

let xRotation = CABasicAnimation(keyPath: "transform.rotation.x")
xRotation.toValue = 0
xRotation.byValue = M_PI

let yRotation = CABasicAnimation(keyPath: "transform.rotation.y")
yRotation.toValue = 0
yRotation.byValue = M_PI

simpleLayer.setValue(M_PI, forKeyPath: "transform.rotation.x")
simpleLayer.setValue(M_PI, forKeyPath: "transform.rotation.y")

let group = CAAnimationGroup()
group.animations = [xRotation, yRotation]
simpleLayer.add(group, forKey: nil)

CATransaction.commit()

这是使用 iOS 应用程序绘制动画的完整代码:

class ViewController: UIViewController {

  var simpleLayer = CALayer()

  override func viewDidLoad() {
    super.viewDidLoad()

    let tap = UITapGestureRecognizer(target: self, action: #selector(handleTap))
    self.view.addGestureRecognizer(tap)

    let ratio: CGFloat = 1 / 5
    let viewWidth = view.bounds.width
    let viewHeight = view.bounds.height
    let layerWidth = viewWidth * ratio
    let layerHeight = viewHeight * ratio

    let rect = CGRect(origin: CGPoint(x: viewWidth / 2 - layerWidth / 2,
                                      y: viewHeight / 2 - layerHeight / 2),
                      size: CGSize(width: layerWidth, height: layerHeight))

    let topRightPoint = CGPoint(x: rect.width, y: 0)
    let bottomRightPoint = CGPoint(x: rect.width, y: rect.height)
    let topLeftPoint = CGPoint(x: 0, y: 0)

    let linePath = UIBezierPath()

    linePath.move(to: topLeftPoint)
    linePath.addLine(to: topRightPoint)
    linePath.addLine(to: bottomRightPoint)
    linePath.addLine(to: topLeftPoint)

    let maskLayer = CAShapeLayer()
    maskLayer.path = linePath.cgPath

    simpleLayer.frame = rect
    simpleLayer.backgroundColor = UIColor.black.cgColor
    simpleLayer.mask = maskLayer

    // Smooth antialiasing
    // * Convert the layer to a simple bitmap that's stored in memory
    // * Saves CPU cycles during complex animations
    // * Rasterization is set to happen during the animation and is disabled afterwards
    simpleLayer.rasterizationScale = UIScreen.main.scale

    view.layer.addSublayer(simpleLayer)
  }

  func handleTap() {
    CATransaction.begin()
    CATransaction.setDisableActions(true)
    CATransaction.setCompletionBlock({
      self.simpleLayer.shouldRasterize = false
    })

    simpleLayer.shouldRasterize = true

    let xRotation = CABasicAnimation(keyPath: "transform.rotation.x")
    xRotation.toValue = 0
    xRotation.byValue = M_PI

    let yRotation = CABasicAnimation(keyPath: "transform.rotation.y")
    yRotation.toValue = 0
    yRotation.byValue = M_PI

    simpleLayer.setValue(M_PI, forKeyPath: "transform.rotation.x")
    simpleLayer.setValue(M_PI, forKeyPath: "transform.rotation.y")

    let group = CAAnimationGroup()
    group.animations = [xRotation, yRotation]
    group.duration = 1.2
    group.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
    simpleLayer.add(group, forKey: nil)

    CATransaction.commit()
  }
}

【讨论】:

  • 很高兴听到它成功了!附带说明一下,您应该能够在动画期间通过栅格化图层(并将栅格化比例设置为主屏幕的比例)来修复这些锯齿边缘。
  • 谢谢@LucasTizma。这也很好用!我在答案中添加了平滑抗锯齿的代码。 ?
  • 理想情况下,您只在动画期间光栅化并在之后禁用它。您可以再次使用事务to know when your animations are all done,这样您就可以禁用光栅化。
  • @LucasTimza 这是有道理的。我再次编辑了答案以合并该更改。感谢您的所有帮助!
【解决方案2】:

您正在创建 2 个单独的动画并将它们应用到一个动画组中。当您这样做时,它们将作为 2 个离散步骤应用。

看起来这不是你想要的。如果不是,则不要创建 2 个单独的动画,一个在 transform.rotation.x 上,另一个在 transform.rotation.y 上。相反,将两个更改连接到一个变换矩阵上,并将更改后的变换矩阵应用为单个动画。

【讨论】:

  • 根据您的反馈,我尝试了这段代码,但仍然不对:gist.github.com/benmorrow/174e4f16053ac979ed62cee0d82c9560
  • 尝试首先对变换应用一个绕 x 的 PI 旋转,然后是一个绕 y 的 PI 旋转。这与沿 1,1,0 到 0,0,0 线的 PI 旋转不同。
  • 是的,你是对的。这个效果差不多。如果您想查看新代码,我已从上一条评论中更新了要点。但是,我要找的效果还是没有,因为它生成的动画相当于绕z轴旋转。这是一个 GIF:i.imgur.com/FpQMOHy.gifv 我正在寻找正面到背面的翻转,就像在主要问题的第一个 GIF 中一样
  • 这不是真的。 CAAnimationGroup 不会按顺序提交其动画。它们同时应用(或相对于每个动画的beginTime)。
猜你喜欢
  • 1970-01-01
  • 2013-01-31
  • 1970-01-01
  • 1970-01-01
  • 2012-09-24
  • 1970-01-01
  • 2011-08-28
相关资源
最近更新 更多