首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >将CABasicAnimation设置为-1,将.speed设置为-1,向后恢复.speed

将CABasicAnimation设置为-1,将.speed设置为-1,向后恢复.speed
EN

Stack Overflow用户
提问于 2021-01-12 11:50:20
回答 1查看 305关注 0票数 0

编辑:我已经对这个问题进行了一些重构,并解决了部分问题,现在的问题归结为为什么在恢复动画时表示层会出现故障/闪烁。在这一点上,我接受任何答案,使动画恢复前后随意,没有任何问题。我不确定我使用的方法是否正确,我对Swift还是很陌生的。

注意事项:下面的示例项目,以便更好地理解问题。

在我的项目中,我通过将layer .speed属性设置为0来暂停.speed,然后每当用户滚动滑块时,通过设置层的.timeOffset属性等于UISlider .value属性,交互地更改动画值。按代码:

代码语言:javascript
复制
layer.speed = 0 

然后,当用户幻灯片:

代码语言:javascript
复制
layer.timeOffset = CFTimeInterval(sender.value)

现在,无论何时滑块上的用户手势结束,我都想从与当前动画值相关的起点开始,随意地恢复动画。我找到的唯一可行的、运行平稳的解决方案如下所示,但它只能继续工作:

代码语言:javascript
复制
let pausedTime = layer.timeOffset
layer.speed = 1.0
layer.timeOffset = 0.0
layer.beginTime = 0.0
let timeSincePause = layer.convertTime(CACurrentMediaTime(), from: nil) - pausedTime
layer.beginTime = timeSincePause

然后,我可以在动画完成时再次暂停它:

代码语言:javascript
复制
 DispatchQueue.main.asyncAfter(deadline: .now()+1) {
    layer.timeOffset = 0
    layer.speed = 0
 }

据我所知,.speed不仅定义了结合.duration属性的动画的实际速度,还定义了动画的方向:如果设置一个层的速度等于-1,则动画向后完成。在提到this关于CAMediaTiming如何工作的答案时,我试图更改上面片段的参数,以恢复动画,但没有运气。我以为这会成功的:

代码语言:javascript
复制
let pausedTime = layer.timeOffset
layer.timeOffset = 0.0
layer.beginTime = 0.0
let timeSincePause = layer.convertTime(CACurrentMediaTime(), from: nil) - pausedTime
layer.beginTime = timeSincePause
layer.timeOffset = pausedTime*2
layer.speed = -1.0

但图层从来没有像这样动画片。这个问题似乎与convertTime方法有关。

然后我发现了this问题,它基本上和我的一样,唯一的答案是有一个很好的解决方案。重构一下代码,我可以这么说:

代码语言:javascript
复制
   layer.beginTime = CACurrentMediaTime()
   layer.speed = -1
   DispatchQueue.main.asyncAfter(deadline: .now()+1) {
       layer.timeOffset = 0
       layer.speed = 0
   }

但是,当动画向后播放时是非常不正常的,特别是演示层在简历和完成时都会闪烁。我尝试过各种各样的解决方案,但没有运气,我做了一些推测:

  • 它可能是一个与CAMediaTimingFillMode相关的问题,因为我可以将其设置为.back.forwards,但当恢复动画时,动画既没有处于最终状态,也没有处于初始状态,因此初始帧没有呈现;
  • 是由于我没有保留模态树和表示树
  • 这一事实造成的。

然而,这两个都不能解释,而它闪烁/闪烁,无论是在简历和完成。此外,在我看来,动画的持续时间可能是1当恢复向前,但只有1-偏移时,恢复向后,不确定。

真的不知道实际的问题是什么,以及如何解决这个烂摊子。我们非常欢迎所有的建议。

对于任何感兴趣的人,这里有一个与我类似的示例项目,灵感来自另一个问题(动画正在向前运行,向后运行并捕捉故障,只需调用resumeLayerBackwards())。我知道代码应该重构,但还是可以的。只需复制、粘贴和运行:

代码语言:javascript
复制
import UIKit

