汉堡按钮在界面设计中已经是老生常谈了,但是当我在dribbble看到这个漂亮的过渡动画时,我决定试试用代码实现它。
 
这是 CreativeDash team 的原型图:
你可能已经注意到了,汉堡顶部和底部的线条是来自’X’字符,中间的线条变为外框。我知道这种效果可以用CAShapeLayer创建出来,但是首先我得为三条线分别创建一个CGPath。
 
这种短直线可以手写出来:
  1. let shortStroke: CGPath = {
  2. let path = CGPathCreateMutable()
  3. CGPathMoveToPoint(path, nil, 2, 2)
  4. CGPathAddLineToPoint(path, nil, 28, 2)
  5. return path
  6. }()
 
对于中间的线条,我用Sketch创建了path,从中间开始,然后无缝地过渡到圆形的外框。
 
接下来我导出这个 path 为 SVG 文件,然后将它导入到旧的 PaintCode1 中,它将转换文件为 UIBezierPath 代码片段。然后我按照说明改写了部分代码,创建了一个使用 CGPath 描述的对象:
  1. let outline: CGPath = {
  2. let path = CGPathCreateMutable()
  3. CGPathMoveToPoint(path, nil, 10, 27)
  4. CGPathAddCurveToPoint(path, nil, 12.00, 27.00, 28.02, 27.00, 40, 27)
  5. CGPathAddCurveToPoint(path, nil, 55.92, 27.00, 50.47,  2.00, 27,  2)
  6. CGPathAddCurveToPoint(path, nil, 13.16,  2.00,  2.00, 13.16,  2, 27)
  7. CGPathAddCurveToPoint(path, nil,  2.00, 40.84, 13.16, 52.00, 27, 52)
  8. CGPathAddCurveToPoint(path, nil, 40.84, 52.00, 52.00, 40.84, 52, 27)
  9. CGPathAddCurveToPoint(path, nil, 52.00, 13.16, 42.39,  2.00, 27,  2)
  10. CGPathAddCurveToPoint(path, nil, 13.16,  2.00,  2.00, 13.16,  2, 27)
  11. return path
  12. }()
 
可能有第三方库能够帮助你从 SVG 文件直接加载为 CGPaths,但是这样的 path 我们这里只有这一个,所以用代码写也无所谓啦。
 
在我的 UIButton 子类中,我添加了三个 CAShapeLayer 属性,并设置它们为相应的 path:
  1. self.top.path = shortStroke
  2. self.middle.path = outline
  3. self.bottom.path = shortStroke
 
然后我设置了它们三个的样式:
  1. layer.fillColor = nil
  2. layer.strokeColor = UIColor.whiteColor().CGColor
  3. layer.lineWidth = 4
  4. layer.miterLimit = 4
  5. layer.lineCap = kCALineCapRound
  6. layer.masksToBounds = true
为了正确地计算出它们的边界,我需要将画笔的大小考虑在内。幸运的是,CGPathCreateCopyByStrokingPath 将创建沿用了原先的画笔的轮廓路径,因此它的边框会完全包含 CAShapeLayer 的内容:
  1. let boundingPath = CGPathCreateCopyByStrokingPath(layer.path, nil, 4, kCGLineCapRound, kCGLineJoinMiter, 4)
  2. layer.bounds = CGPathGetPathBoundingBox(boundingPath)
由于外框将在稍后围绕其最右边的点旋转,我只好在 layers 布局时设置相应的 anchorPoint:
  1. self.top.anchorPoint = CGPointMake(28.0 / 30.0, 0.5)
  2. self.top.position = CGPointMake(40, 18)
  3. self.middle.position = CGPointMake(27, 27)
  4. self.middle.strokeStart = hamburgerStrokeStart
  5. self.middle.strokeEnd = hamburgerStrokeEnd
  6. self.bottom.anchorPoint = CGPointMake(28.0 / 30.0, 0.5)
  7. self.bottom.position = CGPointMake(40, 36)
