SwiftUI 中是否可以控制 withAnimation 动画的执行进度?
你需要的主动控制状态切换动画进度的能力可以通过以下两种方案实现:
方案1:iOS 17+ 原生实现
iOS 17 新增了animationProgress(_:)修饰符,可以直接绑定变量手动控制动画的完成进度,所有关联的过渡、布局动画都会自动基于进度插值,不需要额外维护大量自定义视觉状态:
struct ContentView: View { @State private var showSecondView = false @State private var dragProgress: Double = 0 var body: some View { VStack { ZStack { if !showSecondView { Text("First view") .padding() .background(Color.blue) .transition(.slide) } else { Text("Second view") .padding() .background(Color.red) .transition(.slide) } } // 绑定动画进度到拖动百分比变量 .animationProgress(dragProgress) } .gesture( DragGesture() .onChanged { value in let percentDragged = max(0, min(1, value.translation.width / UIScreen.main.bounds.width)) dragProgress = percentDragged // 首次拖动触发状态切换,后续靠进度控制动画过程 if !showSecondView && percentDragged > 0 { withAnimation(.linear(duration: 1)) { showSecondView = true } } } .onEnded { value in // 松手后根据当前进度决定最终停留状态 let targetProgress = value.translation.width > UIScreen.main.bounds.width * 0.5 ? 1.0 : 0.0 withAnimation(.linear(duration: abs(targetProgress - dragProgress))) { dragProgress = targetProgress } completion: { if targetProgress == 0 { showSecondView = false dragProgress = 0 } } } ) } }
方案2:兼容iOS 13+ 桥接UIKit实现
如果需要支持更低系统版本,可以桥接UIKit的UIViewPropertyAnimator实现,它原生支持fractionComplete属性手动控制动画进度:
import UIKit import SwiftUI struct ContentView: View { @State private var showSecondView = false private let animator = UIViewPropertyAnimator(duration: 1, curve: .linear) init() { // 预先暂停动画,后续完全靠手势控制进度 animator.pauseAnimation() animator.addAnimations { showSecondView.toggle() } } var body: some View { VStack { ZStack { if !showSecondView { Text("First view") .padding() .background(Color.blue) .transition(.slide) } else { Text("Second view") .padding() .background(Color.red) .transition(.slide) } } } .gesture( DragGesture() .onChanged { value in let percentDragged = max(0, min(1, value.translation.width / UIScreen.main.bounds.width)) animator.fractionComplete = percentDragged } .onEnded { value in let percentDragged = value.translation.width / UIScreen.main.bounds.width if percentDragged > 0.5 { // 超过一半进度就完成动画 animator.continueAnimation(withTimingParameters: nil, durationFactor: 1 - percentDragged) } else { // 没到一半就回退到初始状态 animator.isReversed = true animator.continueAnimation(withTimingParameters: nil, durationFactor: percentDragged) } } ) } }
两种方案都不需要手动维护大量offset、opacity之类的独立视觉状态,所有和showSecondView绑定的动画效果都会自动跟随拖动进度做插值。
内容的提问来源于stack exchange,提问作者joshuakcockrell
相关产品推荐
相关产品推荐

