5

假设我有一个动画师将视图从 移动(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,这样我就可以随着手指的移动不断地调整视图的大小。

当我想在动画开始或结束时添加某种弹跳效果时,问题就来了。不仅仅是阻尼比,还有反弹效果。最简单的想象方法是滑动删除功能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我并没有真正测试这个演示,所以它可能有我的主项目中没有的错误。所以你可以放心地忽略它们。

4

2 回答 2

2

您需要允许平移手势到达所需的 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()
        }
    }
}
于 2020-01-16T11:14:57.303 回答
0

这是可能的方法(简化且有点刺耳-仅反弹,右侧没有按钮,因为它需要更多代码,实际上只是帧管理问题)

由于UIPanGestureRecognizer结束时的长时间延迟,我更喜欢使用UILongPressGestureRecognizer,因为它可以提供更快的反馈。

这是演示结果

在此处输入图像描述

下面使用的情节提要ViewController只有灰色背景矩形容器视图,其他一切都在下面提供的代码中完成。

class ViewController: UIViewController {

    @IBOutlet weak var container: UIView!
    let imageView = UIImageView()

    var initial: CGFloat = .zero
    var dropped = false

    private func excedesLimit() -> Bool {
         // < set here desired bounce limits
        return imageView.frame.minX < -180 || imageView.frame.minX > 80
    }

    @IBAction func pressHandler(_ sender: UILongPressGestureRecognizer) {

        let location = sender.location(in: imageView.superview).x
        if sender.state == .began {
            dropped = false
            initial = location - imageView.center.x
        }
        else if !dropped {
            if (sender.state == .changed) {
                imageView.center = CGPoint(x: location - initial, y: imageView.center.y)
                dropped = excedesLimit()
            }

            if sender.state == .ended || dropped {
                initial = .zero

               // variant with animator
               let animator = UIViewPropertyAnimator(duration: 0.2, curve: .easeOut) {
                  let stickTo: CGFloat = self.imageView.frame.minX < -100 ? -100 : 0 // place for button at right
                  self.imageView.frame = CGRect(origin: CGPoint(x: stickTo, y: self.imageView.frame.origin.y), size: self.imageView.frame.size)
               }
               animator.isInterruptible = true
               animator.startAnimation()

// uncomment below - variant with UIViewAnimation
//                UIView.beginAnimations("bounce", context: nil)
//                UIView.setAnimationDuration(0.2)
//                UIView.setAnimationTransition(.none, for: imageView, cache: true)
//                UIView.setAnimationBeginsFromCurrentState(true)
//
//                let stickTo: CGFloat = imageView.frame.minX < -100 ? -100 : 0 // place for button at right
//                imageView.frame = CGRect(origin: CGPoint(x: stickTo, y: imageView.frame.origin.y), size: imageView.frame.size)

//                UIView.setAnimationDelegate(self)
//                UIView.setAnimationDidStop(#selector(makeBounce))
//                UIView.commitAnimations()
            }
        }
    }

//    @objc func makeBounce() {
//        let bounceAnimation = CABasicAnimation(keyPath: "position.x")
//        bounceAnimation.duration = 0.1
//        bounceAnimation.repeatCount = 0
//        bounceAnimation.autoreverses = true
//        bounceAnimation.fillMode = kCAFillModeBackwards
//        bounceAnimation.isRemovedOnCompletion = true
//        bounceAnimation.isAdditive = false
//        bounceAnimation.timingFunction = CAMediaTimingFunction(name: "easeOut")
//        imageView.layer.add(bounceAnimation, forKey:"bounceAnimation");
//    }

    override func viewDidLoad() {
        super.viewDidLoad()

        imageView.translatesAutoresizingMaskIntoConstraints = false
        imageView.image = UIImage(named: "cat")
        imageView.contentMode = .scaleAspectFill
        imageView.layer.borderColor = UIColor.red.cgColor
        imageView.layer.borderWidth = 1.0
        imageView.clipsToBounds = true
        imageView.isUserInteractionEnabled = true

        container.addSubview(imageView)
        imageView.centerXAnchor.constraint(equalTo: container.centerXAnchor).isActive = true
        imageView.centerYAnchor.constraint(equalTo: container.centerYAnchor).isActive = true
        imageView.widthAnchor.constraint(equalTo: container.widthAnchor, multiplier: 1).isActive = true
        imageView.heightAnchor.constraint(equalTo: container.heightAnchor, multiplier: 1).isActive = true

        let pressGesture = UILongPressGestureRecognizer(target: self, action: #selector(pressHandler(_:)))
        pressGesture.minimumPressDuration = 0
        pressGesture.allowableMovement = .infinity

        imageView.addGestureRecognizer(pressGesture)
    }
}
于 2020-01-18T14:46:42.530 回答