简体   繁体   English

如何为 CATextLayer 的字符串设置动画?

[英]How to animate the string of a CATextLayer?

I am currently overlaying some text in a video using the AVMutableComposition, I would like to change the string repeatedly based on some time interval.我目前正在使用 AVMutableComposition 在视频中叠加一些文本,我想根据某个时间间隔重复更改字符串。 I am trying to use the Core Animation to achieve this;我正在尝试使用 Core Animation 来实现这一点; however, the string property does not seem to be animatable.但是,字符串属性似乎不是可动画的。 Is there any other way to achieve the goal?有没有其他方法可以实现目标? Thanks.谢谢。

Code (not working):代码(不工作):

func getSubtitlesAnimation(withFrames frames: [String], duration: CFTimeInterval)->CAKeyframeAnimation {
    let animation = CAKeyframeAnimation(keyPath:"string")
    animation.calculationMode = kCAAnimationDiscrete
    animation.duration = duration
    animation.values = frames
    animation.keyTimes = [0,0.5,1]
    animation.repeatCount = Float(frames.count)
    animation.isRemovedOnCompletion = false
    animation.fillMode = kCAFillModeForwards
    animation.beginTime = AVCoreAnimationBeginTimeAtZero
    return animation
}

String is not an animatable path on a CATextLayer.字符串不是 CATextLayer 上的动画路径。 Here is the list of keyPaths that you can use on any CALayer.这是您可以在任何 CALayer 上使用的 keyPath 列表

Some other important things about using Core Animation with AVFoundation.关于在 AVFoundation 中使用 Core Animation 的其他一些重要事项。

  • All animations have to have removedCompletion of false.所有动画都必须有 removeCompletion 的 false。
  • You can only apply one key path animation to a layer once.您只能将一个关键路径动画应用到图层一次。 Meaning if you want to add an opacity of fade in and fade out it needs to be combined into a single keyframe animation.这意味着如果您想添加淡入淡出的不透明度,则需要将其组合成一个关键帧动画。 In core animation you could use beginTime and apply two different opacity animations but in my experience with AVFoundation and Core Animation this does not work.在核心动画中,您可以使用 beginTime 并应用两种不同的不透明度动画,但根据我对 AVFoundation 和 Core Animation 的经验,这不起作用。 Because of this you will have to figure out the key times and values for what you would want to occur if you wanted to use two different (ex opacity) animations on the same keypath for the CALayer.因此,如果您想在 CALayer 的同一关键路径上使用两个不同的(ex opacity)动画,您将必须弄清楚您想要发生的关键时间和值。
  • Your discrete curve will still work but you will have to work out the keytimes and values.您的离散曲线仍然有效,但您必须计算出关键时间和值。
  • Because string is not available as an animatable property the next best thing would be to use multiple CATextLayers and fade them in one after the other.因为字符串不能用作可动画属性,所以下一个最好的方法是使用多个 CATextLayers 并将它们一个接一个地淡入淡出。 Here is an example.这是一个例子。 Replace CACurrentMediaTime() with AVCoreAnimationBeginTimeAtZero for use with AVFoundation.将 CACurrentMediaTime() 替换为 AVCoreAnimationBeginTimeAtZero 以与 AVFoundation 一起使用。 This was just an example so you could visualize what you want.这只是一个例子,所以你可以想象你想要什么。

Example1Using-Discrete Example1 使用离散

 import UIKit