class ViewController: UIViewController {

var perspectiveLayer: CALayer = {
    let perspectiveLayer = CALayer()
    perspectiveLayer.speed = 0.0
    return perspectiveLayer
}()

var mainView: UIView = {
    let view = UIView()
    return view
}()

private let slider: UISlider = {
    let slider = UISlider()
    slider.addTarget(self, action: #selector(slide(sender:event:)) , for: .valueChanged)
    return slider
}()

override func viewDidLoad() {
    super.viewDidLoad()
    view.addSubview(slider)
    animate()
}

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    slider.frame = CGRect(x: view.bounds.size.width/3,
                          y: view.bounds.size.height/10*8,
                          width: view.bounds.size.width/3,
                          height: view.bounds.size.height/10)
}

@objc private func slide(sender: UISlider, event: UIEvent) {
    if let touchEvent = event.allTouches?.first {
        
      switch touchEvent.phase {
      case .ended:
        resumeLayer(layer: perspectiveLayer)
      default:
        perspectiveLayer.timeOffset = CFTimeInterval(sender.value)
      }
        
    }
}

private func resumeLayer(layer: CALayer) {
    let pausedTime = layer.timeOffset
    layer.speed = 1.0
    layer.timeOffset = 0.0
    layer.beginTime = 0.0
    let timeSincePause = layer.convertTime(CACurrentMediaTime(), from: nil) - pausedTime
    layer.beginTime = timeSincePause
    DispatchQueue.main.asyncAfter(deadline: .now()+1) {
        layer.timeOffset = 1.0
        layer.speed = 0.0
    }
}

private func resumeLayerBackwards(layer: CALayer) {  
        layer.beginTime = CACurrentMediaTime()
        layer.speed = -1
        DispatchQueue.main.asyncAfter(deadline: .now()+1) {
            layer.timeOffset = 0
            layer.speed = 0
        }
}


private func animate() {
    var transform:CATransform3D = CATransform3DIdentity
    var topSleeve:CALayer
    var middleSleeve:CALayer
    var bottomSleeve:CALayer
    var topShadow:CALayer
    var middleShadow:CALayer
    let width:CGFloat = 300
    let height:CGFloat = 150
    var firstJointLayer:CALayer
    var secondJointLayer:CALayer
    
    mainView = UIView(frame:CGRect(x: 50, y: 50, width: width, height: height*3))
    mainView.backgroundColor = UIColor.yellow
    view.addSubview(mainView)
            
    perspectiveLayer.frame = CGRect(x: 0, y: 0, width: width, height: height*2)
    mainView.layer.addSublayer(perspectiveLayer)
    
    firstJointLayer = CATransformLayer()
    firstJointLayer.frame = mainView.bounds
    perspectiveLayer.addSublayer(firstJointLayer)
    
    topSleeve = CALayer()
    topSleeve.frame = CGRect(x: 0, y: 0, width: width, height: height)
    topSleeve.anchorPoint = CGPoint(x: 0.5, y: 0)
    topSleeve.backgroundColor = UIColor.red.cgColor
    topSleeve.position = CGPoint(x: width/2, y: 0)
    firstJointLayer.addSublayer(topSleeve)
    topSleeve.masksToBounds = true
    
    secondJointLayer = CATransformLayer()
    secondJointLayer.frame = mainView.bounds
    secondJointLayer.frame = CGRect(x: 0, y: 0, width: width, height: height*2)
    secondJointLayer.anchorPoint = CGPoint(x: 0.5, y: 0)
    secondJointLayer.position = CGPoint(x: width/2, y: height)
    firstJointLayer.addSublayer(secondJointLayer)
    
    middleSleeve = CALayer()
    middleSleeve.frame = CGRect(x: 0, y: 0, width: width, height: height)
    middleSleeve.anchorPoint = CGPoint(x: 0.5, y: 0)
    middleSleeve.backgroundColor = UIColor.blue.cgColor
    middleSleeve.position = CGPoint(x: width/2, y: 0)
    secondJointLayer.addSublayer(middleSleeve)
    middleSleeve.masksToBounds = true
    
    bottomSleeve = CALayer()
    bottomSleeve.frame = CGRect(x: 0, y: height, width: width, height: height)
    bottomSleeve.anchorPoint = CGPoint(x: 0.5, y: 0)
    bottomSleeve.backgroundColor = UIColor.gray.cgColor
    bottomSleeve.position = CGPoint(x: width/2, y: height)
    secondJointLayer.addSublayer(bottomSleeve)
    
    firstJointLayer.anchorPoint = CGPoint(x: 0.5, y: 0)
    firstJointLayer.position = CGPoint(x: width/2, y: 0)
    
    topShadow = CALayer()
    topSleeve.addSublayer(topShadow)
    topShadow.frame = topSleeve.bounds
    topShadow.backgroundColor = UIColor.black.cgColor
    topShadow.opacity = 0
    
    middleShadow = CALayer()
    middleSleeve.addSublayer(middleShadow)
    middleShadow.frame = middleSleeve.bounds
    middleShadow.backgroundColor = UIColor.black.cgColor
    middleShadow.opacity = 0
    
    transform.m34 = -1/700
    perspectiveLayer.sublayerTransform = transform
    
    var animation = CABasicAnimation(keyPath: "transform.rotation.x")
    animation.fillMode = CAMediaTimingFillMode.forwards
    animation.isRemovedOnCompletion = false
    animation.duration = 1
    animation.fromValue = 0
    animation.toValue = -90*Double.pi/180
    firstJointLayer.add(animation, forKey: nil)
    
    animation = CABasicAnimation(keyPath: "transform.rotation.x")
    animation.fillMode = CAMediaTimingFillMode.forwards
    animation.isRemovedOnCompletion = false
    animation.duration = 1
    animation.fromValue = 0
    animation.toValue = 180*Double.pi/180
    secondJointLayer.add(animation, forKey: nil)
    
    animation = CABasicAnimation(keyPath: "transform.rotation.x")
    animation.fillMode = CAMediaTimingFillMode.forwards
    animation.isRemovedOnCompletion = false
    animation.duration = 1
    animation.fromValue = 0
    animation.toValue = -160*Double.pi/180
    bottomSleeve.add(animation, forKey: nil)
    
    animation = CABasicAnimation(keyPath: "bounds.size.height")
    animation.fillMode = CAMediaTimingFillMode.forwards
    animation.isRemovedOnCompletion = false
    animation.duration = 1
    animation.fromValue = perspectiveLayer.bounds.size.height
    animation.toValue = 0
    perspectiveLayer.add(animation, forKey: nil)
    
    
    animation = CABasicAnimation(keyPath: "position.y")
    animation.fillMode = CAMediaTimingFillMode.forwards
    animation.isRemovedOnCompletion = false
    animation.duration = 1
    animation.fromValue = perspectiveLayer.position.y
    animation.toValue = 0
    perspectiveLayer.add(animation, forKey: nil)
    
    animation = CABasicAnimation(keyPath: "opacity")
    animation.fillMode = CAMediaTimingFillMode.forwards
    animation.isRemovedOnCompletion = false
    animation.duration = 1
    animation.fromValue = 0
    animation.toValue = 0.5
    topShadow.add(animation, forKey: nil)
    
    animation = CABasicAnimation(keyPath: "opacity")
    animation.fillMode = CAMediaTimingFillMode.forwards
    animation.isRemovedOnCompletion = false
    animation.duration = 1
    animation.fromValue = 0
    animation.toValue = 0.5
    middleShadow.add(animation, forKey: nil)
}
}
EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2021-01-14 07:51:29

