DragGesture in SwiftUI

GragGesture在项目中是经常用到的,尤其是在做一个些炫酷的动画中,比如一些流行的社交软件,例如:国内的探探,国外的Tinder, 首页都有类似左滑右滑的动效。这些都和Grag手势有关系。下面我们来一起看看吧。

要想让一个视图动起来,那么我们可以设置视图的 offset 属性,在视图drag过程中,把这个值不断的更新给offset就可以实现试图随着手势动起来的效果。

其实,在用法上和上面几节讲的手势方法使用很类似。我们下面用一个带有圆角的正方形来做例子。

需求

  1. 圆角视图跟着手势移动
  2. 当手势结束时,把offset归零

代码如下:

scss 复制代码
struct DragGestureSample: View {
 @State var offset: CGSize = .zero
    
    var body: some View {
        Rectangle()
            .fill()
            .frame(width: 180, height: 180)
            .cornerRadius(25)
 .offset(offset)
            .gesture(
                DragGesture()
                    .onChanged { value in
                        withAnimation(.spring()) {
                            offset = value.translation
                        }
                    }
                    .onEnded { value in
                        withAnimation(.spring()) {
                            offset = .zero
                        }
                    } 
            )
    }
}

在Drag手势中,value 参数代表当前拖拽手势的信息 ,其中value.translation 表示拖拽的位移距离


我们来复现一下探探的卡片滑动动画。

首先,我们来分析一下动画是什么样的。当你向左向右滑动卡片时,卡片会向左、向右移动 ,其次卡片会有一定的倾斜度 ,还会有一点缩小的效果

多观察几次,会发现卡片的效果是多个动画结合的效果。动画包含:

  1. 位移动画(offset)
  2. 放缩动画(scale)
  3. 旋转动画(rotation)

位移效果

位移效果其实和上面的代码和文章开头的例子一样,都是使用offset来接收一个移动中的位置,从而改变视图的位置,主要就是下面的这段代码,即可实现位移,具体原理和上面的一样

less 复制代码
@State var offset: CGSize = .zero

.offset(offset)
                .gesture(
                    DragGesture()
                        .onChanged { value in
                            withAnimation(.spring()) {
                                offset = value.translation
                            }
                        }
                        .onEnded { value in
                            withAnimation(.spring()) {
                                offset = .zero
                            }
                        }
                )

放缩效果

当我们把卡片往边缘移动时,就会出现卡片放缩的情况 ,如果卡片越靠近屏幕左边或右边,卡片放缩的比例就越小 。卡片放在屏幕中间时,放缩比例为1.0,默认放缩比例也是1.0

那么,主要问题是如何计算这个放缩比例。我们先把基本的代码结构写起来,往.offset方法下面加一行放缩的代码,如下:

less 复制代码
.scaleEffect(getScale())

getScale() 是一个方法,返回一个 CGFloat 类型的放缩值。我们来慢慢调试放缩值

swift 复制代码
func getScale() -> CGFloat {
        let max = UIScreen.main.bounds.width / 2
        let offsetX = abs(offset.width)
        let percentage = offsetX / max
        print("percentage (percentage)")
        return 1.0 - percentage
    }

思考为啥代码会这样写。

首先,我们的视图在屏幕中心 ,我们向左或者向右 可以移动的最大距离就是屏幕的一半大小, 我们用 max 表示。求的视图向左或者向右移动的比例 就用当前视图移动的宽度除以max ,我们用percentage表示,就可以得到移动时的放缩比例。abs是取数值的绝对值,因为不管在屏幕的左边或者右边移动的距离不需要区分正负值,位移由offset来管理,我们这里只处理放缩比例。

为啥要用1.0 减去percentage?

因为我们的视图最开始的比例是1.0, 视图在屏幕中心,随着视图向两侧移动,我们会把视图变小。但是随着视图向两侧移动,我们可以看到percentage的分母是不变的,那么offsetX会越来越大,越接近屏幕边缘,offsetX就会越大。最终percentage会变成1.0,如果不用1减去percentage,效果就会相反,效果就是在屏幕中心是放缩比例是0.0,在两侧是1.0