class ViewController: UIViewController {

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)

        //center the frame
        let textFrame = CGRect(x: (self.view.bounds.width - 200)/2, y: (self.view.bounds.height - 100)/2, width: 200, height: 50)

        DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()) {
            //animation spacing could be a negative value of half the animation to appear to fade between strings
            self.animateText(subtitles: ["Hello","Good Morning","Good Afternoon","Good Evening","Goodnight","Goodbye"], duration: 2, animationSpacing: 0, frame: textFrame, targetLayer: self.view.layer)
        }
    }

    func animateText(subtitles:[String],duration:Double,animationSpacing:Double,frame:CGRect,targetLayer:CALayer){
        var currentTime : Double = 0
        for x in 0..<subtitles.count{
            let string = subtitles[x]
            let textLayer = CATextLayer()
            textLayer.frame = frame
            textLayer.string = string
            textLayer.font = UIFont.systemFont(ofSize: 20)
            textLayer.foregroundColor = UIColor.black.cgColor
            textLayer.fontSize = 20.0
            textLayer.alignmentMode = kCAAlignmentCenter
            let anim = getSubtitlesAnimation(duration: duration, startTime: currentTime)
            targetLayer.addSublayer(textLayer)
            textLayer.add(anim, forKey: "opacityLayer\(x)")
            currentTime += duration + animationSpacing
        }
    }
    func getSubtitlesAnimation(duration: CFTimeInterval,startTime:Double)->CAKeyframeAnimation {
        let animation = CAKeyframeAnimation(keyPath:"opacity")
        animation.duration = duration
        animation.calculationMode = kCAAnimationDiscrete
        //have to fade in and out with a single animation because AVFoundation
        //won't allow you to animate the same propery on the same layer with
        //two different animations
        animation.values = [0,1,1,0,0]
        animation.keyTimes = [0,0.001,0.99,0.999,1]
        animation.isRemovedOnCompletion = false
        animation.fillMode = kCAFillModeBoth
        //Replace with AVCoreAnimationBeginTimeAtZero for AVFoundation
        animation.beginTime = CACurrentMediaTime() + startTime
        return animation
    }
}

EXAMPLE2-Using a long fade-Gif Attached示例 2-使用附加的长淡入淡出 Gif

import UIKit

class ViewController: UIViewController {

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)

        //center the frame
        let textFrame = CGRect(x: (self.view.bounds.width - 200)/2, y: (self.view.bounds.height - 100)/2, width: 200, height: 50)

        DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()) {
            //animation spacing could be a negative value of half the animation to appear to fade between strings
            self.animateText(subtitles: ["Hello","Good Morning","Good Afternoon","Good Evening","Goodnight","Goodbye"], duration: 4, animationSpacing: -2, frame: textFrame, targetLayer: self.view.layer)
        }
    }

    func animateText(subtitles:[String],duration:Double,animationSpacing:Double,frame:CGRect,targetLayer:CALayer){
        var currentTime : Double = 0
        for x in 0..<subtitles.count{
            let string = subtitles[x]
            let textLayer = CATextLayer()
            textLayer.frame = frame
            textLayer.string = string
            textLayer.font = UIFont.systemFont(ofSize: 20)
            textLayer.foregroundColor = UIColor.black.cgColor
            textLayer.fontSize = 20.0
            textLayer.alignmentMode = kCAAlignmentCenter
            let anim = getSubtitlesAnimation(duration: duration, startTime: currentTime)
            targetLayer.addSublayer(textLayer)
            textLayer.add(anim, forKey: "opacityLayer\(x)")
            currentTime += duration + animationSpacing
        }
    }
    func getSubtitlesAnimation(duration: CFTimeInterval,startTime:Double)->CAKeyframeAnimation {
        let animation = CAKeyframeAnimation(keyPath:"opacity")
        animation.duration = duration
        //have to fade in and out with a single animation because AVFoundation
        //won't allow you to animate the same propery on the same layer with
        //two different animations
        animation.values = [0,0.5,1,0.5,0]
        animation.keyTimes = [0,0.25,0.5,0.75,1]
        animation.isRemovedOnCompletion = false
        animation.fillMode = kCAFillModeBoth
        //Replace with AVCoreAnimationBeginTimeAtZero for AVFoundation
        animation.beginTime = CACurrentMediaTime() + startTime
        return animation
    }
}

RESULT: Duration of 2 seconds in and 2 seconds out.结果:持续时间为 2 秒和 2 秒。 Could be immediate.可以立竿见影。 结果gif

Solution for Swift 5: Swift 5 的解决方案:

like the answer from @agibson007就像@agibson007 的回答一样

optimized for a duration of 0.25, faster wasn´t possible.优化了 0.25 的持续时间,更快是不可能的。 (problems with fading / flicker) (褪色/闪烁问题)