现在,当按钮改变状态时,它应当显示三条线到新位置的动画。接下来的两个外笔划非常简单。对于顶部的线条,我首先将它向内移动4个points,以保持在中心,然后旋转至负45度,形成半个X:
  1. var transform = CATransform3DIdentity
  2. transform = CATransform3DTranslate(transform, -4, 0, 0)
  3. transform = CATransform3DRotate(transform, -M_PI_4, 0, 0, 1)
  4. let animation = CABasicAnimation(keyPath: "transform")
  5. animation.fromValue = NSValue(CATransform3D: CATransform3DIdentity)
  6. animation.toValue = NSValue(CATransform3D: transform)
  7. animation.timingFunction = CAMediaTimingFunction(controlPoints: 0.25, -0.8, 0.75, 1.85)
  8. animation.beginTime = CACurrentMediaTime() + 0.25
  9. self.top.addAnimation(animation, forKey: "transform")
  10. self.top.transform = transform
(底部的线条留给读者作为练习吧。)
 
中部的线条有点麻烦。为了达到预期的效果,我需要单独为 CAShapeLayer 的 strokeStart 和 strokeEnd 属性添加动画。
 
首先,我必须弄清楚在两种状态下的这两个属性正确的值。注意,即使是汉堡包状态下,笔画也不是从0开始。由现有路径延伸略微超过外笔划的左边缘,当应用到 timingFunction 后,我们就完美实现了预期的效果:
  1. let menuStrokeStart: CGFloat = 0.325
  2. let menuStrokeEnd: CGFloat = 0.9
  3. let hamburgerStrokeStart: CGFloat = 0.028
  4. let hamburgerStrokeEnd: CGFloat = 0.111
 
现在我们只需要创建动画,然后添加它们到 layer 中:
  1. let strokeStart = CABasicAnimation(keyPath: "strokeStart")
  2. strokeStart.fromValue = hamburgerStrokeStart
  3. strokeStart.toValue = menuStrokeStart
  4. strokeStart.duration = 0.5
  5. strokeStart.timingFunction = CAMediaTimingFunction(controlPoints: 0.25, -0.4, 0.5, 1)
  6. self.middle.addAnimation(strokeStart, forKey: "strokeStart")
  7. self.middle.strokeStart = menuStrokeStart
  8. let strokeEnd = CABasicAnimation(keyPath: "strokeEnd")
  9. strokeEnd.fromValue = hamburgerStrokeEnd
  10. strokeEnd.toValue = menuStrokeEnd
  11. strokeEnd.duration = 0.6
  12. strokeEnd.timingFunction = CAMediaTimingFunction(controlPoints: 0.25, -0.4, 0.5, 1)
  13. self.middle.addAnimation(strokeEnd, forKey: "strokeEnd")
  14. self.middle.strokeEnd = menuStrokeEnd
 
将所有代码组合在一起,结果如下:
 
我很高兴完成了。你可以在 GitHub 上找到代码,以及follow我的Twitter

