重制Skype应用中Action Sheet的动画效果

发布于:2014-07-18 11:51阅读数:

Skype最近发了一个新的iOS版本,整体体验更为流畅,也弹性十足。在新版本中,一个很明显的视觉改变是应用内多个地方增加了弹性的动画效果,比如导航、添加联系人、界面转换、菜单以及

 
Skype最近发了一个新的iOS版本,整体体验更为流畅,也弹性十足。在新版本中,一个很明显的视觉改变是应用内多个地方增加了弹性的动画效果,比如导航、添加联系人、界面转换、菜单以及Action Sheet等等。这篇文章中我们将会复现Skype应用中Action Sheet的动画效果。

 

 

方法
由于没有越狱手机,所以不能查看应用的层级视图。所以我们必须想出自己的动画。第一眼看去,似乎可以把三个“spring(弹簧)”连接到包含 UIBezierPath的视图中,从而实现Skype Action Sheet的动画效果,这也是我们选择的方法:
Springs
我们可以使用 -animateWithDuration:delay:usingSpringWithDamping: initialSpringVelocity:options:animations:completion:来动画这些“弹簧”。我们将使用两个辅助视图(即 sideHelperView  centerHelperView)来观察动画的过程。我们不需要第三个视图,因为旁边的“弹簧”都是一样的,以下是该部分内容中视图控制器的源码:
  1. class ViewController: UIViewController { 
  2.  
  3.     @IBOutlet var sideHelperView: UIView 
  4.     @IBOutlet var centerHelperView: UIView 
  5.  
  6.     // all constraints are between the view's top and the bottom layout guide 
  7.     @IBOutlet var sideHelperTopConstraint: NSLayoutConstraint 
  8.     @IBOutlet var centerHelperTopConstraint: NSLayoutConstraint 
  9.  
  10.     let animationDuration = 0.5 
  11.  
  12.     @IBAction func toggleVisibility(sender: UIButton) { 
  13.         let actionSheetHeight: Float = 240 // will be changed later 
  14.         let hiddenTopMargin: Float = 0 
  15.         let showedTopMargin: Float = -actionSheetHeight 
  16.         let newTopMargin: Float = abs(centerHelperTopConstraint.constant - hiddenTopMargin) < 1 ? showedTopMargin : hiddenTopMargin 
  17.         let options: UIViewAnimationOptions = .BeginFromCurrentState | .AllowUserInteraction 
  18.  
  19.         // Spring Type 1 
  20.         sideHelperTopConstraint.constant = newTopMargin 
  21.         UIView.animateWithDuration(animationDuration, 
  22.             delay: 0, 
  23.             usingSpringWithDamping: 0.75, 
  24.             initialSpringVelocity: 0.8, 
  25.             options: options, 
  26.             animations: { 
  27.                 self.sideHelperView.layoutIfNeeded() 
  28.             }, completion:nil 
  29.         ) 
  30.  
  31.         // Spring Type 2 
  32.         centerHelperTopConstraint.constant = newTopMargin 
  33.         UIView.animateWithDuration(animationDuration, 
  34.             delay: 0, 
  35.             usingSpringWithDamping: 0.9, 
  36.             initialSpringVelocity: 0.9, 
  37.             options: options, 
  38.             animations: { 
  39.                 self.centerHelperView.layoutIfNeeded() 
  40.             }, completion:nil 
  41.         ) 
  42.     } 
操作过程
展示链接
现在说一个不是很明显的地方:我们如何使用辅助视图的位置来驱动 UIBezierPath 绘制。我们需要回调动画的每个 frame 才能知道何时重绘路径。
 
CAAnimation 没有提供这种便利,但可以用以下几种方法添加:
1.我们可以利用这一事实--动画的每一帧都要调用 -drawInContext:  ;可参阅 Core animation progress callback 学习更多内容。
 
2.我们可以使用 CADisplayLink,它是一个特殊类型的timer,描述如下:
 A CADisplayLink object is a timer object that allows your application to synchronize its drawing to the refresh rate of the display.
 
使用 CADisplayLink 的方法看起来很简单,所以这也是我们要继续的,我们将为 ViewController 添加两个属性:
 
  1. var displayLink: CADisplayLink? 
  2. var animationCount = 0 
并创建两个方法来创建和阻止 displayLink ,这样它就不会无限地运行下去:animationWillStart 会在动画之前调用,animationDidComplete 处于动画的完成块中。动画允许用户交互,所以我们需要跟踪运行了多少个动画,并在动画完成后解除 displayLink 的有效性:
  1. func animationWillStart() { 
  2.     if !displayLink { 
  3.         displayLink = CADisplayLink(target: self, selector: "tick:"
  4.         displayLink!.addToRunLoop(NSRunLoop.mainRunLoop(), forMode: NSDefaultRunLoopMode) 
  5.     } 
  6.  
  7.     animationCount++ 
  8.  
  9. func animationDidComplete() { 
  10.     animationCount-- 
  11.     if animationCount == 0 { 
  12.         displayLink!.invalidate() 
  13.         displayLink = nil 
  14.     } 
 
贝塞尔路径
现在我们已准备在storyboard添加了弹性视图,并添加outlet进行连接到它和它的顶部约束中:
  1. @IBOutlet var bouncyView: BouncyView 
  2. @IBOutlet var bouncyViewTopConstraint: NSLayoutConstraint 
下一步,我们看到 -addQuadCurveToPoint:controlPoint: 似乎是绘制所需曲线最简单的方法。文档甚至包含了一个示例,精确展示我们试图达到的目标:
使用 -drawRect: 的绘制会自动附在视图的 bounds上,所以我们需要偏移A和C的位置,为高于这两点的 Control Point 留出空间。不需要知道Control Point的准确位置,所以我们可以进使用位置的三角区域范围(sideToCenterDelta)。最终 BouncyView 的实现就变得非常简便。
  1. class BouncyView: UIView { 
  2.  
  3.     var sideToCenterDelta: Float = 0.0 
  4.     let fillColor = UIColor(red: 0, green: 0.722, blue: 1, alpha: 1) // blue 
  5.  
  6.     override func drawRect(rect: CGRect) { 
  7.         let yOffset: Float = 20.0 
  8.         let width = CGRectGetWidth(rect) 
  9.         let height = CGRectGetHeight(rect) 
  10.  
  11.         let path = UIBezierPath() 
  12.         path.moveToPoint(CGPoint(x: 0.0, y: yOffset)) 
  13.         path.addQuadCurveToPoint(CGPoint(x: width, y: yOffset), 
  14.             controlPoint:CGPoint(x: width / 2.0, y: yOffset + sideToCenterDelta)) 
  15.         path.addLineToPoint(CGPoint(x: width, y: height)) 
  16.         path.addLineToPoint(CGPoint(x: 0.0, y: height)) 
  17.         path.closePath() 
  18.  
  19.         let context = UIGraphicsGetCurrentContext() 
  20.         CGContextAddPath(context, path.CGPath) 
  21.         fillColor.set() 
  22.         CGContextFillPath(context) 
  23.     } 
 
连接各个部分
最终填充了-tick: 方法,它将会驱动 bouncyView 的动画。我们不能直接访问辅助视图的 frames,因为它们没有在动画期间展示当前的值,相反我们需要方位它们呈现层的 frames.下一步,我们将会更新 bouncyViewTopConstraint ,这样 bouncyView 的垂直位置就和 centerHelperView 一样了,并基于辅助视图的位置正确设置 sideToCenterDelta
  1. func tick(displayLink: CADisplayLink) { 
  2.     let sideHelperPresentationLayer = sideHelperView.layer.presentationLayer() as CALayer 
  3.     let centerHelperPresentationLayer = centerHelperView.layer.presentationLayer() as CALayer 
  4.     let newBouncyViewTopConstraint = CGRectGetMinY(sideHelperPresentationLayer.frame) - CGRectGetMaxY(view.frame) 
  5.  
  6.     bouncyViewTopConstraint.constant = newBouncyViewTopConstraint 
  7.     bouncyView.layoutIfNeeded() 
  8.  
  9.     bouncyView.sideToCenterDelta = CGRectGetMinY(sideHelperPresentationLayer.frame) - CGRectGetMinY(centerHelperPresentationLayer.frame) 
  10.     bouncyView.setNeedsDisplay() 
总结
下边是最终的结果,并不完全和Skype应用中的效果一样,不过我认为已经以非标准的方法达到了很不错的效果。你可以在gitHub上查看该项目,并通过改变 spring 的参数来呈现不一样的结果。
 
如果你还对其他类似实现方法感兴趣,可以看看BRFlabbyTable.
 
我写了一个后续的文章,并发布了一个开源库AHKBendableView

 

 
 

CocoaChina是全球最大的苹果开发中文社区,官方微信每日定时推送各种精彩的研发教程资源和工具,介绍app推广营销经验,最新企业招聘和外包信息,以及Cocos2d引擎、Cocos Studio开发工具包的最新动态及培训信息。关注微信可以第一时间了解最新产品和服务动态,微信在手,天下我有!

请搜索微信号“CocoaChina”关注我们!

搜索CocoaChina微信公众号:CocoaChina

顶部