func animateText(subtitles:[String],duration:Double,animationSpacing:Double,frame:CGRect,targetLayer:CALayer){
        var currentTime : Double = 0
        for x in 0..<subtitles.count{
            let textLayer = CATextLayer()
            textLayer.frame = frame
            textLayer.string = subtitles[x]
            textLayer.font = UIFont.systemFont(ofSize: 20)
            textLayer.foregroundColor = UIColor.black.cgColor
            textLayer.fontSize = 40.0
            textLayer.alignmentMode = .center
            let anim = getSubtitlesAnimation(duration: duration, startTime: currentTime)
            textLayer.add(anim, forKey: "opacityLayer\(x)")
            targetLayer.addSublayer(textLayer)
            currentTime += duration + animationSpacing
        }
    }

    func getSubtitlesAnimation(duration: CFTimeInterval,startTime:Double)->CAKeyframeAnimation {
        let animation = CAKeyframeAnimation(keyPath:"opacity")
        animation.duration = duration
        animation.calculationMode = .discrete
        animation.values = [0,1,1,0,0]
        animation.keyTimes = [0,0.00000001,0.999,0.999995,1]
        animation.isRemovedOnCompletion = false
        animation.fillMode = .both
        animation.beginTime = AVCoreAnimationBeginTimeAtZero + startTime // CACurrentMediaTime() <- NO AV Foundation
        return animation
    }

    let steps = 0.25
    let duration = 8.0
    let textFrame = CGRect( x: (videoSize.width / 2) - (90 / 2) , y: videoSize.height * 0.2, width: 90, height: 50)

    var subtitles:[String] = []
    for i in 0...Int(duration / steps) {
        let time = i > 0 ? steps * Double(i) : Double(i)
        subtitles.append(String(format: "%0.1f", time) )
    }

    animateText(subtitles: subtitles, duration: steps, animationSpacing: 0, frame: textFrame, targetLayer: layer)

Solution for text animation using CATextLayer and CAKeyframeAnimation.使用 CATextLayer 和 CAKeyframeAnimation 的文本动画解决方案。 Right now the string property is animate-able.现在 string 属性是可动画的。 Idk how was it in the past.想知道过去怎么样。

func addTextLayer(to layer: CALayer) {
    let myAnimation = CAKeyframeAnimation(keyPath: "string");
    myAnimation.beginTime = 0;
    myAnimation.duration = 1.0;
    myAnimation.values = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"]
    myAnimation.fillMode = CAMediaTimingFillMode.forwards;
    myAnimation.isRemovedOnCompletion = false;
    myAnimation.repeatCount = 1;
    
    let textLayer = CATextLayer();
    textLayer.frame = CGRect(x: 200, y: 300, width: 100, height: 100);
    textLayer.string = "0";
    textLayer.font = UIFont.systemFont(ofSize: 20)
    textLayer.foregroundColor = UIColor.black.cgColor
    textLayer.fontSize = 40.0
    textLayer.alignmentMode = .center
    textLayer.add(myAnimation, forKey: nil);

    layer.addSublayer(textLayer);
}

Try to think how subtitles are added in a movie?试着想想如何在电影中添加字幕?

A subtitle file contains time stamp values like, 00:31:21 : "Some text".字幕文件包含时间戳值,例如 00:31:21 : "Some text"。 So when the seekbar of a movie is at 00:31:21, you see "Some text" as the subtitle.因此,当电影的搜索栏位于 00:31:21 时,您会看到“Some text”作为副标题。

Similarly fo your requirement, you would need multiple CATextLayers which have animations corresponding to them (same or different for each CATExtLayer).与您的要求类似,您需要多个CATextLayers ,它们具有与其对应的动画(每个 CATextLayer 相同或不同)。 When one CATextLayer animation ends, you can fade in your second CATextLayer and Fadeout the first one.当一个 CATextLayer 动画结束时,您可以淡入第二个 CATextLayer 并淡出第一个。

Last time I did this I remember, animations can be grouped and you can actually specify start point of an animation.我记得上次这样做时,动画可以分组,您实际上可以指定动画的起点。 Check beginTime property of CABasicAnimation检查CABasicAnimation beginTime属性

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM