专栏首页Charlie's RoadUIKit Dynamics:抛出视图 —《Graphics & Animation系列三》

UIKit Dynamics:抛出视图 —《Graphics & Animation系列三》

翻译自raywenderlich网站iOS教程Graphics & Animation系列

准备开始

首先用storyboard布局一个页面(或者你可以用纯代码去设置),效果如下:

在文件中声明和拖拽出如下参数:(blueSquare、redSquare、imgView是storyboard拖出的)

// x:80 y: 420 width: 8 height: 8 bgColor:blue
@IBOutlet weak var blueSquare: UIView!
// x:156 y: 219 width: 8 height: 8 bgColor:red
@IBOutlet weak var redSquare: UIView!
// x:33 y: 137 width: 254 height: 172
@IBOutlet weak var imgView: UIImageView!

private var originalBounds = CGRect.zero
private var originalCenter = CGPoint.zero

private var animator: UIDynamicAnimator!
private var attachmentBehavior: UIAttachmentBehavior!
private var pushBehavior: UIPushBehavior!
private var itemBehaviod: UIDynamicItemBehavior!

红色和蓝色方块表示让图片做动画的UIKit动态物理引擎点:蓝色方块表示触摸开始的位置,红色方块会在手指移动时跟踪。

现在给view添加一个手势识别器:在DynamicsTossingVC.swift添加如下代码:

@IBAction func handleAttachmentGesture(_ sender: UIPanGestureRecognizer) {
    let location = sender.location(in: self.view)
    let boxLocation = sender.location(in: self.imgView)
    switch sender.state {
    case .began:
        print("Touch start position is \(location)")
        print("Start location in image is \(boxLocation)")
    case .ended:
        print("Touch end position is \(location)")
        print("End location in image is \(boxLocation)")
    default:
        break
    }
}

你可以在storyboard中添加Pan Gesture,也可以用代码创建一个panGesture,并关联这个方法。 现在运行项目,在屏幕上滑动或者拖动,控制台的输出信息应该如下类似:

Touch start position is (234.666656494141, 463.666656494141)
Start location in image is (201.666656494141, 306.666656494141)
Touch end position is (63.0, 482.666656494141)
End location in image is (30.0, 325.666656494141)

现在我们已经设置了基本的界面,下面开始让它动起来。

UIDynamicAnimator和UIAttachmentBehavior

现在我们想要做的第一件事就是让imgView在拖动的时候移动,将要用到一种名为UIAttachmentBehavior的UIKit Dynamics类来执行此操作。

打开DynamicsTossingVC.swift并将以下代码放在viewDidLoad()super.viewDidLoad()下方。

    animator = UIDynamicAnimator(referenceView: view)
    originalBounds = imgView.bounds
    originalCenter = imgView.center

上面的代码建立了一个UIDynamicAnimator,它是UIKit基于物理动画的引擎。 我们用VC的view作为参考视图,该视图定义了动画制作者的坐标系统。

可以将动画添加到动画制作工具中,这样可以执行诸如附加视图,推动视图,使其受重力影响等等。

从UIAttachmentBehavior开始,使图像视图在制作平移手势时跟踪手指。 为此,请将以下代码添加到handleAttachmentGesture(sender :)下面case .began:部分的两个print语句下方:

        // 删除可能存在的任何现有动画行为。
        animator.removeAllBehaviors()
        // 创建一个UIAttachmentBehavior,它将图像视图中的点附加到用户点击一个锚点(碰巧是完全相同的点)。 稍后,更改定位点使图像视图移动。
        // 将锚点附加到视图就像安装一个将锚点连接到视图上的固定附件位置的不可见杆。
        let centerOffset = UIOffset(horizontal: boxLocation.x - imgView.bounds.midX, vertical: boxLocation.y - imgView.bounds.midY)
        attachmentBehavior = UIAttachmentBehavior(item: imgView, offsetFromCenter: centerOffset, attachedToAnchor: location)
        // 更新红色方块以指示定位点,并使用蓝色方块来指示图像视图内所附的点。 当手势开始时,这些将是相同的点。
        redSquare.center = attachmentBehavior.anchorPoint
        blueSquare.center = location
        // 将此行为添加到动画器以使其生效。
        animator.addBehavior(attachmentBehavior)

接下来,需要让锚点跟随手指。 在handleAttachmentGesture_ :)中,用下面的代码替换default下的break语句:

        attachmentBehavior.anchorPoint = sender.location(in: view)
        redSquare.center = attachmentBehavior.anchorPoint

default 下, 这里的代码简单地将锚点和红色方块与手指的当前位置对齐。 当用户的手指移动时,手势识别器调用此方法更新锚点以跟随触摸。 另外,animator 会自动更新视图以跟随定位点。

运行demo,拖动视图会出现如下效果:

注意视图不仅仅是在屏幕上进行旋转; 如果您在图像的某个角落开始手势,则由于锚点的缘故,视图会随着手指移动而旋转。

但是,当完成拖动时,将视图恢复到原始位置会更好。 为了解决这个问题,将这个新方法添加到类中:

fileprivate func resetDemo() {
    animator.removeAllBehaviors()
    UIView.animate(withDuration: 0.5) {
        self.imgView.bounds = self.originalBounds
        self.imgView.center = self.originalCenter
        self.imgView.transform = CGAffineTransform.identity
    }
}

