假设我有一个动画师,可以将 View 从 (0, 0)
移动到 (-120, 0)
:
let frameAnimator = UIViewPropertyAnimator(duration: duration, dampingRatio: 0.8)
animator.addAnimations {
switch state:
case .normal: view.frame.origin.x = 0
case .swiped: view.frame.origin.x = -120
}
}
我将它与UIPanGestureRecognizer
一起使用,这样我就可以随着手指的移动不断调整 View 的大小。
当我想在动画开始或结束时添加某种弹跳效果时,就会出现问题。不仅仅是阻尼比,还有弹跳效果。最简单的想象方法是 UITableViewCell 的“滑动删除”功能,您可以将“删除”按钮拖动到超出其实际宽度,然后它会弹回来。
实际上我想要实现的是在 [0, 1]
段之外设置 fractionComplete
属性的方法,因此当分数为 1.2
,偏移量变为 144
而不是其最大值 120。
现在 fractionComplete
的最大值恰好是 1
。
下面是一些可视化此问题的示例:
<小时/>编辑(1 月 19 日):
抱歉我的回复迟了。以下是一些说明:
我不使用UIView.animate(...)
,而是使用UIViewPropertyAnimator
,原因非常具体:它为我处理所有的计时、曲线和速度。
例如,您将 View 拖到了一半。这意味着剩余部分的持续时间应比总持续时间短两倍。或者,如果您拖动了 99% 的距离,它应该几乎立即完成剩余部分。
作为补充,UIViewPropertyAnimator
具有暂停(当用户再次开始拖动时)或反向(当用户开始向左拖动时,但之后他改变了主意并移动了手指向右),我也从中受益。
所有这些对于简单的 UIView 动画来说是不可用的,或者最多需要大量的努力。它只能进行简单的转换,但事实并非如此。
这就是为什么我必须使用某种动画师。
正如我在被发布者删除的答案的评论线程中提到的,对我来说最复杂的部分是模拟摩擦效果:拖动得越远, View 实际移动的越少。就像当您尝试将任何 UIScrollView 拖到其内容之外时一样。
感谢你们的努力,但我认为这两个答案都不相关。只要有时间,我就会尝试使用 UIDynamicAnimator 来实现此行为。可能在最近的一两周内。如果我有任何不错的结果,我将发布我的方法。
<小时/>编辑(1 月 20 日):
我刚刚将一个演示项目上传到 GitHub,其中包括我项目中的所有转换。所以现在您实际上可以了解为什么我需要使用动画师以及如何使用它们:https://github.com/demon9733/bouncingview-prototype
您真正感兴趣的唯一文件是MainViewController+Modes.swift
。与过渡和动画相关的所有内容都包含在那里。
我需要做的是让用户能够将 handle 区域拖动到“隐藏”按钮宽度之外,并具有阻尼效果。向左滑动 handle 区域会出现“隐藏”按钮。
附注我没有真正测试这个演示,因此它可能存在我的主项目中没有的错误。所以你可以安全地忽略它们。
最佳答案
您需要允许平移手势到达所需的 x 位置,并且在平移结束时需要触发动画
实现此目的的一种方法是:
var initial = CGRect.zero
override func viewDidLayoutSubviews() {
initial = animatedView.frame
}
@IBAction func pan(_ sender: UIPanGestureRecognizer) {
let closed = initial
let open = initial.offsetBy(dx: -120, dy: 0)
// 1 manage panning along x direction
sender.view?.center = CGPoint(x: (sender.view?.center.x)! + sender.translation(in: sender.view).x, y: (sender.view?.center.y)! )
sender.setTranslation(CGPoint.zero, in: self.view)
// 2 animate to needed position once pan ends
if sender.state == .ended {
if (sender.view?.frame.origin.x)! > initialOrigin.origin.x {
UIView.animate(withDuration: 1 , animations: {
sender.view?.frame = closed
})
} else {
UIView.animate(withDuration: 1 , animations: {
sender.view?.frame = open
})
}
}
}
编辑 1 月 20 日
为了模拟阻尼效果,专门使用UIViewPropertyAnimator
,
var initialOrigin = CGRect.zero
override func viewDidLayoutSubviews() {
initialOrigin = animatedView.frame
}
@IBAction func pan(_ sender: UIPanGestureRecognizer) {
let closed = initialOrigin
let open = initialOrigin.offsetBy(dx: -120, dy: 0)
// 1. to simulate dampening
var multiplier: CGFloat = 1.0
if animatedView?.frame.origin.x ?? CGFloat(0) > closed.origin.x || animatedView?.frame.origin.x ?? CGFloat(0) < open.origin.x {
multiplier = 0.2
} else {
multiplier = 1
}
// 2. animate panning
sender.view?.center = CGPoint(x: (sender.view?.center.x)! + sender.translation(in: sender.view).x * multiplier, y: (sender.view?.center.y)! )
sender.setTranslation(CGPoint.zero, in: self.view)
// 3. animate to needed position once pan ends
if sender.state == .ended {
if (sender.view?.frame.origin.x)! > initialOrigin.origin.x {
let animate = UIViewPropertyAnimator(duration: 0.3, curve: .easeOut, animations: {
self.animatedView.frame.origin.x = closed.origin.x
})
animate.startAnimation()
} else {
let animate = UIViewPropertyAnimator(duration: 0.3, curve: .easeOut, animations: {
self.animatedView.frame.origin.x = open.origin.x
})
animate.startAnimation()
}
}
}
关于swift - UIViewPropertyAnimator的弹跳效果,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59684727/