如何正确设置 CABasicAnimation(开始)时间?

Posted

技术标签:

【中文标题】如何正确设置 CABasicAnimation(开始)时间?【英文标题】:How to properly set the CABasicAnimation (begin) time? 【发布时间】:2019-02-01 14:20:50 【问题描述】:

我使用 CABasicAnimation 为存储在数组中的 CAShapeLayers 的颜色设置动画。根据animation.duration,动画显示不规律,我不知道为什么。我怀疑animation.beginTime = CACurrentMediaTime() + delay 有问题

动画说明

动画包括在动画结束后将形状连续闪烁为黄色,然后将其变为黑色。

动画的当前状态

当动画持续时间超过一定时间时,它可以正常工作。

例如持续时间为 2 秒:

但是当我缩短持续时间时,结果却大不相同。

例如,持续时间为 1 秒:

您会注意到,前 10 条左右的动画已经缓存/结束,然后等待并开始为其余形状设置动画。

同样,持续时间为 0.5 秒:

在这种情况下,似乎有更多的动画已经结束(形状为黑色),然后在一定时间后才显示一些动画。您还可以注意到,虽然形状颜色动画应该持续相同的持续时间(0.5 秒),但有些人感觉比其他人更快。

守则

动画是在 UIViewController 类的 viewDidAppear 方法中调用的。

我创建了一个 UIView 自定义类来绘制我的形状,并使用该类的扩展为它们设置动画。

动画颜色的代码:

    enum ColorAnimation
        case continuousSwap
        case continousWithNewColor(color: UIColor)
        case randomSwap
        case randomWithNewColor(color: UIColor)
        case randomFromUsedColors
    


    func animateColors(for duration: Double,_ animationType: ColorAnimation, colorChangeDuration swapColorDuration: Double)
        guard abs(swapColorDuration) != Double.infinity else 
            print("Error in defining the shape color change duration")
            return
        
        let animDuration = abs(duration)
        let swapDuration = abs(swapColorDuration)
        let numberOfSwaps = Int(animDuration / min(swapDuration, animDuration))


        switch animationType 
        case .continousWithNewColor(color: let value):
            var fullAnimation = [CABasicAnimation]()
            for i in (0...numberOfSwaps) 
                let index = i % (self.pLayers.count)
                let fromValue = pLayers[index].pattern.color
                let delay =  Double(i) * swapDuration / 3
                let anim = colorAnimation(for: swapDuration, fromColor: value, toColor: fromValue, startAfter: delay)
                fullAnimation.append(anim)
            

            for i in (0...numberOfSwaps) 
                CATransaction.begin()
                 let index = i % (self.pLayers.count)
                CATransaction.setCompletionBlock 
                    self.pLayers[index].shapeLayer.fillColor = UIColor.black.cgColor
                
                pLayers[index].shapeLayer.add(fullAnimation[i], forKey: "fillColorShape")
                CATransaction.commit()
            
        default:
            ()
        
    

通过颜色变化的持续时间分割动画的整个持续时间(例如如果整个动画是10s并且每个形状在1s内改变颜色,这意味着10个形状会改变颜色)。 然后我使用方法 colorAnimation(for: fromColor, toColor, startAfter:) 创建 CABasicaAnimation 对象。

func colorAnimation(for duration: TimeInterval, fromColor: UIColor, toColor: UIColor, reverse: Bool = false, startAfter delay: TimeInterval) -> CABasicAnimation 
        let anim = CABasicAnimation(keyPath: "fillColor")
        anim.fromValue = fromColor.cgColor
        anim.toValue = toColor.cgColor
        anim.duration = duration
        anim.autoreverses = reverse
        anim.beginTime = CACurrentMediaTime() + delay
        return anim
    

最后我将动画添加到适当的 CAShapeLayer 中。 代码显然可以优化,但我选择继续执行这些步骤以尝试找出它无法正常工作的原因。

目前的尝试

到目前为止,我已经尝试过:

在 colorAnimation 方法中设置和不设置animation.beginTime,包括设置和不设置CACurrentMediaTime():如果我不设置animation.beginTimeCACurrentMediaTime,我根本看不到任何动画。

有和没有指向animation.delegate = self:它没有改变任何东西。

使用 DispatchQueue(将动画存储在 global 中并在 main 中运行),并且怀疑形状没有动画。

我怀疑 beginTime 的某些部分无法正常工作,但可能并非如此,或者仅仅是因为即使形状动画,形状动画持续时间似乎也会有所不同,但它不应该发生变化。

非常感谢提前查看此问题。欢迎任何想法,即使它看起来很牵强,它可以开辟新的方法来解决这个问题!

最好的,

【问题讨论】:

【参考方案1】:

其实duration和swapColorDuration是有关系的

func animateColors(for duration: Double,_ animationType: ColorAnimation, colorChangeDuration swapColorDuration: Double)

当你调用它时,你可能需要保持这种关系

    let colorChangeDuration: TimeInterval = 0.5
    animateColors(for: colorChangeDuration * TimeInterval(pLayers.count), .continousWithNewColor(color: UIColor.black), colorChangeDuration: colorChangeDuration)

也在这里:

 let numberOfSwaps = Int(animDuration / min(swapDuration, animDuration)) - 1

这个值可能比你需要的高一点。

问题出在这个let index = i % (self.pLayers.count)

if numberOfSwaps > self.pLayers.count,有些乐队会是双重动画。

 let numberOfSwaps1 = Int(animDuration / min(swapDuration, animDuration))
 let numberOfSwaps = min(numberOfSwaps1, self.pLayers.count)

剩下的是

       for i in (0..<numberOfSwaps) ... 

现在如果numberOfSwaps &lt; self.pLayers.count。还没完呢。

如果 numberOfSwaps 较大,则可以。

如果需要双重动画,请更改以下内容:

pLayers[index].shapeLayer.add(fullAnimation[i], forKey: nil)

pLayers[index].shapeLayer.add(fullAnimation[i], forKey: "fillColorShape" + String(i))

【讨论】:

感谢您的浏览!事实上duration 是完整动画的时间,而swapDuration 是实际颜色变化的持续时间。问题中解释了它们之间的关系:您运行 x 次颜色变化动画,x 是 由持续时间/swapDuration 产生的整数。在我放入 GIF 的所有示例中,duration 设置为 40s,swapDuration 分别设置为 2s、1s、0.5s。因此,我真的不明白为什么这种关系可以解释动画行为。 最后一行将解决最后一分钟的乐队问题。也许该值大于必要的值。这里的关系是如果 swapDuration 很小并且 numberOfSwaps 将大于 shapeLayer 的计数。所以在动画实际发生之前有更多的时间等待。 我对您的想法进行了一些尝试,它确实产生了影响。我确实意识到,如果duration / swapDuration 的比率低于pLayers.count,则动画会正确显示。我正在挖掘......并会报告回来!谢谢! 我明白你做了什么,谢谢!但问题是:如果动画的持续时间大于所有乐队动画的总和,我希望一些乐队将动画加倍...... 如果是双动画,它们的键应该不同。 pLayers[index].shapeLayer.add(fullAnimation[i], forKey: "fillColorShape")

以上是关于如何正确设置 CABasicAnimation(开始)时间?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 CABasicAnimation 水平移动球并将球停在正确的位置

通过将 .speed 设置为 -1 向后恢复 CABasicAnimation

如何在 CABasicAnimation 结束之前重置它?

如何使用 CABasicAnimation 从一个点移动到另一个点?

CABasicAnimation 无 HUGE_VALF 无限重复?

使用 CABasicAnimation 移动时无法使用 UIButton