SwiftUI 提供了声明式的手势和动画 API,用于创建流畅的交互体验。手势识别器可以直接附加到视图上,动画则通过修饰器实现。

重点:

  • 基础手势的使用方法
  • 组合手势的实现
  • 动画的类型和时机
  • 自定义动画曲线
  • 转场动画效果

基础手势 Link to heading

  1. 点击手势:
struct TapDemo: View {
    @State private var isPressed = false
    
    var body: some View {
        Circle()
            .fill(isPressed ? Color.red : Color.blue)
            .frame(width: 100, height: 100)
            .onTapGesture(count: 1) {
                isPressed.toggle()
            }
    }
}
  1. 长按手势:
struct LongPressDemo: View {
    @State private var isLoading = false
    
    var body: some View {
        Circle()
            .fill(isLoading ? Color.green : Color.gray)
            .frame(width: 100, height: 100)
            .onLongPressGesture(minimumDuration: 1) {
                isLoading = true
            } onPressingChanged: { isPressing in
                // 按压状态变化
            }
    }
}
  1. 拖拽手势:
struct DragDemo: View {
    @State private var offset = CGSize.zero
    
    var body: some View {
        Circle()
            .fill(Color.blue)
            .frame(width: 100, height: 100)
            .offset(offset)
            .gesture(
                DragGesture()
                    .onChanged { gesture in
                        offset = gesture.translation
                    }
                    .onEnded { _ in
                        withAnimation {
                            offset = .zero
                        }
                    }
            )
    }
}

组合手势 Link to heading

  1. 同时识别:
struct CombinedGestureDemo: View {
    @State private var scale = 1.0
    @State private var angle = Angle.zero
    
    var body: some View {
        Image(systemName: "star.fill")
            .font(.system(size: 50))
            .foregroundColor(.yellow)
            .scaleEffect(scale)
            .rotationEffect(angle)
            .gesture(
                SimultaneousGesture(
                    MagnificationGesture()
                        .onChanged { value in
                            scale = value
                        },
                    RotationGesture()
                        .onChanged { value in
                            angle = value
                        }
                )
            )
    }
}
  1. 顺序识别:
struct SequencedGestureDemo: View {
    @State private var offset = CGSize.zero
    @State private var isDragging = false
    
    var body: some View {
        Circle()
            .fill(isDragging ? Color.red : Color.blue)
            .frame(width: 100, height: 100)
            .offset(offset)
            .gesture(
                LongPressGesture(minimumDuration: 0.5)
                    .sequenced(before: DragGesture())
                    .onChanged { value in
                        switch value {
                        case .first(true):
                            isDragging = true
                        case .second(true, let drag):
                            offset = drag?.translation ?? .zero
                        default:
                            break
                        }
                    }
                    .onEnded { _ in
                        isDragging = false
                        offset = .zero
                    }
            )
    }
}

基础动画 Link to heading

  1. 隐式动画:
struct ImplicitAnimationDemo: View {
    @State private var scale = 1.0
    
    var body: some View {
        Circle()
            .fill(Color.blue)
            .frame(width: 100, height: 100)
            .scaleEffect(scale)
            .animation(.spring(), value: scale)
            .onTapGesture {
                scale = scale == 1.0 ? 2.0 : 1.0
            }
    }
}
  1. 显式动画:
struct ExplicitAnimationDemo: View {
    @State private var isRotated = false
    
    var body: some View {
        Button("旋转") {
            withAnimation(.spring(
                response: 0.5,
                dampingFraction: 0.5,
                blendDuration: 0
            )) {
                isRotated.toggle()
            }
        }
        .rotationEffect(Angle(degrees: isRotated ? 360 : 0))
    }
}

自定义动画 Link to heading

  1. 动画曲线:
struct CustomAnimationDemo: View {
    @State private var offset: CGFloat = 0
    
    var body: some View {
        Circle()
            .fill(Color.blue)
            .frame(width: 50, height: 50)
            .offset(x: offset)
            .animation(
                .timingCurve(0.68, -0.6, 0.32, 1.6),
                value: offset
            )
            .onTapGesture {
                offset = offset == 0 ? 200 : 0
            }
    }
}
  1. 重复动画:
struct RepeatAnimationDemo: View {
    @State private var isAnimating = false
    
    var body: some View {
        Circle()
            .fill(Color.blue)
            .frame(width: 50, height: 50)
            .scaleEffect(isAnimating ? 1.5 : 1)
            .opacity(isAnimating ? 0.5 : 1)
            .animation(
                .easeInOut(duration: 1)
                .repeatForever(autoreverses: true),
                value: isAnimating
            )
            .onAppear {
                isAnimating = true
            }
    }
}

转场动画 Link to heading

  1. 视图切换:
struct TransitionDemo: View {
    @State private var showDetail = false
    
    var body: some View {
        VStack {
            if showDetail {
                DetailView()
                    .transition(.move(edge: .trailing))
            } else {
                SummaryView()
                    .transition(.move(edge: .leading))
            }
        }
        .animation(.spring(), value: showDetail)
    }
}
  1. 自定义转场:
struct CustomTransitionDemo: View {
    @State private var show = false
    
    var body: some View {
        VStack {
            Button("切换") {
                show.toggle()
            }
            
            if show {
                Rectangle()
                    .fill(Color.blue)
                    .frame(width: 200, height: 200)
                    .transition(
                        .asymmetric(
                            insertion: .scale.combined(with: .opacity),
                            removal: .slide
                        )
                    )
            }
        }
        .animation(.spring(), value: show)
    }
}

实用技巧 Link to heading

  1. 动画状态监听:
struct AnimationStateDemo: View {
    @State private var isAnimating = false
    @State private var completed = false
    
    var body: some View {
        Circle()
            .fill(completed ? Color.green : Color.blue)
            .frame(width: 100, height: 100)
            .scaleEffect(isAnimating ? 1.5 : 1)
            .animation(.easeInOut(duration: 1), value: isAnimating)
            .onChange(of: isAnimating) { newValue in
                if !newValue {
                    completed = true
                }
            }
            .onTapGesture {
                isAnimating.toggle()
            }
    }
}
  1. 手势状态管理:
struct GestureStateDemo: View {
    @GestureState private var dragState = false
    @State private var position = CGSize.zero
    
    var body: some View {
        Circle()
            .fill(dragState ? Color.red : Color.blue)
            .frame(width: 100, height: 100)
            .offset(position)
            .gesture(
                DragGesture()
                    .updating($dragState) { _, state, _ in
                        state = true
                    }
                    .onChanged { gesture in
                        position = gesture.translation
                    }
                    .onEnded { _ in
                        withAnimation {
                            position = .zero
                        }
                    }
            )
    }
}

手势和动画是创建优秀用户体验的关键要素。SwiftUI 的声明式语法使复杂的交互变得简单直观,同时保持了代码的可维护性。在实际开发中,合理使用这些特性可以显著提升应用的交互体验。