首页 > 其他分享 >SwiftUI中的手势(DragGesture拖拽手势及Drag动画组件)

SwiftUI中的手势(DragGesture拖拽手势及Drag动画组件)

时间:2024-05-24 08:56:37浏览次数:25  
标签:修饰符 DragGesture dragOffset value width Drag offset position 手势

上一篇文章我们了解了如何使用.gesture修饰符和@GestureState属性包装器,让我们看看另一种常见的手势:DragGesture拖拽手势。

下面先看个效果图:
在这里插入图片描述
这个效果中,我们实现了一个Text文本,并添加了拖拽手势,可以拖动到屏幕的任意位置,松手后停留在目标位置,而非回到原来的起点位置。

UI组件就不多说了,我们在Text组件上添加了.gesture修饰符,并在该修饰符内部添加了DragGesture手势,然后给DragGesture添加了.updating.onEnded修饰符。

关于.updating修饰符上一篇文章已经介绍过了(SwiftUI中的手势(TapGesture、LongPressGesture、GestureState的使用)),这里创建了一个@GestureState修饰的dragOffset变量,用于绑定到.updating修饰符上的参数上。在.updating修饰符的body中进行了赋值操作,这样在拖拽过程中,state将不断地被赋予新的值。

@GestureState private var dragOffset: CGSize = .zero
.updating($dragOffset, body: { value, state, _ in
  state = value.translation
})

同时我们对Text组件添加了.offset(dragOffset)修饰符,并传入dragOffset变量,到此就可以拖拽Text组件了,但是松手后,它会自动地回到原来的位置。

.offset(dragOffset)

为了解决这个问题,我们还需要再来一个变量position记录松手时的位置信息。

@State private var position: CGSize = .zero

并且在.onEnded修饰符的闭包中,给position赋值。该闭包中返回了当前的手势信息变量,而移动信息储存在该变量的translation中。

.onEnded({ value in
  position.width += value.translation.width
  position.height += value.translation.height
})

这里更新了position信息,这里主要注意用了+=运算符,因为拖动不止一次,上一次的结束位置即是下一次的起点位置。如果说拖拽松手后想回到原点,那就移出关于position的相关代码。

最后在给Text添加一个.offset(position)修饰符,并传入position变量。

.offset(position)

到现在已经添加了两个.offset()修饰符,也可以添加一个,但是要将positiondragOffset两个变量加起来传入进去,比如这样:

.offset(CGSize(width: dragOffset.width + position.width, height: dragOffset.height + position.height))

至此,该动画就全部完成了。另外上面的视图中,在界面的顶部加了两个Text,用来显示dragOffsetposition的数值,可以看出,每次动作结束@GestureState修饰的dragOffset都恢复了初始值。

完整代码如下:

struct DragGestureDemo: View {

  @GestureState private var dragOffset: CGSize = .zero
  @State private var position: CGSize = .zero

  var body: some View {
    ZStack {
      VStack {
        Text("dragOffset: \(dragOffset)")
        Text("position: \(position)")
        Spacer()
      }
      Text("Drag me!")
        .font(.title)
        .padding()
        .background(Color.cyan)
        .cornerRadius(10.0)
        .offset(dragOffset)
        .offset(position)
        .gesture(
          DragGesture()
            .updating($dragOffset, body: { value, state, _ in
              state = value.translation
            })
            .onEnded({ value in
              position.width += value.translation.width
              position.height += value.translation.height
            })
      )
    }
  }
}

可能有人会说,我习惯用了.onChange()修饰符,不习惯用.updating()修饰符,下面这个就是用.onChange()修饰符实现的拖动。
在这里插入图片描述
这里需要两个@State修饰的变量来记录信息,dragOffset记录相对于最初位置的偏移量,position记录松手后相对于最初位置的偏移量。

Text设置一个.offset()修饰符即可,要传入dragOffsetdragOffset是被@State修饰器修饰的,在松手后该值不会重置。

.onChange()修饰符闭包内,计算dragOffset的值,dragOffset = 上一次位置偏移量 + 手势偏移量。

.onEnded()修饰符闭包内,计算position的值,position = dragOffset,因为dragOffset不会重置,且也是手离开的时候的偏移量。给position赋值,是为了在.onChange()修饰符闭包内给dragOffset赋值。

最终代码如下:

struct DragGestureDemo2: View {
  @State private var dragOffset: CGSize  = .zero
  @State private var position: CGSize = .zero

  var body: some View {
    ZStack {
      VStack {
        Text("dragOffset: \(dragOffset)")
        Text("position: \(position)")
        Spacer()
      }
      Text("Drag me!")
        .font(.title)
        .padding()
        .background(Color.cyan)
        .cornerRadius(10.0)
        .offset(dragOffset)
        .gesture(
          DragGesture()
            .onChanged({ value in
              dragOffset.width = position.width + value.translation.width
              dragOffset.height = position.height + value.translation.height
            })
            .onEnded({ _ in
              position = dragOffset
            })
      )
    }
  }
}

