简介
因为我想继续写一些博文并借此学习更多的swift知识,所以我决定去实现一些在Dribble里关于iOS评价高的UIX交互。
让我们先来看看我是如何来实现‘Floating burger 2.0 by Eddie Lobanovskiy’效果的。
实现方法
CAShapeLayer能用来渲染CGPath,虽然CAShapeLayer有许多的属性,但是为了实现这个简单的交互,我们只需要设置2个属性:
- strokeStart和strokeEnd,它们能用来控制实际绘制的路径长度
如果我们让线条更好插入屏幕中,我们把所有的路径看成单一的路径
这意味着,对于我们动画的两种状态我们能使用strokeStart和strokeEnd来分别渲染特定的部分。
当我们创建路径的时候,我们需要小心处理。
线条路径
通过使用这两个属性,我们能够完全控制形状的展示效果,它能更好的手动地组成我们的CGPath,而不是用类似于addArcWithCenter。
好在我们的路径只是一条直线和一个圆:
let path = UIBezierPath()
let radius: CGFloat = 20
let inset: CGFloat = 30
let lineLength = viewportWidth() - inset
let lineStart = (viewportWidth() - (lineLength - radius)) / 2
path.moveToPoint(CGPoint(x: lineStart, y: 0))
path.addLineToPoint(CGPoint(x: lineLength, y: 0))
let circleCenter = CGPoint(x:lineLength, y: -radius)
var nextPoint = CGPointZero
let _ = (0..<360).map {
nextPoint = CGPoint(x: CGFloat(sinf(toRadian($0))) * radius + circleCenter.x, y: CGFloat(cosf(toRadian($0))) * radius + circleCenter.y)
path.addLineToPoint(nextPoint)
}
首先我们创建一个水平线条,然后利用基本数学运算的sin/cos来组成一个圆。
因为strokeEnd和strokeStart是使用标准化坐标(0-1),所以我们需要标准化我们线条的长度。
let circleLength = 2.0 * CGFloat(M_PI) * radius
let totalLength = circleLength + lineLength - lineStart
let lineLengthNormalized = (lineLength - lineStart) / totalLength
从而得到该值
lineLengthNormalized = 0.67833278554572718
动画
通过如下配置
shapeLayer.strokeStart = 0
shapeLayer.strokeEnd = lineLengthNormalized
我们得到我们的第一个动画状态。
通过如下配置
shapeLayer.strokeStart = lineLengthNormalized
shapeLayer.strokeEnd = 1
我们得到折叠收起状态
下面只剩下一个任务,就是让这两种状态动起来。
你需要CoreAnimation里的CABasicAnimation,strokeStart和strokeEnd都用相同的动画类型来表达。
给出如下函数原型:
func animate(shape: CAShapeLayer, duration: CFTimeInterval, stroke: (start: CGFloat, end: CGFloat), headerAlpha: CGFloat)
对每个属性的动画如下:
//! 1
let strokeEndAnimation = CABasicAnimation(keyPath: "strokeEnd")
strokeEndAnimation.fromValue = shape.strokeEnd
strokeEndAnimation.toValue = stroke.end
strokeEndAnimation.duration = duration
strokeEndAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
strokeEndAnimation.fillMode = kCAFillModeBoth
//! 2
shape.strokeEnd = stroke.end
shape.addAnimation(strokeEndAnimation, forKey: strokeEndAnimation.keyPath
但在此重要的一点就是如何确定我们的模型和显示的匹配:CoreAnimation有2层,model和presentationLayer。
当你执行CAAnimation你只能修改显示部分。
当动画结束后,你会发现你的对象有变回之前旧的配置。
为了保持model和presentation(显示)同步:
设置fromValue到当前layer上
运行动画之前,设置layer值到我们的目标toValue
再滚动时,触发两种状态的代码如下:
func scrollViewDidScroll(scrollView: UIScrollView) {
let deadZone = (start: CGFloat(10), end: CGFloat(50))
if (scrollView.contentOffset.y < deadZone.start && isCollapsed) {
animateToOpen(headerShapeLayer, duration: 0.25)
} else
if (scrollView.contentOffset.y > deadZone.end && !isCollapsed) {
animateToCollapsed(headerShapeLayer, duration: 0.25)
}
}
总结
UIKit和CoreGraphics都非常有用,它能让我们写很少的代码就能实现非常好的特效。
所以值得我们投入更多的时间来学习CoreGraphics。确保先阅读更多关于CALayer组件的强大。
参考: