发布网友 发布时间:2022-04-20 05:08
共1个回答
热心网友 时间:2023-09-03 19:05
我们可以这样手动创建一个短短的直线:
let shortStroke: CGPath = {let path = CGPathCreateMutable()CGPathMoveToPoint(path, nil, 2, 2)CGPathAddLineToPoint(path, nil, 28, 2)return path}()
不过对于中间那个线,因为它有比较复杂的运动轨迹,所以我用 Sketch 创建了一条路径,把动画融入了线条中:
接下来我把图片导出成了 SVG 格式,然后通过 PaintCode 1 把图片转换成代码片段,导入项目中。接下来我重写了导入的代码片段,并且创建了想要的 CGPath 对象:
let outline: CGPath = {let path = CGPathCreateMutable()CGPathMoveToPoint(path, nil, 10, 27)CGPathAddCurveToPoint(path, nil, 12.00, 27.00, 28.02, 27.00, 40, 27)CGPathAddCurveToPoint(path, nil, 55.92, 27.00, 50.47,2.00, 27,2)CGPathAddCurveToPoint(path, nil, 13.16,2.00,2.00, 13.16,2, 27)CGPathAddCurveToPoint(path, nil,2.00, 40.84, 13.16, 52.00, 27, 52)CGPathAddCurveToPoint(path, nil, 40.84, 52.00, 52.00, 40.84, 52, 27)CGPathAddCurveToPoint(path, nil, 52.00, 13.16, 42.39,2.00, 27,2)CGPathAddCurveToPoint(path, nil, 13.16,2.00,2.00, 13.16,2, 27)return path}()
可能有第三方的类库可以让你直接把 SVG 转换成 CGPath,但是对于这种简单的图像,没必要杀鸡用牛刀。
在我自定义的 UIButton 的子类里,我添加了三个 CAShapeLayer 属性并且把他们和前面定义的路径关联了起来:
self.top.path = shortStrokeself.middle.path = outlineself.bottom.path = shortStroke
然后设置一下相关的属性:
layer.fillColor = nillayer.strokeColor = UIColor.whiteColor().CGColorlayer.lineWidth = 4layer.miterLimit = 4layer.lineCap = kCALineCapRoundlayer.masksToBounds = true
为了正确的计算出边框 ( bounds ) ,我需要对这些线条的大小进行计算。谢天谢地,用CGPathCreateCopyByStrokingPath 这个函数可以创建一条和原来线条一样的路径,所以它的 bounds 和参数里那个 CAShapeLayer 类型的内容完全一致:
let boundingPath = CGPathCreateCopyByStrokingPath(layer.path, nil, 4, kCGLineCapRound, kCGLineJoinMiter, 4)layer.bounds = CGPathGetPathBoundingBox(boundingPath)
因为汉堡按钮的上下两个线条接下来会绕着最右点旋转,所以在布局图层的时候,我们需要相应的设置它的锚点 ( anchorPoint ):
self.top.anchorPoint = CGPointMake(28.0 / 30.0, 0.5)self.top.position = CGPointMake(40, 18)self.middle.position = CGPointMake(27, 27)self.middle.strokeStart = hamburgerStrokeStartself.middle.strokeEnd = hamburgerStrokeEndself.bottom.anchorPoint = CGPointMake(28.0 / 30.0, 0.5)self.bottom.position = CGPointMake(40, 36)
接下来就是动画的部分了。当按钮的状态改变的时候,三条直线应该会动态的移动到新的位置。对于那两个外围的直线而言,实现起来很容易。比如最上面的那条线,我先把它往里移动 4 points 让它位于中心位置,然后再逆时针旋转45度,形成了半个 X :
var transform = CATransform3DIdentitytransform = CATransform3DTranslate(transform, -4, 0, 0)transform = CATransform3DRotate(transform, -M_PI_4, 0, 0, 1)let animation = CABasicAnimation(keyPath: "transform")animation.fromValue = NSValue(CATransform3D: CATransform3DIdentity)animation.toValue = NSValue(CATransform3D: transform)animation.timingFunction = CAMediaTimingFunction(controlPoints: 0.25, -0.8, 0.75, 1.85)animation.beginTime = CACurrentMediaTime() + 0.25self.top.addAnimation(animation, forKey: "transform")self.top.transform = transform
最下面的那条线的部分交给读者朋友们作为练习。
接下来,中间的那条直线似乎有点棘手。为了实现理想的效果,我们需要给 CAShapeLayer 的起点和终点分别设置动画效果。
首先我需要知道这两个属性在两种状态下的值分别是多少。注意,就算是在汉堡状态下,直线也没有从 O 开始。路径稍微的从左边延伸,我们可以稍候应用 timing 函数实现一个酷炫的效果:
let menuStrokeStart: CGFloat = 0.325let menuStrokeEnd: CGFloat = 0.9let hamburgerStrokeStart: CGFloat = 0.028let hamburgerStrokeEnd: CGFloat = 0.111
接下来我们只需要创建动画并加到图层里即可:
let strokeStart = CABasicAnimation(keyPath: "strokeStart")strokeStart.fromValue = hamburgerStrokeStartstrokeStart.toValue = menuStrokeStartstrokeStart.ration = 0.5strokeStart.timingFunction = CAMediaTimingFunction(controlPoints: 0.25, -0.4, 0.5, 1)self.middle.addAnimation(strokeStart, forKey: "strokeStart")self.middle.strokeStart = menuStrokeStartlet strokeEnd = CABasicAnimation(keyPath: "strokeEnd")strokeEnd.fromValue = hamburgerStrokeEndstrokeEnd.toValue = menuStrokeEndstrokeEnd.ration = 0.6strokeEnd.timingFunction = CAMediaTimingFunction(controlPoints: 0.25, -0.4, 0.5, 1)self.middle.addAnimation(strokeEnd, forKey: "strokeEnd")self.middle.strokeEnd = menuStrokeEnd
把前面的工作整合一下,你可以看到如下的结果: