SwiftUI 为多行文本添加动画

问题描述 投票:0回答:1

如何对文本行进行动画处理,以实现笔记应用程序中看到的动画:https://imgur.com/a/eC62tmJ

文本的高度在动画过程中逐渐增加,同时还具有某种不透明度以及应用从上到下偏移的渐变。我不确定如何与波浪效果结合起来实现这一点,其中线条似乎在出现时垂直偏移其位置。 我有以下代码:

struct TextAnimation: View {
    @State var animate = false
    @State var removeGradient = false
    let text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
    
    var body: some View {
        Text(text)
            .padding()
            .frame(height: 300)
            .foregroundStyle(
                LinearGradient(colors: removeGradient ? [Color.black] : [.blue, .red, .green, .yellow], startPoint: .top, endPoint: .bottom)
                    .opacity(animate ? 1 : 0.5)
            )
            .overlay {
                LinearGradient(colors: [.white.opacity(0.8)], startPoint: .top, endPoint: .bottom)
                    .offset(y: animate ? 300 : 0)
                    .animation(.easeIn(duration: 1), value: animate)
            }
            .onAppear(perform: {
                DispatchQueue.main.asyncAfter(wallDeadline: .now() + 1.0) {
                    animate.toggle()
                }
                
                DispatchQueue.main.asyncAfter(wallDeadline: .now() + 2.0) {
                    removeGradient.toggle()
                }
            })
    }
}
ios swiftui
1个回答
0
投票

如果可以单独对文本行进行动画处理,则此动画可能更容易实现。实现此目的的一种方法是将视图切割成代表单独线条的切片。这只需要知道有多少行。

  • 可以根据文本行的总高度和估计高度来计算行数。
  • A
    GeometryReader
    可用于传递总高度。这可以用于覆盖文本的隐藏版本。隐藏版本提供了动画版本的足迹。
  • A
    ScaledMetric
    可用于保存行高的估计值。这将适应不同的文本大小并且应该非常可靠地工作。
  • 通过使用(负)y 偏移将所需的线移动到视图顶部并应用具有单线高度的框架来提取单线。然后可以将文本剪辑到该框架。

将文本分割成单独的行后,可以通过动画删除每行的填充,并为每行使用逐渐更长的延迟来实现波浪效果。

在您链接到的动画中,线条以编辑文本样式的填充块开始。这可以通过在每行的背景中显示圆角矩形来实现。矩形可以在文本淡入的同时淡出。

不透明动画可能希望比波浪动画运行得更快。实现此目的的一种方法是使用带有主体闭合的单独

.animation
修改器来应用不透明度更改,请参阅 animation(_:body:)

所以这里尝试实现类似的动画。它包括您之前用作

foregroundStyle
的彩色渐变,但省略了您作为叠加应用的白色渐变。这个覆盖效果不太好,我不确定它是否真的需要。

动画可能需要更多调整,但我希望它能让您更进一步:

struct TextAnimation: View {
    @State private var animate = false
    @ScaledMetric(relativeTo: .body) private var estimatedHeightOfTenLines = 218.0

    let text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."

    private func textLine(index: Int, frameSize: CGSize, lineHeight: CGFloat) -> some View {
        Text(text)
            .frame(width: frameSize.width, height: frameSize.height)
            .offset(y: CGFloat(-index) * lineHeight)
            .frame(height: lineHeight, alignment: .top)
            .clipped()
    }

    var body: some View {
        VStack(spacing: 100) {
            Text(text)
                .hidden()
                .overlay(alignment: .top) {
                    GeometryReader { proxy in
                        let nLines = Int(round(10.0 * proxy.size.height / estimatedHeightOfTenLines))
                        let lineHeight = nLines > 0 ? proxy.size.height / CGFloat(nLines) : 0
                        VStack(spacing: 0) {
                            ForEach(0..<nLines, id: \.self) { index in
                                textLine(index: index, frameSize: proxy.size, lineHeight: lineHeight)
                                    .animation(.easeInOut(duration: 0.5)) { view in
                                        view.opacity(animate ? 1 : 0)
                                    }
                                    .background {
                                        RoundedRectangle(cornerRadius: 4)
                                            .fill(.gray.opacity(0.5))
                                            .padding(.vertical, 2)
                                            .animation(.easeInOut(duration: 0.5)) { view in
                                                view.opacity(animate ? 0 : 1)
                                            }
                                    }
                                    .padding(.top, animate ? 0 : 5)
                                    .animation(.easeInOut(duration: 0.25).delay(Double(index) * 0.05), value: animate)
                            }
                        }
                        .foregroundStyle(
                            LinearGradient(
                                colors: animate ? [Color.black] : [.blue, .red, .green, .yellow],
                                startPoint: .top,
                                endPoint: .bottom
                            )
                        )
                        .animation(.easeInOut(duration: 1), value: animate)
                        .fixedSize(horizontal: false, vertical: true)
                    }
                }
                .padding()
                .onAppear { animate.toggle() }

            Button("Toggle") { animate.toggle() }
                .buttonStyle(.bordered)
        }
    }
}

Animation

© www.soinside.com 2019 - 2024. All rights reserved.