在 iOS 中是否可以仅在部分文本中设置动画文本颜色变化?
Is is possible to animate text color changing only in a part of text in iOS?
我想知道在 iOS 中是否可以仅在文本的一部分中设置动画颜色变化,最好不是逐个字符,而是逐个像素,就像这张照片一样?
我知道如何使用 NSAttributedString 更改静态文本颜色,我知道如何使用 CADisplayLink 为整个文本设置动画,但这让我很担心。
也许我可以深入研究 CoreText,但我仍然不确定它是否可行。有什么想法吗?
UPD 我决定在我的第一个结果中添加一个视频,以使问题更清楚:
您可以很容易地使用 CoreAnimation 实现这一点。
我已经添加了一个简单的演示,你可以玩它 here(只需构建项目并点击任意位置即可观看动画)。
逻辑如下:
- 创建 UIView 的自定义子类。
- 设置一些文本后,创建两个相似的
CATextLayers
,每个文本和框架都相同。
- 为这些图层设置不同的
foregroundColor
和 mask
。左层的mask
将是视图的左部分,右层的mask
将是右部分。
- 为这些图层设置动画
foregroundColor
(同时)。
自定义视图代码:
class CustomTextLabel: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .green
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private var textLayer1: CATextLayer?
private var textLayer2: CATextLayer?
func setText(_ text: String, fontSize: CGFloat) {
// create 2 layers with the same text and size, we'll set the colors for them later
textLayer1 = createTextLayer(text, fontSize: fontSize)
textLayer2 = createTextLayer(text, fontSize: fontSize)
// estimate the frame size needed for the text layer with such text and font size
let textSize = textLayer1!.preferredFrameSize()
let w = frame.width, h = frame.height
// calculate the frame such that both layers will be in center of view
let centeredTextFrame = CGRect(x: (w-textSize.width)/2, y: (h-textSize.height)/2, width: textSize.width, height: textSize.height)
textLayer1!.frame = centeredTextFrame
textLayer2!.frame = centeredTextFrame
// set up default color for the text
textLayer1!.foregroundColor = UIColor.yellow.cgColor
textLayer2!.foregroundColor = UIColor.yellow.cgColor
// set background transparent, that's very important
textLayer1!.backgroundColor = UIColor.clear.cgColor
textLayer2!.backgroundColor = UIColor.clear.cgColor
// set up masks, such that each layer's text is visible only in its part
textLayer1!.mask = createMaskLayer(CGRect(x: 0, y: 0, width: textSize.width/2, height: textSize.height))
textLayer2!.mask = createMaskLayer(CGRect(x: textSize.width/2, y: 0, width: textSize.width/2, height: textSize.height))
layer.addSublayer(textLayer1!)
layer.addSublayer(textLayer2!)
}
private var finishColor1: UIColor = .black, finishColor2: UIColor = .black
func animateText(leftPartColor1: UIColor, leftPartColor2: UIColor, rightPartColor1: UIColor, rightPartColor2: UIColor) {
finishColor1 = leftPartColor2
finishColor2 = rightPartColor2
if let layer1 = textLayer1, let layer2 = textLayer2 {
CATransaction.begin()
let animation1 = CABasicAnimation(keyPath: "foregroundColor")
animation1.fromValue = leftPartColor1.cgColor
animation1.toValue = leftPartColor2.cgColor
animation1.duration = 3.0
layer1.add(animation1, forKey: "animation1")
let animation2 = CABasicAnimation(keyPath: "foregroundColor")
animation2.fromValue = rightPartColor1.cgColor
animation2.toValue = rightPartColor2.cgColor
animation2.duration = 3.0
layer2.add(animation2, forKey: "animation2")
CATransaction.setCompletionBlock {
self.textLayer1?.foregroundColor = self.finishColor1.cgColor
self.textLayer2?.foregroundColor = self.finishColor2.cgColor
}
CATransaction.commit()
}
}
private func createTextLayer(_ text: String, fontSize: CGFloat) -> CATextLayer {
let textLayer = CATextLayer()
textLayer.string = text
textLayer.fontSize = fontSize // TODO: also set font name
textLayer.contentsScale = UIScreen.main.scale
return textLayer
}
private func createMaskLayer(_ holeRect: CGRect) -> CAShapeLayer {
let layer = CAShapeLayer()
let path = CGMutablePath()
path.addRect(holeRect)
path.addRect(bounds)
layer.path = path
layer.fillRule = CAShapeLayerFillRule.evenOdd
layer.opacity = 1
return layer
}
}
自定义视图的调用次数:
class ViewController: UIViewController {
var customLabel: CustomTextLabel!
override func viewDidLoad() {
super.viewDidLoad()
let viewW = view.frame.width, viewH = view.frame.height
let labelW: CGFloat = 200, labelH: CGFloat = 50
customLabel = CustomTextLabel(frame: CGRect(x: (viewW-labelW)/2, y: (viewH-labelH)/2, width: labelW, height: labelH))
customLabel.setText("Optimizing...", fontSize: 20)
view.addSubview(customLabel)
let tapRecogniner = UITapGestureRecognizer(target: self, action: #selector(onTap))
view.addGestureRecognizer(tapRecogniner)
}
@objc func onTap() {
customLabel.animateText(leftPartColor1: UIColor.blue,
leftPartColor2: UIColor.red,
rightPartColor1: UIColor.white,
rightPartColor2: UIColor.black)
}
}
感谢 Olha (@OlhaPavliuk) 的回答,我为文本层使用了两个 CATextLayer 形状和两个 CAShapeLayer 蒙版。在绘图方法中,我只是将蒙版帧更改为计算大小(bounds.width * 进度值),并将第二个蒙版原点更改为新的起点(bounds.width - bounds.width * 进度值)。
此外,在创建蒙版时设置 layer.fillRule = CAShapeLayerFillRule.evenOdd
非常重要,这样两个图层都可见。
事实证明,我实际上不需要任何动画代码,因为改变帧看起来就可以了。
运动中:https://giphy.com/gifs/LMbmlMoxY9oaWhXfO1
完整代码:https://gist.github.com/joliejuly/a792c2ab8d97d304d731a4a5202f741a
我想知道在 iOS 中是否可以仅在文本的一部分中设置动画颜色变化,最好不是逐个字符,而是逐个像素,就像这张照片一样?
我知道如何使用 NSAttributedString 更改静态文本颜色,我知道如何使用 CADisplayLink 为整个文本设置动画,但这让我很担心。
也许我可以深入研究 CoreText,但我仍然不确定它是否可行。有什么想法吗?
UPD 我决定在我的第一个结果中添加一个视频,以使问题更清楚:
您可以很容易地使用 CoreAnimation 实现这一点。 我已经添加了一个简单的演示,你可以玩它 here(只需构建项目并点击任意位置即可观看动画)。
逻辑如下:
- 创建 UIView 的自定义子类。
- 设置一些文本后,创建两个相似的
CATextLayers
,每个文本和框架都相同。 - 为这些图层设置不同的
foregroundColor
和mask
。左层的mask
将是视图的左部分,右层的mask
将是右部分。 - 为这些图层设置动画
foregroundColor
(同时)。
自定义视图代码:
class CustomTextLabel: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .green
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private var textLayer1: CATextLayer?
private var textLayer2: CATextLayer?
func setText(_ text: String, fontSize: CGFloat) {
// create 2 layers with the same text and size, we'll set the colors for them later
textLayer1 = createTextLayer(text, fontSize: fontSize)
textLayer2 = createTextLayer(text, fontSize: fontSize)
// estimate the frame size needed for the text layer with such text and font size
let textSize = textLayer1!.preferredFrameSize()
let w = frame.width, h = frame.height
// calculate the frame such that both layers will be in center of view
let centeredTextFrame = CGRect(x: (w-textSize.width)/2, y: (h-textSize.height)/2, width: textSize.width, height: textSize.height)
textLayer1!.frame = centeredTextFrame
textLayer2!.frame = centeredTextFrame
// set up default color for the text
textLayer1!.foregroundColor = UIColor.yellow.cgColor
textLayer2!.foregroundColor = UIColor.yellow.cgColor
// set background transparent, that's very important
textLayer1!.backgroundColor = UIColor.clear.cgColor
textLayer2!.backgroundColor = UIColor.clear.cgColor
// set up masks, such that each layer's text is visible only in its part
textLayer1!.mask = createMaskLayer(CGRect(x: 0, y: 0, width: textSize.width/2, height: textSize.height))
textLayer2!.mask = createMaskLayer(CGRect(x: textSize.width/2, y: 0, width: textSize.width/2, height: textSize.height))
layer.addSublayer(textLayer1!)
layer.addSublayer(textLayer2!)
}
private var finishColor1: UIColor = .black, finishColor2: UIColor = .black
func animateText(leftPartColor1: UIColor, leftPartColor2: UIColor, rightPartColor1: UIColor, rightPartColor2: UIColor) {
finishColor1 = leftPartColor2
finishColor2 = rightPartColor2
if let layer1 = textLayer1, let layer2 = textLayer2 {
CATransaction.begin()
let animation1 = CABasicAnimation(keyPath: "foregroundColor")
animation1.fromValue = leftPartColor1.cgColor
animation1.toValue = leftPartColor2.cgColor
animation1.duration = 3.0
layer1.add(animation1, forKey: "animation1")
let animation2 = CABasicAnimation(keyPath: "foregroundColor")
animation2.fromValue = rightPartColor1.cgColor
animation2.toValue = rightPartColor2.cgColor
animation2.duration = 3.0
layer2.add(animation2, forKey: "animation2")
CATransaction.setCompletionBlock {
self.textLayer1?.foregroundColor = self.finishColor1.cgColor
self.textLayer2?.foregroundColor = self.finishColor2.cgColor
}
CATransaction.commit()
}
}
private func createTextLayer(_ text: String, fontSize: CGFloat) -> CATextLayer {
let textLayer = CATextLayer()
textLayer.string = text
textLayer.fontSize = fontSize // TODO: also set font name
textLayer.contentsScale = UIScreen.main.scale
return textLayer
}
private func createMaskLayer(_ holeRect: CGRect) -> CAShapeLayer {
let layer = CAShapeLayer()
let path = CGMutablePath()
path.addRect(holeRect)
path.addRect(bounds)
layer.path = path
layer.fillRule = CAShapeLayerFillRule.evenOdd
layer.opacity = 1
return layer
}
}
自定义视图的调用次数:
class ViewController: UIViewController {
var customLabel: CustomTextLabel!
override func viewDidLoad() {
super.viewDidLoad()
let viewW = view.frame.width, viewH = view.frame.height
let labelW: CGFloat = 200, labelH: CGFloat = 50
customLabel = CustomTextLabel(frame: CGRect(x: (viewW-labelW)/2, y: (viewH-labelH)/2, width: labelW, height: labelH))
customLabel.setText("Optimizing...", fontSize: 20)
view.addSubview(customLabel)
let tapRecogniner = UITapGestureRecognizer(target: self, action: #selector(onTap))
view.addGestureRecognizer(tapRecogniner)
}
@objc func onTap() {
customLabel.animateText(leftPartColor1: UIColor.blue,
leftPartColor2: UIColor.red,
rightPartColor1: UIColor.white,
rightPartColor2: UIColor.black)
}
}
感谢 Olha (@OlhaPavliuk) 的回答,我为文本层使用了两个 CATextLayer 形状和两个 CAShapeLayer 蒙版。在绘图方法中,我只是将蒙版帧更改为计算大小(bounds.width * 进度值),并将第二个蒙版原点更改为新的起点(bounds.width - bounds.width * 进度值)。
此外,在创建蒙版时设置 layer.fillRule = CAShapeLayerFillRule.evenOdd
非常重要,这样两个图层都可见。
事实证明,我实际上不需要任何动画代码,因为改变帧看起来就可以了。
运动中:https://giphy.com/gifs/LMbmlMoxY9oaWhXfO1
完整代码:https://gist.github.com/joliejuly/a792c2ab8d97d304d731a4a5202f741a