SwiftUI — DragGesture

在 SwiftUI 的 DragGesture 中,DragGesture.Value 是非常重要的类型,封装了用户手势的实时数据(例如当前手指位置、起点位置、相对偏移量、滑动速度等)。其中,属性 predictedEndLocation 是用于预测拖拽手势的 结束位置,这是基于手势的当前速度和方向推算出来的。

以下是对 DragGesture.ValuepredictedEndLocation 的体系化介绍,包括它们的用途、属性、实用场景以及完整应用案例。


1. DragGesture.Value 简介 #

DragGesture.Value 是 SwiftUI 手势系统提供的一个结构体,专用于传递 DragGesture(拖动手势) 的实时状态信息。用户拖拽过程中,每一帧都会触发手势的更新,这些数据由 DragGesture.Value 承载。

重要角色 #

  • 实时捕捉用户手指的位置信息:如开始位置(startLocation)、当前位置(location)。
  • 提供拖拽位移信息:捕获拖动的距离(translation)。
  • 为后续动画预测行为:通过 predictedEndLocation 判断手势完成后的惯性结果,决定动画方向/速度。

主要属性 #

以下是 DragGesture.Value 的核心属性(SwiftUI 官方定义)及其用途:

属性名类型用途
startLocationCGPoint手势的起始位置(即用户开始拖拽时手指的初始位置)。
locationCGPoint手势的当前触摸位置(即用户手指当前拖拽到的位置)。
translationCGSize拖拽的累计偏移量,即从起始位置到当前位置的 x 和 y 方向上的偏移(location - startLocation)。
predictedEndLocationCGPointiOS 系统预测的手势拖拽自然结束位置,基于当前速度和方向预测的坐标点,主要用于惯性动画效果。
velocityCGVector拖拽手势的实时速度(x 和 y 两个方向上的分量)。

属性解释:predictedEndLocation #

1. 定义 #

predictedEndLocation 是一个便捷的系统预测值,它表示 iOS 系统预测手势在当前趋势下自然停止时的位置,预测基于手势的速度、方向和当前点。

优点用途场景
在手势完成(抬手)前预测手势终点,模拟惯性滑动时更加自然。- 实现惯性滑动的动画终点位置。
- 判断向哪个方向惯性滚动。
提供一种近似模拟的方式来估算系统结束的滑动位置,在手势快速拖动中非常实用。- 实现卡片或列表的滑动动画。
- 判断是否完成翻页或触发新动作。

2. 使用 DragGesture.Value 的场景 #

2.1 拖拽交互场景 #

在拖动对象(如视图、卡片)的过程中,你可以实时读取用户的 当前位置偏移位移,并利用这些属性更新视图的动态位置。此外,还能通过 predictedEndLocation 预测惯性效果。

2.2 惯性滑动动画 #

结合 predictedEndLocation,你可以为手势结束(即手指离开屏幕)后的视图设置滑动效果。例如:

  • 拖动的视图,根据惯性滑到 “下一页” 或 “上一页”。
  • 拖动之后自定义弹回或移除(如卡片动画)。

2.3 速度控制的滑动效果 #

利用 velocitypredictedEndLocation,可以根据手势的速率调整动画的时间或滑动的距离。例如,用户滑动越快时,动画的时间越短,滑动距离越远。


3. 使用示例:逐项演示 #

下面用示例分步讲解如何使用 DragGesture.Value 的每个组成部分,特别是 predictedEndLocation 的实际用途。


3.1 基础拖拽效果 #

通过 locationtranslation 计算拖拽中视图的实时位置。

示例代码: #

import SwiftUI

struct DragGestureView: 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 { value in
                    self.offset = value.translation // 实时偏移值
                }
                .onEnded { _ in
                    self.offset = .zero // 手指抬起后重置位置
                }
            )
    }
}

3.2 利用 predictedEndLocation 实现惯性滑动 #

当用户手指抬起后,根据预测的终点位置触发 “惯性滑动动画”。

示例代码: #

import SwiftUI

struct PredictedEndLocationExample: View {
    @State private var offset: CGSize = .zero // 当前偏移量

    var body: some View {
        Circle()
            .fill(Color.green)
            .frame(width: 100, height: 100)
            .offset(offset) // 动态偏移圆的位置
            .gesture(
                DragGesture()
                    .onChanged { value in
                        self.offset = value.translation // 实时更新偏移量
                    }
                    .onEnded { value in
                        // 获取预测的终点位置
                        let predictedEnd = value.predictedEndLocation
                        let current = value.location

                        // 计算预测惯性位移
                        let inertialOffset = CGSize(
                            width: predictedEnd.x - current.x,
                            height: predictedEnd.y - current.y
                        )

                        // 动画移动到预测终点
                        withAnimation(.easeOut) {
                            self.offset.width += inertialOffset.width
                            self.offset.height += inertialOffset.height
                        }
                    }
            )
    }
}

