You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI按钮波纹动画异常:波纹被遮挡&模拟器运行失效

问题修复方案

1. 修复ZStack层级遮挡问题

ZStack的渲染逻辑是先添加的视图在底层,后添加的在上层。原代码中波纹被中心圆和按钮完全遮挡,调整顺序让波纹显示在中心圆上层、按钮下层,既保证动画可见,又不影响按钮交互:

ZStack {
    // 中心实心圆(底层)
    Circle()
        .fill(vpnIsActive ? Color("homeBlue") : Color("homeGray"))
        .frame(width: Constants.width * 0.4, height: Constants.width * 0.4)
    
    // 波纹动画(中间层,显示在中心圆上层)
    ForEach(1...4, id: \.self) { i in
        RippleEffect(vpnIsActive: $vpnIsActive, delay: delayArr[i])
    }
    
    // 按钮和文字(最上层,保证可交互)
    VStack {
        Button {
            vpnIsActive.toggle()
        } label: {
            Image("power")
        }
        Text(vpnIsActive ? "Tap to Disconnect" : "Tap to Connect")
            .foregroundColor(.white)
    }
}

2. 修复模拟器动画失效问题

原动画仅靠onAppear触发,状态切换时无法重启,且旧动画写法在模拟器中存在兼容性问题,修改如下:

调整RippleEffect实现

struct RippleEffect: View {
    @Binding var vpnIsActive: Bool
    let delay: Double
    
    // 用vpnIsActive驱动动画,状态变化时自动重置
    private var scale: CGFloat {
        vpnIsActive ? 2.5 : 1.0
    }
    
    var body: some View {
        Circle()
            .fill(vpnIsActive ? 
                  LinearGradient(colors: [Color("homeBlue").opacity(0.5), Color("homeBlue").opacity(0.05)], startPoint: .top, endPoint: .bottom) : 
                  LinearGradient(colors: [Color("homeGray").opacity(0.8), Color("homeGray").opacity(0.1)], startPoint: .top, endPoint: .bottom))
            .scaleEffect(scale)
            .opacity(2 - scale)
            // 绑定vpnIsActive,状态变化时重新触发动画
            .animation(Animation.easeInOut(duration: 2).repeatForever(autoreverses: false).delay(delay), value: vpnIsActive)
            .frame(width: Constants.width * 0.4, height: Constants.width * 0.4) // 和中心圆大小一致,扩散效果更自然
    }
}

关键修改点

  • 移除@State var scale,改用计算属性绑定vpnIsActive,状态切换时自动重置动画
  • 使用animation(_:value:)替代旧写法,确保状态变化时动画可靠触发
  • 波纹初始大小与中心圆一致,让扩散效果更贴合预期

3. 优化波纹数量(可选)

若仅需外侧两个扩散圆形,修改ForEach范围和delayArr:

@State var vpnIsActive = false
let delayArr = [0.0, 0.5] // 对应两个波纹的延迟

// ZStack中修改ForEach
ForEach(0...1, id: \.self) { i in
    RippleEffect(vpnIsActive: $vpnIsActive, delay: delayArr[i])
}

完整修复后代码

import SwiftUI

struct SwiftUIView: View {
    @State var vpnIsActive = false
    let delayArr = [0.0, 0.5, 1.0, 1.5] // 匹配4个波纹的延迟
    
    var body: some View {
        VStack {
            ZStack {
                // 中心实心圆
                Circle()
                    .fill(vpnIsActive ? Color("homeBlue") : Color("homeGray"))
                    .frame(width: Constants.width * 0.4, height: Constants.width * 0.4)
                
                // 波纹动画
                ForEach(0..<delayArr.count, id: \.self) { i in
                    RippleEffect(vpnIsActive: $vpnIsActive, delay: delayArr[i])
                }
                
                // 交互按钮
                VStack {
                    Button {
                        vpnIsActive.toggle()
                    } label: {
                        Image("power")
                    }
                    Text(vpnIsActive ? "Tap to Disconnect" : "Tap to Connect")
                        .foregroundColor(.white)
                }
            }
            .frame(width: Constants.width * 0.4, height: Constants.width * 0.4)
        }
    }
}

struct SwiftUIView_Previews: PreviewProvider {
    static var previews: some View {
        SwiftUIView()
    }
}

struct Constants {
    static let height = UIScreen.main.bounds.height
    static let width = UIScreen.main.bounds.width
}

struct RippleEffect: View {
    @Binding var vpnIsActive: Bool
    let delay: Double
    
    private var scale: CGFloat {
        vpnIsActive ? 2.5 : 1.0
    }
    
    var body: some View {
        Circle()
            .fill(vpnIsActive ? 
                  LinearGradient(colors: [Color("homeBlue").opacity(0.5), Color("homeBlue").opacity(0.05)], startPoint: .top, endPoint: .bottom) : 
                  LinearGradient(colors: [Color("homeGray").opacity(0.8), Color("homeGray").opacity(0.1)], startPoint: .top, endPoint: .bottom))
            .scaleEffect(scale)
            .opacity(2 - scale)
            .animation(Animation.easeInOut(duration: 2).repeatForever(autoreverses: false).delay(delay), value: vpnIsActive)
            .frame(width: Constants.width * 0.4, height: Constants.width * 0.4)
    }
}

内容的提问来源于stack exchange,提问作者Iftikhar Hussain

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 18:06:06