用 Swift 制作一个漂亮的汉堡按钮过渡动画的更多相关文章

  1. Swift 制作一个新闻通知中心插件1

    使用 Swift 制作一个新闻通知中心插件(1) 随着 iOS 8 的发布,苹果为开发者们开放了很多新的 API,而在这些开放的接口中 通知中心插件 无疑是最显眼的一个.通知中心就不用过多介绍了,相信 ...

  2. 小强的HTML5移动开发之路(5)——制作一个漂亮的视频播放器

    来自:http://blog.csdn.net/dawanganban/article/details/17679069 在前面几篇文章中介绍了HTML5的特点和需要掌握的基础知识,下面我们开始真正的 ...

  3. 纯Div+Css制作的漂亮点击按钮和关闭按钮

    纯Div+Css制作的漂亮点击按钮和关闭按钮,单击点击按钮也有效果.这些都不是图片.

  4. 使用 Swift 制作一个新闻通知中心插件(1)

    input[type="date"].form-control,.input-group-sm>input[type="date"].input-grou ...

  5. Swift - 制作一个在线流媒体音乐播放器(使用StreamingKit库)

    在之前的文章中,我介绍了如何使用 AVPlayer 制作一个简单的音乐播放器(点击查看1.点击查看2).虽然这个播放器也可以播放网络音频,但其实际上是将音频文件下载到本地后再播放的. 本文演示如何使用 ...

  6. Expression Blend4经验分享:制作一个简单的图片按钮样式

    这次分享如何做一个简单的图片按钮经验 在我的个人Silverlight网页上,有个Iphone手机的效果,其中用到大量的图片按钮 http://raimon.6.gwidc.com/Iphone/de ...

  7. Swift - 制作一个录音机(声音的录制与播放)

    1,技术介绍 (1)AVFoundation.framework框架提供了AVAudioRecorder类.它可以实现录音功能. (2)而使用该框架的AVAudioPlayer类,可以实现声音的播放. ...

  8. css3制作一个漂亮的按钮

    aaarticlea/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFMAAAA4CAIAAAAO41POAAAGWklEQVRogeWabWwTdRzH/8EwMb6Q69

  9. 使用 Swift 制作一个新闻通知中心插件(2)

    我们在第一部分的文章中详细讲解了创建一个通知中心插件的整体过程.我们成功的在通知中心里面显示了新闻列表.但是截止到目前,我们还不能从通知中心的列表中查看新闻的详细内容.在这次的教程中,我们就以上次的教 ...

随机推荐

  1. MyBatis 入门到精通(三) 高级结果映射

    MyBatis的创建基于这样一个思想:数据库并不是您想怎样就怎样的.虽然我们希望所有的数据库遵守第三范式或BCNF(修正的第三范式),但它们不是.如果有一个数据库能够完美映射到所有应用程序,也将是非常 ...

  2. Android UI开发详解之Fragment

    Fragment是Android自从3.0之后新加入的一个组件,我相信很多人都已经听说过这个组件了,但这个组件到底是个什么,如何去使用他呢,且听我讲来. 以下部分资料来自官网(官网才是王道,其他都是浮 ...

  3. 白盒测试之gmock入门篇

    一.gmock是什么 gmock是google公司推出的一款开源的白盒测试工具.gmock是个很强大的东西,测试一个模块的时候,可能涉及到和其他模块交互,可以将模块之间的接口mock起来,模拟交互过程 ...

  4. gcc编译器基本命令

    1 unix操作系统 ubuntu 12版本Unix内核0.5天 常用命令2 c语言:标准c 十天主要练习动手能力 小案例水平高的 自己去写案例水平低的 理解并跟着写3 c++:5天高级语言的特点:面 ...

  5. 未能加载Connector/NET :: v6.7.4

    //从*.config文件获取连接字符串和提供程序 string dp = ConfigurationManager.AppSettings["provider"]; string ...

  6. Annotations:注解

    注解,作为元数据的一种形式,虽不是程序的一部分,却有以下作用: 可以让编译器跳过某些检测 某些工具可以根据注解信息生成文档等 某些注解可以在运行时检查   @表示这是一个注解   @Override ...

  7. Spark编程实现SQL查询的实例

    1.Oracle中的SQL select count(1) from a_V_PWYZL_CUSTACCT_PSMIS t where not exists (select 1 from tb_sho ...

  8. hive-mysql安装配置

    默认情况下,hive的元数据信息存储在内置的Derby数据中.Facebook将hive元数据存储在关系数据库 1.安装好mysql ,sudo apt-get install mysql-serve ...

  9. Deep Learning Practice【开篇】

    Chapter 0 初入深度学习实战 最近一直在学习深度学习相关的知识,看文献,看博客,看书,与别人讨论,等等,但是总觉得这样的学习只是停留在表面,无法去深入的学习到深度学习的内幕.于是,决定开始深度 ...

  10. -bash: ./radar.sh: /bin/sh^M: bad interpreter: 没有那个文件或目录

    运行shell脚本时,报下面这个错误:   -bash: ./radar.sh: /bin/sh^M: bad interpreter: 没有那个文件或目录 错误分析: 因为操作系统是windows, ...