关键点#

  1. 实时更新偏移量: 在手势更新时,根据实时 translation 更新圆的位置。
  2. 预测终点: 当手势完成时,使用预测的 predictedEndLocation
  3. 惯性动画: 动画模拟视图滑向系统预测的终点位置,提供流畅的用户体验。

3.3 滑动方向判断:基于 predictedEndLocation #

通过判断 predictedEndLocationlocation 之间的差值判断滑动方向(左右滑动)。

示例代码: #

import SwiftUI

struct SwipeDirectionExample: View {
    var body: some View {
        VStack {
            Text("Swipe to See Direction")
                .font(.headline)
                .padding()

            Color.gray
                .frame(height: 200)
                .gesture(
                    DragGesture()
                        .onEnded { value in
                            let velocity = value.predictedEndLocation.x - value.location.x

                            if velocity > 0 {
                                print("Swiped Right")
                            } else {
                                print("Swiped Left")
                            }
                        }
                )
        }
    }
}

关键逻辑:

  • 如果 predictedEndLocation.x > location.x:用户右滑(手指向右拖拽)。
  • 如果 predictedEndLocation.x < location.x:用户左滑(手指向左拖拽)。

3.4 卡片滑动效果 #

用拖拽结合惯性判断,让卡片移除或回归初始位置。

示例代码: #

import SwiftUI

struct CardSwipeExample: View {
    @State private var offset: CGSize = .zero // 偏移量

    var body: some View {
        ZStack {
            ForEach(0..<3, id: \.self) { index in
                ZStack {
                    Color.blue
                    Text("Card \(index + 1)")
                        .font(.title)
                        .foregroundColor(.white)
                }
                .frame(width: 300, height: 400)
                .cornerRadius(20)
                .offset(offset)
                .gesture(
                    DragGesture()
                        .onChanged { value in
                            self.offset = value.translation
                        }
                        .onEnded { value in
                            let velocity = value.predictedEndLocation.x - value.location.x

                            withAnimation(.spring()) {
                                // 移除或回归初始位置
                                if abs(velocity) > 200 {
                                    self.offset.width += velocity > 0 ? 500 : -500 // 向右或向左滑动移除
                                } else {
                                    self.offset = .zero // 恢复初始位置
                                }
                            }
                        }
                )
            }
        }
    }
}

关键点:

  1. velocity 判断滑动速度:快滑时,移除卡片;慢滑时回弹到原位置。
  2. 惯性滑动:通过 predictedEndLocation 决定滑动的终点与方向。

4. 总结:DragGesture.ValuepredictedEndLocation 的作用 #

属性用途
startLocation记录手势起点,常用于计算位移或拖拽比例。
location获取用户当前拖拽的坐标位置,实时更新视图位置。
translation获取相对偏移(当前位置 - 起点位置),常用于动态更新视图的拖拽平移效果。
predictedEndLocation预测系统计算的终点位置,常用于惯性动画,例如快速滑动卡片的去向或视图弹回的终点。
速度计算公式velocity = predictedEndLocation.x - location.x 用于计算滑动速度,判断方向及动画持续时间等。

通过熟练掌握 DragGesture.Value,尤其是 predictedEndLocation 的应用,你可以构建出非常流畅自然的手势交互和惯性动画。如果有更复杂的需求或进一步的问题,欢迎随时提问! 😊

predictedEndLocation #

这行代码:

let velocity = value.predictedEndLocation.x - value.location.x

主要是用于 计算用户手势的水平滑动速度(拖拽速度),在动画拖拽中,这是一个非常关键的计算,用于确定用户当前滑动手势的动态趋势(比如滑动快慢或者拖拽意图),从而指导动画的表现(例如惯性滑动的方向和距离)。


1. 参数解释 #

在拖拽手势中,valueDragGesture.Value,它描述了当前手势的状态以及各种属性。

  • value.location

    • 用户手指当前触碰的屏幕坐标(CGPoint)。
    • 在这里我们取的是 x 坐标(水平位置)。
  • value.predictedEndLocation

    • 系统预测 用户手指在当前滑动速度和方向下,可能结束触摸的位置(也是以一个屏幕坐标 CGPoint 表示)。
    • 这个预测是基于手势的当前速度估算的,提供的值可以帮助我们模拟滑动的惯性。
  • predictedEndLocation.x - location.x

    • 表示 手指当前位置与预测终点之间的水平距离
    • 这个值反映了:以当前速度继续滑动的情况下,手指在 x 方向上可能滑动的距离

2. 这行代码的作用 #

这行代码是用来 近似计算手势的水平滑动速度

公式:

