什么是最速降线
引用维基百科上的解释:
在重力作用且忽略摩擦力的情况下,一个质点在一点 A 以速率为零开始,沿某条曲线,娶到一点不高于 A 的 B 点,怎样的曲线能令所需的时间最短?这就是最速降线问题,又称最短时间问题、最速落径问题。
从直觉上来看,我们会认为从 A 点到 B 点最快的方式就是一条直线,但其实这是一个错觉,从 A 点到 B 点的最短路径的确是直线,但是如果是在重力作用下采用时间来衡量的话,就不是这条直线最快了,而是某个曲线。
我们下面要讲的就是通过在 A 点和 B 点之间画三条不同的路径,然后看哪个小球最先到达 B 点。我们测试的路径是一条直线和两条曲线。
UIDynamic
UIDynamic 是从 iOS 7 开始引入的一种新技术。看到以UI开始,我们就知道它是隶属于UIKit框架了。UIDynamic 是一种物理引擎,能够模拟现实生活中的物理现象:重力、弹性、摩擦以及碰撞等。
UIDynamic 由三部分组成:
- UIDynamicAnimator
- UIDynamicBehavior
- UIDynamicItem
UIDynamicAnimator
A dynamic animator provides physics-related capabilities and animations for its dynamic items, and provides the context for those animations. It does this by intermediating between the underlying iOS physics engine and dynamic items, via behavior objects you add to the animator.
上面是官方文档中对UIDynamicAnimator的定义。意思是 dynamic animator 是为 dynamic items 提供物理相关的能力和动画以及动画相关的上下文,它是通过添加到 animator 里的 behavior objects 在 iOS 的底层物理引擎和 dynamic items 之间做媒介的。
UIDynamicBehavior
A dynamic behavior confers a behavioral configuration on one or more dynamic items for their participation in two-dimensional animation.
仿真行为是为了让动力学元素能够参与二维动画用来设置行为参数用的。
目前支持的行为有:
- UIAttachmentBehavior
- UICollisionBehavior
- UIGravityBehavior
- UIDynamicItemBehaviro
- UIPushBehavior
- UISnapBehavior
UIDynamicItem
动力学元素是任何遵循了UIDynamicItem协议的 iOS 或者自定义的对象。UIView和UICollectionViewLayoutAttributes从 iOS 7.0 开始就默认实现了这个协议。
介绍完了 UIDynamic 的一些相关的概念,我们就开始来实践吧。
用到的类
- UIDynamicAnimator
- UIGravityBehavior
- UICollisionBehavior
UIDynamicAnimator
这个不用多说了,只要使用 UIDynamic 特性就必然用到。
UIGravityBehavior
将元素添加到重力行为里之后,元素就会收到重力的影响往下掉,就跟苹果往地上掉是一个道理。如果没有设置碰撞检测,那么元素会一直往下掉。
UICollisionBehavior
当元素与元素或者元素与边界之间发生接触的时候会产生我们平时生活中看到的反弹或者其他碰撞行为(运动的物体变静止等等)。当我们给元素添加重力行为之后,元素会一直往下掉,如果有了碰撞行为,元素遇到边界就会停下来。
如何实现
我们这里需要实现的三种不同路径下的小球降落问题,原理上都是相同的,不同的地方在于路径不一样而已。
首先我们需要有一个小球,这个小球是一个UIImageView对象。UIView默认实现了UIDynamicItem协议,所以我们的这个UIImageView就是一个UIDynamicItem对象了。
UIView是一个矩形,为了让我们的小球滚动的时候更逼真,我们需要重写collisionBoundsType属性,返回一个ellipse的UIDynamicItemCollisionBoundsType,UIDynamicItemCollisionBoundsType是 iOS 9才有的,所以我们的项目只能支持 iOS 9以上的设备。
有了小球之后,我们就要将小球添加到各种行为里面去了。首先是UIGravityBehavior,只要将小球通过addItem方法添加到gravity对象里去就行。
self.gravity.addItem(ball)
有了动力行为之后,就要添加碰撞检测了。在我们的示例中,有两个边界需要进行碰撞检测,一个就是小球下降时的曲线,另一个就是右侧使小球停下的竖直边界。
let startPoint = CGPoint(x:xOffset, y:yOffset)
let endPoint = CGPoint(x:width + xOffset, y:yOffset + pathHeight)
collision.addBoundary(withIdentifier: self.pathIdentifierAt(index: index) as NSCopying, for: self.pathAtIndex(index:index, startPoint: startPoint, endPoint: endPoint))
collision.addBoundary(withIdentifier: self.lineIdentifierAt(index: index) as NSCopying, from: CGPoint(x:endPoint.x,y:startPoint.y), to: endPoint)
pathAtIndex方法返回一个小球下降的路径曲线,我们使用UIBezierPath来生成曲线,然后将 path 添加到UIShapeLayer,最后将 layer 添加到 view中。
let bezierPath = UIBezierPath()
bezierPath.move(to: startPoint)
bezierPath.addCurve(to: endPoint, controlPoint1: CGPoint(x:xOffset + 5.0, y:endPoint.y - 5), controlPoint2: CGPoint(x:xOffset + 15.0, y:endPoint.y))
bezierPath.addCurve(to: startPoint, controlPoint1: CGPoint(x:xOffset + 15.0, y:endPoint.y), controlPoint2: CGPoint(x:xOffset + 5.0, y:endPoint.y - 5))
addShapeLayerWithPath(path: bezierPath)
return bezierPath
可以调整中间两个点的坐标来改变曲线的曲度,上面的代码中只贴出了其中一条曲线的算饭,另外一条请查看代码。
边界都有了之后,值要将小球加入到碰撞行为中即可。
我们重载了UICollisionBehavior的beginContactFor方法,在这里我们将检测小球是否与竖直的边界发生了碰撞,我们利用identifier来识别是与哪个边界发生了接触。一旦小球与右侧边界发生借出,我们就将元素从所有行为中移除。当行为总不存在任何元素时,我们将行为从UIDynamicAnimator中也移除。
let ball = item as! Ball
let index = balls.index(of: ball)
if identifier as! String == self.lineIdentifierAt(index: index!) as! String {
self.gravity.removeItem(item)
self.collision.removeItem(ball)
if self.gravity.items.count == 0 {
self.animator?.removeBehavior(gravity)
self.animator?.removeBehavior(collision)
}
}
当点击“再来一次”的时候,我们元素添加到行为当中,再将行为添加到UIDynamicAnimator就可以再演示一次。
从演示中我们可以看到,小球停下来所需要的时间是不一样的,并且直线的并不是用时最短的一个。
Flower's Home
A Remember of life and study RSSCategories
Recent Posts
- 使用 UIDynamic 实现物理上的最速降线演示
- 侧边菜单栏动画效果实现
- 对象类型的属性使用assign关键字会发生什么?
- 属性相关的一些关键字详解
- Build universal library in iOS
Copyright © 2015 Theme used GitHub CSS.
