一个很复杂的交互动画实现起来比较困难,在这篇教程中,我会向你展示如何创建一个复杂的百分比行为驱动交互动画
- Xcode 7.2
- Swift 2.0
- 在本教程中,我会假设你已经熟悉CAAnimationGroup
百分比行为驱动动画
让我们先来了解一下 CAMediaTiming
的背景知识, CAMediaTiming
是CAAnimation实现的一个协议,但是这个协议同样也被CALayer,以及所有的Core Animation layers的基类所实现,这意味着,如果你为一个Layer设置一个2倍的速度,这个layer的所有动画都会添加自身,并且以两倍的速度运行,执行动画。控制着这个速度的一个animation或者一个layer也可以用来暂停,只要你设置速度为0即可。与timeOffset一起使用,可以像PanGesture,** UIScrollView, slider**一样利用外部输入一个值来控制动画的执行周期。
Switch animation
我们开始创建一个类,继承UIView,它会包含我们的动画。这个动画效果和我们的UISwitch相似。
- 这个动画包含三部分
- Thumb layer(响应交互层,我的理解)
从左至右,从右至左,都会有动画效果。开/关 - 背景+ stroke layer 这个边框颜色是由灰到绿淡出,填充颜色动画是从白到绿。
- 背景遮罩层 + 做路径动画的白色圆点。
- Thumb layer(响应交互层,我的理解)
我们来初始化每一个layer,然后把它们添加到主视图的Layer上。
override func setupLayers() {
super.setupLayers()
width = self.frame.width
height = self.frame.height
strokeBackgroundLayer.path = backgroundPath(CGRect(x: 0,y: 0,width: width,height: height), radius: 50/2).CGPath
strokeBackgroundLayer.strokeColor = strokeColor.CGColor
strokeBackgroundLayer.fillColor = selectedColor.CGColor
strokeBackgroundLayer.lineWidth = 2
backgroundLayer.path = backgroundPath(CGRect(x: 1,y: 1,width: width-2,height: height-2), radius: 50/2).CGPath
backgroundLayer.fillColor = UIColor.whiteColor().CGColor
thumbLayer.path = UIBezierPath(ovalInRect: CGRect(x: thumbInset/2, y: thumbInset/2, width: height-thumbInset, height: height-thumbInset)).CGPath
thumbLayer.strokeColor = strokeColor.CGColor thumbLayer.fillColor = UIColor.whiteColor().CGColor
thumbLayer.lineWidth = 0.5 thumbLayer.shadowColor = UIColor.blackColor().CGColor
thumbLayer.shadowOpacity = 0.2
thumbLayer.shadowRadius = 1
thumbLayer.shadowOffset = CGSizeMake(0, 1)
layer.addSublayer(strokeBackgroundLayer)
layer.addSublayer(backgroundLayer)
layer.addSublayer(thumbLayer)
}
现在,我们已经准备为每个layer创建动画效果,背景+边框需要2个动画: 边框颜色的变化
和背景颜色填充的变化
,我们可以使用CAAnimationGroup
去添加多个动画到同一Layer(图层).
// MARK: ANIMATION LAYERS
// MARK: STROKE
func strokeBackgroundAnimations() -> CAAnimationGroup {
let groupAnimation = CAAnimationGroup()
groupAnimation.duration = animDuration
groupAnimation.animations = [strokeColorAnimation(), strokeFillColorAnimation()]
groupAnimation.removedOnCompletion = false; return groupAnimation
}
func strokeColorAnimation()-> CABasicAnimation {
let strokeAnim = CABasicAnimation(keyPath: "strokeColor")
strokeAnim.fromValue = strokeColor.CGColor
strokeAnim.toValue = selectedColor.CGColor strokeAnim.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
return strokeAnim
}
func strokeFillColorAnimation()-> CABasicAnimation {
let fillAnim = CABasicAnimation(keyPath: "fillColor")
fillAnim.fromValue = UIColor.whiteColor().CGColor
fillAnim.toValue = selectedColor.CGColor
fillAnim.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
return fillAnim
}
The thumb and background only need one animation but let's keep them in a group animation so it's easier if you want to add to them later on.
// MARK: BACKGROUND
func backgroundAnimations() -> CAAnimationGroup {
let groupAnimation = CAAnimationGroup()
groupAnimation.duration = animDuration
groupAnimation.animations = [backgroundFillAnimation()]
groupAnimation.removedOnCompletion = false;
return groupAnimation
}
func backgroundFillAnimation()-> CAKeyframeAnimation {
let endPath = backgroundPath(CGRect(x: 1, y: 1, width: width-2, height: height-2), radius: 0)
let beginPath = backgroundPath(CGRect(x: width/2, y: height/2, width: 0, height: 0), radius: 0)
let fillAnim = CAKeyframeAnimation(keyPath: "path")
fillAnim.values = [ endPath.CGPath, beginPath.CGPath]
fillAnim.keyTimes = [ NSNumber(float: 0.0), NSNumber(float: 1.0)]
fillAnim.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
return fillAnim
}
func backgroundPath(frame: CGRect, radius: CGFloat) -> UIBezierPath {
//// Rectangle Drawing
let rectanglePath = UIBezierPath(roundedRect: CGRect(x: frame.origin.x, y: frame.origin.y, width: frame.width, height: frame.height), cornerRadius: frame.height/2)
return rectanglePath
}
// MARK: THUMB
func thumbAnimations() -> CAAnimationGroup {
let groupAnimation = CAAnimationGroup()
groupAnimation.duration = animDuration
groupAnimation.animations = [thumbPositionAnimation()]
groupAnimation.removedOnCompletion = false
return groupAnimation
}
func thumbPositionAnimation()-> CABasicAnimation {
let posAnim = CABasicAnimation(keyPath: "position")
posAnim.fromValue = NSValue(CGPoint:CGPointMake(0, 0))
posAnim.toValue = NSValue(CGPoint:CGPointMake( (width - height), 0))
posAnim.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) return posAnim
}
现在我们已经准备好我们所需要的动画了。我们要把这些动画添加到它们各自的图层上。但是当它们不开始的时候,我们也不得不停止这些动画。我们可以创建一个用来盛放做动画的CALayer的数组,以便于这个类尽可能的重用。
var layerWithAnims : [CALayer]!
让我们在setupLayer
方法后面,把所有的图层添加到数组中
self.layerWithAnims = [strokeBackgroundLayer, backgroundLayer, thumbLayer]
下面的代码会清除每一个图层的动画效果,然后再依次为它们添加动画.
//override and add your layers with animation
func startAllAnimations(){
for layer in self.layerWithAnims{
layer.speed = 0
} //ADD YOUR ANIMATIONS TO THE LAYER
strokeBackgroundLayer.addAnimation(strokeBackgroundAnimations(), forKey: "strokeBackgroundAnimations")
backgroundLayer.addAnimation(backgroundAnimations(), forKey: "backgroundAnimations")
thumbLayer.addAnimation(thumbAnimations(), forKey: "thumbAnimations")
}
交互动画
我们可以使用 timeOffset属性来控制这些动画.让我们来创建一个记录进度的属性,然后我们来设置动画的进度.如果这些图层上没有添加动画,我们可以调用startAllAnimation()
来给这些Layer(图层) 添加动画.但是要确保这些图层被添加动画之前是没有动画的.
如果动画已经添加完成,我们可以设置每个layer(图层)的timeoffset作为当前动画的进度.
那么动画所需要的时间就等于: 进度*当前的timeOffset.
let offset = progress * CGFloat(animDuration)
var progress: CGFloat = 0 {
didSet {
if(!self.animationAdded) {
startAllAnimations()
self.animationAdded = true
for layer in self.layerWithAnims {
layer.speed = 0 layer.timeOffset = 0
}
} else{
let offset = progress * CGFloat(animDuration)
for layer in self.layerWithAnims {
layer.timeOffset = CFTimeInterval(offset)
}
}
}
}
现在,你可以通过设置进度来控制动画了.例如 你可以使用panGesture或Slider来获取用户输入和设置动画的进度。
他已经写了一个自定义UISwitch的行为驱动动画的lib.这里有一个具有更加复杂动画效果的例子。 github地址: WACustomSwitch,灵感来源于DayNight switch
不想看我的可以直接查看英文原版,原文链接: http://iostuts.io/2015/10/15/how-to-make-amazing-custom-switch/
PS: 无聊中,翻译一篇英文博客,也是这个demo挺棒的,激发了我的兴趣,锻炼一下英文阅读能力.顺便也可以增长一下见识.翻译的不好,欢迎吐槽,哈哈。谢谢.