如何用Swift做一个不错的按钮变换动画

如何用Swift做一个不错的按钮变换动画,第1张

我们可以这样手动创建一个短短的直线

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.duration = 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.duration = 0.6strokeEnd.timingFunction = CAMediaTimingFunction(controlPoints: 0.25, -0.4, 0.5, 1)self.middle.addAnimation(strokeEnd, forKey: "strokeEnd")self.middle.strokeEnd = menuStrokeEnd

把前面的工作整合一下

riangleLayer.swift:当OvalLayer层正在抖动的时候出现,当这层旋转的时候,OvalLayer收缩到没有,只让TriangleLayer可视

RectangleLayer.swift:这层作为TriangleLayer可视的容器

ArcLayer.swift:这层填充RectangleLayer通过一个动画,类似于玻璃中充满水的样子


欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/bake/11769527.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-05-18
下一篇 2023-05-18

发表评论

登录后才能评论

评论列表(0条)

保存