SwiftUI:动画文本颜色 - foregroundColor()

23
我一直在尝试对UI的各个部分进行动画处理,但是好像无法对SwiftUI中Text的foregroundColor进行动画处理?我想在状态更改时平滑地切换一些文本的颜色。如果我将Text周围视图的背景颜色进行动画处理,则可以正常工作,但前景颜色不起作用。有人成功地执行过此类更改的动画吗?不确定这是否是Xcode beta的错误或者它的预期功能......
Text(highlightedText)
    .foregroundColor(color.wrappedValue)
    .animation(.easeInOut)

// Error: Cannot convert value of type 'Text' to closure result type '_'

快问一下,你能在 UIKit 中做到这个吗?(我真的不知道。) 我猜现在在 SwiftUI 中你基本上可以做到在 UIKit 中你不能做的事情非常少。 - user7014451
@dfd 快速回答。是的,你可以。 - Mojtaba Hosseini
@MojtabaHosseini,我建议您发布UIKit代码,并让OP将其转换为UIViewRepresentable - user7014451
为了避免被负分投票,我提供了一种不使用UIKit的解决方案。 - Mojtaba Hosseini
1
我在 Reddit 上找到了这个答案,解决了我的问题:https://www.reddit.com/r/SwiftUI/comments/hxbdns/why_foregroundcolorflag_green_red_do_not_animate/ 基本上,添加以下内容: .foregroundColor(.clear) .overlay(Rectangle() .foregroundColor(color.wrappedValue) .mask(Text(highlightedText)) ) - Ludvig W
3个回答

54

有一个更简单的方法,借鉴了苹果公司的“动画视图和转换”教程代码。HikeGraph中GraphCapsule的实例化演示了这一点。

虽然 foregroundColor 不能被动画化,但 colorMultiply 可以。将前景色设置为白色,并使用 colorMultiply 设置实际需要的颜色。要从红色过渡到蓝色:

struct AnimateDemo: View {
    @State private var color = Color.red

    var body: some View {
        Text("Animate Me!")
            .foregroundColor(Color.white)
            .colorMultiply(self.color)
            .onTapGesture {
                withAnimation(.easeInOut(duration: 1)) {
                    self.color = Color.blue
                }
        }
    }
}

struct AnimateDemo_Previews: PreviewProvider {
    static var previews: some View {
        AnimateDemo()
    }
}

所以我实际上在苹果教程中找不到这个参考 https://developer.apple.com/tutorials/swiftui/animating-views-and-transitions#Add-Animations-to-Individual-Views 但这个方法非常有效,谢谢。 - MadeByDouglas
1
这会破坏像表情符号这样的字符,因为它们会变成单色。我发现https://dev59.com/hFMH5IYBdhLWcg3w3Ejb#57836412是一个更健壮的解决方案。 - SchoonSauce

9

enter image description here

SwiftUIUIKit中的Text颜色属性不可动画化,但您可以通过以下方法实现所需结果:

struct ContentView: View {

    @State var highlighted = false

    var body: some View {
        VStack {
            ZStack {
            // Highlighted State
            Text("Text To Change Color")
                .foregroundColor(.red)
                .opacity(highlighted ? 1 : 0)

            // Normal State
            Text("Text To Change Color")
                .foregroundColor(.blue)
                .opacity(highlighted ? 0 : 1)
            }
            Button("Change") {
                withAnimation(.easeIn) {
                    self.highlighted.toggle()
                }
            }
        }
    }
}

您可以将此功能封装在自定义View中,并在任何地方使用它。

很好的答案!希望在未来的SwiftUI版本中,您可以直接实现它。 - user7014451

7

SwiftUI中有一个很好的协议可以让你动画化任何东西,即使是不可动画化的东西(例如文本颜色)。该协议称为AnimatableModifier。

如果您想了解更多信息,我写了一篇完整的文章来解释如何使用此功能:https://swiftui-lab.com/swiftui-animations-part3/

下面是一个示例,演示如何创建这样一个视图:

AnimatableColorText(from: UIColor.systemRed, to: UIColor.systemGreen, pct: flag ? 1 : 0) {
    Text("Hello World").font(.largeTitle)
}.onTapGesture {
    withAnimation(.easeInOut(duration: 2.0)) {
      self.flag.toggle()
    }
}

实现方式:

struct AnimatableColorText: View {
    let from: UIColor
    let to: UIColor
    let pct: CGFloat
    let text: () -> Text

    var body: some View {
        let textView = text()

        // This should be enough, but there is a bug, so we implement a workaround
        // AnimatableColorTextModifier(from: from, to: to, pct: pct, text: textView)

        // This is the workaround
        return textView.foregroundColor(Color.clear)
            .overlay(Color.clear.modifier(AnimatableColorTextModifier(from: from, to: to, pct: pct, text: textView)))
    }

    struct AnimatableColorTextModifier: AnimatableModifier {
        let from: UIColor
        let to: UIColor
        var pct: CGFloat
        let text: Text

        var animatableData: CGFloat {
            get { pct }
            set { pct = newValue }
        }

        func body(content: Content) -> some View {
            return text.foregroundColor(colorMixer(c1: from, c2: to, pct: pct))
        }

        // This is a very basic implementation of a color interpolation
        // between two values.
        func colorMixer(c1: UIColor, c2: UIColor, pct: CGFloat) -> Color {
            guard let cc1 = c1.cgColor.components else { return Color(c1) }
            guard let cc2 = c2.cgColor.components else { return Color(c1) }

            let r = (cc1[0] + (cc2[0] - cc1[0]) * pct)
            let g = (cc1[1] + (cc2[1] - cc1[1]) * pct)
            let b = (cc1[2] + (cc2[2] - cc1[2]) * pct)

            return Color(red: Double(r), green: Double(g), blue: Double(b))
        }

    }
}

尝试了这段代码-非常有用。我需要的是文本从蓝色开始,然后过渡到红色,停留2秒钟,然后再回到蓝色。 - thstart
如果您需要在两个动画之间暂停,可以使用两个单独的动画,并在 DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(4)) { ... } 中启动第二个动画。 - kontiki

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接