我们来看看效果。

似乎效果不对,可以看到越靠近边缘,视图就越小,这个没有问题,但是太过于小了。我们需要限定一个最小比例。限定为 percentage 最小值为 0.5

swift 复制代码
func getScale() -> CGFloat {
        let width = UIScreen.main.bounds.width / 2
        let offsetX = abs(offset.width)
        let percentage = offsetX / width
        let value =  1.0 - min(0.5, percentage)
        return value
    }

似乎效果还是不太好,我们可以再把缩放比例调小一点,比例变成原来的0.5倍,让试图的放缩比例变缓慢一点

swift 复制代码
func getScale() -> CGFloat {
        let width = UIScreen.main.bounds.width / 2
        let offsetX = abs(offset.width)
        let percentage = offsetX / width
        return 1.0 - min(0.5, percentage)
    }

最后再看看效果:

似乎这个效果就是我们要的效果,现在位移和放缩都有了,接下来我们看看那旋转动画。

旋转动画

旋转动画的角度计算思路和计算放缩值Scale是一样的,但是略有不同的是,我们要区分正负值 。当我们向左滑动 就要一个负的角度值让视图向左边倾斜 ,当我们向右边滑动 时,需要视图向右边倾斜 ,此时是一个正值

less 复制代码
.rotationEffect(Angle(degrees: getRotation()))

我们继续把上面的这段代码放在放缩代码下面,来具体看看getRotation方法

swift 复制代码
func getRotation() -> Double {
        let max = UIScreen.main.bounds.width / 2
        let currentWidth = offset.width
        let percentage = currentWidth / max
        print("percentage (percentage)")
        let maxAngle: Double = 10
        return Double(percentage * maxAngle)
    }

percentage的原理已经解释过了,以及什么需要正负值区分。

为什么要乘以maxAngle?

因为我们的percentage的最大值通常在[-1.x, 1.x] 之间,为什么这么说,因为我们的max是固定屏幕宽度的一半currentWidth最大值可能会大于max,当你的视图超出屏幕边缘时 ,所以最多percentage也只会大于1一点点, 不会有太多。而如果只是使用percentage作为最后的旋转角度值,那么效果就非常的不明显,所以我乘以10,相当于把角度变大了,效果更明显一些。当然这个值你可以在实际工作中去调整,来达到工作实际场景的需求。

效果如图,还是很不错的。

以上就是Drag手势的基本介绍,本来还有一个例子。但是介于篇幅太长,我会安排在下一章继续讲解另一个例子。

大家有什么看法呢?欢迎留言讨论。

公众号:RobotPBQ

相关推荐
dnekmihfbnmv1 小时前
好用的电容笔有哪些推荐一下?年度最值得推荐五款电容笔分享!
ios·电脑·ipad·平板
Magnetic_h20 小时前
【iOS】单例模式
笔记·学习·ui·ios·单例模式·objective-c
归辞...21 小时前
「iOS」——单例模式
ios·单例模式·cocoa
humiaor1 天前
Xcode报错:No exact matches in reference to static method ‘buildExpression‘
swiftui·xcode
yanling20231 天前
黑神话悟空mac可以玩吗
macos·ios·crossove·crossove24
归辞...1 天前
「iOS」viewController的生命周期
ios·cocoa·xcode
crasowas1 天前
Flutter问题记录 - 适配Xcode 16和iOS 18
flutter·ios·xcode
2401_852403551 天前
Mac导入iPhone的照片怎么删除?快速方法讲解
macos·ios·iphone
SchneeDuan1 天前
iOS六大设计原则&&设计模式
ios·设计模式·cocoa·设计原则
JohnsonXin2 天前
【兼容性记录】video标签在 IOS 和 安卓中的问题
android·前端·css·ios·h5·兼容性