我设法消除了示例项目中resumeLayerBackwards(layer:)的故障。其实有两个问题:

  1. 动画完成
  2. 后有一个空屏幕,空屏幕在1 - .timeOffset

中是可见的

因此,问题似乎是动画实际上不仅在.timeOffset期间播放,而且在整个.duration期间播放。而出现空屏幕是因为没有为1 - .timeOffset块定义动画。

回顾一下:CALayer也采用CAMediaTiming协议,就像CAAnimation所做的那样(定义了所有属性:尽管其中一些属性似乎不太清楚如何应用于层)。

使用speed = -1,在.timeOffset秒之后-属性.timeOffset变为零。这意味着动画已经达到了它的起点,因此(以负速度)它就完成了。虽然没有那么明显--因为.fillMode属性,它似乎被删除了。为了解决这个问题,我将perspectiveLayer.fillMode = .forwards添加到animate()方法中。

要让动画在.timeOffset秒之后完全完成,而不是整个.duration --使用.repeatDuration属性。我已经将layer.repeatDuration = layer.timeOffset添加到您的resumeLayerBackwards(layer:)方法中。

该项目只对添加的两行进行工作。

我不能说这个解决方案对我来说确实是合乎逻辑的,尽管它是可行的。负速度对我来说有点难以预测。在我的项目中,我经常通过交换克隆动画对象中的开始值和结束值来反转动画。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/65683369

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档