UIViewPropertyAnimator的弹跳效果

dem*_*733 5 animation bounce uipangesturerecognizer swift uiviewpropertyanimator

假设我有一个动画师将视图从 移动(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
    }
}
Run Code Online (Sandbox Code Playgroud)

我将它与 一起使用UIPanGestureRecognizer,这样我就可以随着手指的移动不断调整视图的大小。

当我想在动画开始或结束时添加某种弹跳效果时,就会出现问题。不仅仅是阻尼比,还有弹跳效果。最简单的想象方法是 的“滑动删除”功能UITableViewCell,您可以将“删除”按钮拖动到超出其实际宽度,然后它会弹回来。

实际上,我想要实现的是在段fractionComplete之外设置属性的方法[0, 1],因此当分数为 时1.2,偏移量将变为144而不是其最大值 120。

现在 的最大值fractionComplete正是1

以下是一些可视化此问题的示例:

我目前拥有的: 在此输入图像描述

我想要实现的目标: 在此输入图像描述


编辑(1 月 19 日):

抱歉我的回复迟了。以下是一些说明:

我不使用UIView.animate(...),而是UIViewPropertyAnimator出于一个非常具体的原因使用:它为我处理所有的计时、曲线和速度。

例如,您将视图拖动到一半。这意味着剩余部分的持续时间应比总持续时间短两倍。或者,如果您拖动了 99% 的距离,它应该几乎立即完成剩余部分。

作为补充,UIViewPropertyAnimator具有诸如暂停(当用户再次开始拖动时)或反向(当用户开始向左拖动,但之后他改变主意并将手指移到右侧时)等功能,我也受益于。

所有这些对于简单的 UIView 动画来说是不可用的,或者最多需要大量的努力。它只能进行简单的转换,但事实并非如此。

这就是为什么我必须使用某种动画师。

正如我在被发布者删除的答案的评论线程中提到的,对我来说最复杂的部分是模拟摩擦效果:拖动得越远,视图实际移动的越少。就像当您尝试将任何 UIScrollView 拖动到其内容之外时一样。

感谢你们的努力,但我认为这两个答案都不相关。UIDynamicAnimator只要有时间,我就会尝试实现这种行为。可能在最近的一两周内。如果我有任何不错的结果,我将发布我的方法。


编辑(1 月 20 日):

我刚刚将一个演示项目上传到 GitHub,其中包括我项目中的所有转换。所以现在你实际上可以知道为什么我需要使用动画师以及如何使用它们: https: //github.com/demon9733/bouncingview-prototype

您真正感兴趣的唯一文件是MainViewController+Modes.swift. 与过渡和动画相关的所有内容都包含在那里。

我需要做的是使用户能够将手柄区域拖动到“隐藏”按钮宽度之外并具有阻尼效果。向左滑动手柄区域会出现“隐藏”按钮。

PS 我没有真正测试这个演示,所以它可能存在我的主项目中没有的错误。所以你可以安全地忽略它们。

Dha*_*ray 3

您需要允许平移手势到达所需的 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
            })
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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()
        }
    }
}
Run Code Online (Sandbox Code Playgroud)