如果想要在松手后回到原点,那就不需要position记录位置了,直接修改成下面代码即可:

.gesture(
  DragGesture()
    .onChanged({ value in
      dragOffset = value.translation
    })
    .onEnded({ _ in
      dragOffset = .zero
    })
)

以上就是两种方式实现的拖拽动画,下面看一个比较酷的左右滑动卡片的拖拽动画。
在这里插入图片描述
关于这个拖拽动画效果,不做过多的说明了,附上源码,有问题可以留言。

struct DragGestureDemo3: View {
  @State private var offset: CGSize = .zero

  var body: some View {
    Image("liuyifei")
      .resizable()
      .frame(width: 260)
      .aspectRatio(1.0, contentMode: .fit)
      .offset(offset)
      .scaleEffect(getScale())
      .rotationEffect(Angle(degrees: getRotation()))
      .gesture(
        DragGesture()
          .onChanged({ value in
            withAnimation(.spring()) {
              offset = value.translation
            }
          })
          .onEnded({ _ in
            withAnimation(.spring()) {
              offset = .zero
            }
          })
      )
  }

  func getScale() -> CGFloat {
    let max = UIScreen.main.bounds.width / 2
    let current = abs(offset.width)
    let percentage = current / max
    return 1.0 - min(percentage, 0.5) * 0.5
  }

  func getRotation() -> Double {
    let max = UIScreen.main.bounds.width / 2
    let current = offset.width
    let percentage = Double(current / max)
    let maxAngle: Double = 10
    return percentage * maxAngle
  }
}

最后,希望能够帮助到有需要的朋友,如果觉得有帮助,还望点个赞,添加个关注,笔者也会不断地努力,写出更多更好用的文章。

标签:修饰符,DragGesture,dragOffset,value,width,Drag,offset,position,手势
From: https://blog.csdn.net/guoyongming925/article/details/139150139

相关文章

  • 利用Python训练手势模型代码
    importcv2ascvimportosimportnumpyasnpfromsklearn.decompositionimportPCAfromsklearn.model_selectionimporttrain_test_splitfromsklearn.svmimportSVCfromsklearn.treeimportDecisionTreeClassifierfromsklearn.neighborsimportKNeighborsClassifie......
  • 基于毫米波雷达的手势识别神经网络
    具体的软硬件实现点击http://mcu-ai.com/MCU-AI技术网页_MCU-AI概要手势识别是智能教育领域的关键技术,毫米波信号具有分辨率高、穿透能力强等优点。本文介绍了一种基于毫米波雷达的高精度、鲁棒的手势识别方法。该方法包括用毫米波雷达模块捕获手部运动的原始信号,并对接收到的......
  • html------拖拽属性draggable
    draggable用于定义元素是否可以拖拽,在拖拽过程中鼠标会变成禁止标志<imgdraggable="true"/>下面是一个简单的图片拖拽属性添加<!DOCTYPEhtml><html><head> <metacharset="UTF-8"> <title>draggable属性</title></head><body>......
  • HTML5中 drag 和 drop api
    被拖放元素--A,目标元素--B。dragstart事件主体是A,在开始拖放A时触发。dragend事件主体是A,在整个拖放操作结束时触发。drag事件主体是A,正在拖放A时触发(整个拖拽,drag事件会在被拖拉的节点上持续触发,相隔几百毫秒)。dragenter事件主体是B,在A进入某元素的时候触发。drago......
  • 鸿蒙HarmonyOS实战-ArkUI事件(组合手势)
    ......
  • 鸿蒙HarmonyOS实战-ArkUI事件(组合手势)
    ......
  • EPAI手绘建模APP动画、场景、手势操作
    (15) 动画 图 299 动画控制器① 打开动画控制器。播放动画过程中,切换场景观察视角时,自动停止播放。动画编辑参见常用工具栏-更多-动画动画编辑器部分。② 关闭动画控制器。③ 设置动画参数:设置动画总帧数;这只帧率,帧率越大,播放速度越快;设置是否循环播放,如果设置了,动画......
  • 鸿蒙HarmonyOS实战-ArkUI事件(单一手势)
    ......
  • 鸿蒙HarmonyOS实战-ArkUI事件(手势方法)
    ......
  • UEC++做拖拽时的UDragDropOperation 的PayLoad是什么
    在UnrealEngine中,使用C++进行拖拽操作时,UDragDropOperation类的Payload成员变量允许你传递与拖拽操作相关的任何类型的数据。它通常被用来存储一些关于被拖拽元素的信息,这些信息在拖拽开始时被设置,然后可以在拖拽结束时被检索和使用。Payload是一个UObject*类型的指针,这意......