GragGesture在项目中是经常用到的,尤其是在做一个些炫酷的动画中,比如一些流行的社交软件,例如:国内的探探,国外的Tinder, 首页都有类似左滑右滑的动效。这些都和Grag手势有关系。下面我们来一起看看吧。
要想让一个视图动起来,那么我们可以设置视图的 offset 属性,在视图drag过程中,把这个值不断的更新给offset就可以实现试图随着手势动起来的效果。
其实,在用法上和上面几节讲的手势方法使用很类似。我们下面用一个带有圆角的正方形来做例子。
需求
- 圆角视图跟着手势移动
- 当手势结束时,把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 表示拖拽的位移距离
我们来复现一下探探的卡片滑动动画。
首先,我们来分析一下动画是什么样的。当你向左向右滑动卡片时,卡片会向左、向右移动 ,其次卡片会有一定的倾斜度 ,还会有一点缩小的效果。
多观察几次,会发现卡片的效果是多个动画结合的效果。动画包含:
- 位移动画(offset)
- 放缩动画(scale)
- 旋转动画(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