velocity = predictedEndLocation.x - location.x
  • velocity 的含义

    • 它表示当前拖拽手势的水平瞬时速度(以每次手势帧捕获的单位为参考)。
    • 值越大,说明用户的水平滑动越快,越小则说明滑动较慢。
  • 用途

    1. 用在后续惯性动画中,决定动画的方向、加速度和持续时间。
    2. 根据用户拖拽手势速度决定视图的 “是否完成某种动作”(例如页面翻页、内容进入或返回)。

3. 具体场景应用 #

以下是动画拖拽场景中,这行代码的具体用途:

3.1 惯性滑动 #

基于用户的滑动速度,模拟滑动的惯性动画。

  • 如果 velocity 很大(用户快速滑动),惯性滑动的距离就会较远。
  • 如果 velocity 很小(用户慢速拖动),惯性滑动的距离就会较短。

例如,在一个 卡片滑动 动画中:

示例: #

.onEnded { value in
    let velocity = value.predictedEndLocation.x - value.location.x

    withAnimation {
        if abs(velocity) > 200 { // 假设 200 是滑动速度的临界值
            // 执行惯性滑动动画(如移除卡片)
            self.cardPosition = .offscreen
        } else {
            // 回到初始位置(用户没有滑动足够快)
            self.cardPosition = .onscreen
        }
    }
}
  • 用户快速滑动卡片触发 “彻底移除”。
  • 用户慢速操作,卡片会回弹到原位置。

3.2 翻页控制 #

当你做类似 分页动画(Paging) 时,使用 velocity 可以判断用户是 “上一页” 还是 “下一页”,并确定页切换的动画方向。

示例: #

.onEnded { value in
    let velocity = value.predictedEndLocation.x - value.location.x

    if velocity > 0 {
        // 用户快速右滑,翻到上一页
        self.currentPage -= 1
    } else {
        // 用户快速左滑,翻到下一页
        self.currentPage += 1
    }
}
  • 正值(velocity > 0):用户往右拖,翻到上一页。
  • 负值(velocity < 0):用户往左拖,翻到下一页。

3.3 判断方向与拖拽目的 #

您可以通过 velocity 的正负值 来判断用户的滑动方向:

  • velocity > 0:表示用户手势水平向右(拖拽目标往右)。
  • velocity < 0:表示用户手势水平向左(拖拽目标往左)。

在 UI 动画中,这种判断常见于:

  • 滑动关闭页面(右滑返回)。
  • 从左向右拖动时触发视图解锁。

3.4 惯性动画的 “速度” 与 “距离” 结合使用 #

为了更精确地模拟现实中的惯性滑动,velocity 还可以和手指移动的 距离时间 相结合计算加速度。

公式

let speed = velocity / timeDelta // 速度
let distance = velocity * 0.5    // 惯性滑动距离(简单估算公式)

示例: #

.onEnded { value in
    let velocity = value.predictedEndLocation.x - value.location.x

    withAnimation(.easeOut(duration: abs(velocity) / 500)) { // 动画时间基于速度
        if velocity > 0 {
            // 模拟右滑动的惯性动画
            self.cardOffset = CGSize(width: 500, height: 0)
        } else {
            // 模拟左滑动的惯性动画
            self.cardOffset = CGSize(width: -500, height: 0)
        }
    }
}

解释

  • abs(velocity) / 500 动态控制动画时间(速度越大,动画越短,体现惯性越强)。
  • cardOffset 决定手势拖拽后动画的最终位置。

4. 延伸知识:velocity 的含义为什么与速度相关? #

在 SwiftUI 的 DragGesture.Value 中,predictedEndLocation 是基于当前手势的拖拽速度估算的(这由 iOS 系统通过内部算法计算)。简单来说:

  1. predictedEndLocation 是依据当前滑动速度推算出的位置。
  2. 如果用户滑动得很快,则预测的终点会更远;如果滑动得慢,则预测终点更接近当前位置。
  3. 两者的差值 predictedEndLocation.x - location.x 正好可以反映速度(速度越大,差值越明显)。

5. 总结 #

代码作用
let velocity = value.predictedEndLocation.x - value.location.x用于计算用户水平拖拽的瞬时速度(基于当前位置和预测位置的差值)。
主要用途:
1. 惯性动画:如滑动卡片的惯性去向(参考滑动速度)。
2. 翻页:确定用户是否滑动到下一页或上一页(通过速度方向判断)。
3. 滑动方向的判断:右滑(正值)或左滑(负值)。
4. 动态固化动画时间:速度大时动画时间短,速度小时动画时间长,体现惯性滑动的自然效果。

配合惯性滑动、分页动画和手势逻辑,velocity 是动画控制中一个核心的计算指标。如果需要更深入的拖拽动画案例实现,欢迎随时交流! 😊

本文共 3968 字,上次修改于 Jan 13, 2025