如何为 CATextLayer 的字符串设置动画?
How to animate the string of a CATextLayer?
我目前正在使用 AVMutableComposition 在视频中叠加一些文本,我想根据某个时间间隔重复更改字符串。我正在尝试使用核心动画来实现这一点;但是,字符串 属性 似乎不可设置动画。有没有其他方法可以达到目的?谢谢。
代码(无效):
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
}
字符串不是 CATextLayer 上的动画路径。这是您可以在任何 CALayer 上使用的 list of keyPaths。
关于将 Core Animation 与 AVFoundation 一起使用的一些其他重要事项。
- 所有的动画都得去掉false的完成。
- 一个图层只能应用一次关键路径动画。这意味着如果你想添加淡入和淡出的不透明度,它需要组合成一个关键帧动画。在核心动画中,您可以使用 beginTime 并应用两个不同的不透明度动画,但根据我使用 AVFoundation 和核心动画的经验,这不起作用。因此,如果您想在 CALayer 的同一键路径上使用两个不同的(不透明度)动画,您将必须计算出您希望发生的键时间和值。
- 您的离散曲线仍然有效,但您必须计算出关键时间和值。
- 因为字符串不可用作动画 属性 下一个最好的办法是使用多个 CATextLayer 并将它们一个接一个地淡入淡出。这是一个例子。将 CACurrentMediaTime() 替换为 AVCoreAnimationBeginTimeAtZero 以便与 AVFoundation 一起使用。这只是一个例子,所以你可以想象你想要什么。
Example1Using-Discrete
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
}
}
示例 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
}
}
结果:
持续时间为 2 秒进出 2 秒。可以是即时的。
试想一下电影是怎么加字幕的?
字幕文件包含时间戳值,例如 00:31:21 : "Some text"。因此,当电影的搜索栏位于 00:31:21 时,您会看到 "Some text" 作为副标题。
与您的要求类似,您需要多个 CATextLayers
具有与之对应的动画(每个 CAExtLayer 相同或不同)。当一个 CATextLayer 动画结束时,您可以淡入第二个 CATextLayer 并淡出第一个。
我记得上次这样做,动画可以分组,您实际上可以指定动画的起点。检查 beginTime
属性 共 CABasicAnimation
Swift 5 的解决方案:
喜欢@agibson007 的回答
针对 0.25 的持续时间进行了优化,更快是不可能的。 (褪色/闪烁问题)
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)
使用 CATextLayer 和 CAKeyframeAnimation 的文本动画解决方案。现在字符串 属性 是可以动画的。不知道以前怎么样。
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);
}
我目前正在使用 AVMutableComposition 在视频中叠加一些文本,我想根据某个时间间隔重复更改字符串。我正在尝试使用核心动画来实现这一点;但是,字符串 属性 似乎不可设置动画。有没有其他方法可以达到目的?谢谢。
代码(无效):
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
}
字符串不是 CATextLayer 上的动画路径。这是您可以在任何 CALayer 上使用的 list of keyPaths。
关于将 Core Animation 与 AVFoundation 一起使用的一些其他重要事项。
- 所有的动画都得去掉false的完成。
- 一个图层只能应用一次关键路径动画。这意味着如果你想添加淡入和淡出的不透明度,它需要组合成一个关键帧动画。在核心动画中,您可以使用 beginTime 并应用两个不同的不透明度动画,但根据我使用 AVFoundation 和核心动画的经验,这不起作用。因此,如果您想在 CALayer 的同一键路径上使用两个不同的(不透明度)动画,您将必须计算出您希望发生的键时间和值。
- 您的离散曲线仍然有效,但您必须计算出关键时间和值。
- 因为字符串不可用作动画 属性 下一个最好的办法是使用多个 CATextLayer 并将它们一个接一个地淡入淡出。这是一个例子。将 CACurrentMediaTime() 替换为 AVCoreAnimationBeginTimeAtZero 以便与 AVFoundation 一起使用。这只是一个例子,所以你可以想象你想要什么。
Example1Using-Discrete
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
}
}
示例 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
}
}
结果:
持续时间为 2 秒进出 2 秒。可以是即时的。
试想一下电影是怎么加字幕的?
字幕文件包含时间戳值,例如 00:31:21 : "Some text"。因此,当电影的搜索栏位于 00:31:21 时,您会看到 "Some text" 作为副标题。
与您的要求类似,您需要多个 CATextLayers
具有与之对应的动画(每个 CAExtLayer 相同或不同)。当一个 CATextLayer 动画结束时,您可以淡入第二个 CATextLayer 并淡出第一个。
我记得上次这样做,动画可以分组,您实际上可以指定动画的起点。检查 beginTime
属性 共 CABasicAnimation
Swift 5 的解决方案:
喜欢@agibson007 的回答
针对 0.25 的持续时间进行了优化,更快是不可能的。 (褪色/闪烁问题)
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)
使用 CATextLayer 和 CAKeyframeAnimation 的文本动画解决方案。现在字符串 属性 是可以动画的。不知道以前怎么样。
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);
}