然后在handleAttachmentGesture(_:)中的case .ended下面两个print后面调用:

resetDemo()

运行demo。现在拖动图像后,它应该恢复到原始位置。

UIPushBehavior

接下来,我们需要在停止拖动时分离视图,并为其提供动力,以便在运动中释放视图时可以继续其轨迹。 将使用UIPushBehavior完成此操作。

首先,需要两个常量。 将这些添加到文件的顶部:

let ThrowingThreshold: CGFloat = 1000
let ThrowingVelocityPadding: CGFloat = 35

ThrowingThreshhold表示视图必须移动的速度有多快才能使视图继续移动(而不是立即返回到原始位置)。 ThrowingVelocityPadding是一个常数,它会影响投掷应该多快或多慢(这是通过反复试验来选择的)。

最后,在handleAttachmentGesture(_ :)内部,用下面的代码替换resetDemo()的调用

       animator.removeAllBehaviors()
        // 1
        let velocity = sender.velocity(in: view)
        let magnitude = sqrt(velocity.x * velocity.x + velocity.y * velocity.y)
        
        if magnitude > ThrowingThreshold {
            // 2
            let pushBehavior = UIPushBehavior(items: [imgView], mode: .instantaneous)
            pushBehavior.pushDirection = CGVector(dx: velocity.x / 10, dy: velocity.y / 10)
            pushBehavior.magnitude = magnitude / ThrowingVelocityPadding
            
            self.pushBehavior = pushBehavior
            animator.addBehavior(pushBehavior)
            
            // 3
            let angle = Int(arc4random_uniform(20)) - 10
            
            itemBehavior = UIDynamicItemBehavior(items: [imgView])
            itemBehavior.friction = 0.2
            itemBehavior.allowsRotation = true
            itemBehavior.addAngularVelocity(CGFloat(angle), for: imgView)
            animator.addBehavior(itemBehavior)
            
            // 4
            let timeOffset = Int(0.4 * Double(NSEC_PER_SEC))
            DispatchQueue.main.asyncAfter(deadline: DispatchTime
                .now() + DispatchTimeInterval.seconds(timeOffset)) {
                self.resetDemo()
            }
        } else {
            resetDemo()
        }

对上面的代码一节一节地回顾一下: 1、获取手势的拖动速度。计算速度的大小 - 这是由x方向速度和y方向速度形成的三角 形的斜边。 要理解这个背后的理论,请查看这个Trigonometry for Game Programming教程。

2、假设手势速度超过为动作设置的最小阈值,则设置push行为。 所需的方向由x和y速度组成,并转换为一个给定方向部分的向量。 一旦设置了推送行为,就将其添加到动画序列中。

3、本部分设置了一些旋转以使图像“飞走”。 在这里阅读复杂的计算。 其中一些取决于手指在启动手势时距离手指边缘的距离。 调整这块的value,观察运动如何改变效果。

4、在指定的时间间隔之后,动画通过将图像发送回目的地进行重置,所以它会缩回并返回到屏幕 - 就像球从墙上弹起一样

运行可以看到如下效果:

这里是最终的demo。此demo是raywenderlich下面iOS的Graphics & Animation整个教程系列的集合。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Swift算法俱乐部:Swift栈(Stack)数据结构

    翻译自raywenderlich网站iOS教程Swift Algorithm Club系列

    用户3539187
  • <Solidity学习系列一>根据例子学习Solidity

    声明:本系列文章是自己在http://solidity-cn.readthedoc... 学习solidity时,因为英语水平不够,被迫用谷歌粗略翻译的。仅为了...

    用户3539187
  • 类方法load和initialize的区别

    Objective-C作为一门面向对象语言,有类和对象的概念。编译后,类相关的数据结构会保留在目标文件中,在运行时得到解析和使用。在应用程序运行起来的时候,类的...

    用户3539187
  • ElasticSearch权威指南学习(索引管理)

    "char_filter": { "&_to_and": { "type": "mapping", "ma...

    老梁
  • 23种设计模式之备忘录模式

    定义: 在不破坏封装性的前提下, 捕获一个对象的内部状态, 并在该对象之外保存这个状态. 这样以后就可将该对象回复到原先保存的状态

    烟草的香味
  • loadrunner之java user脚本开发

    * LoadRunner Java script. (Build: _build_number_)

    流柯
  • 黄仁勋从煤气灶下取出最新GPU:7nm全新安培架构,售价20万美元,训练性能顶6张V100

    因为美国疫情的原因,英伟达和其他科技公司一样,把今年的GPU技术大会(GTC 2020)改成线上举行。

    量子位
  • finecms设置伪静态后分享到微信不能访问怎么处理

      finecms设置伪静态后分享到微信不能访问,分享的链接自动增加了一串参数,类似这样的***.html?from=singlemessage&isappin...

    ytkah
  • 面向对象设计的五项基本原则

    面向对象设计(OOD)是面向对象编程(OOP)必不可少的一个环节,只有好的设计,才能保障程序的质量。面向对象设计的主要任务就是类的设计,不少面向对象(OO)的先...

    Dabelv
  • 作为一名高级测试,这些试题你居然都不知道?

    首先,根据用户需求报告中关于功能要求和性能指标的规格说明书,定义相应的测试需求报告,即制订黑盒测试的最高标准,

    测试小兵

扫码关注云+社区

领取腾讯云代金券