最近学习了Ray Wenderlich关于iOS动画的系列教程,学习到了很多关于动画的方法,现在在这里将一点点学习心得记录下,方便以后复习,写的不对的,大家积极指正哈~
在Part4和Part5里,Marin(听起来像这个名字,因为没有字幕所以具体拼写……)教我们实现了这样一种动画实现效果:
主要有四种动画:1.天气背景的切换,主要是旧背景的淡出与新背景的淡入 2.航班号label与登记口label的上下切换,这里给用户一种我们在候机大厅能见到的那种航班公告板切换的感觉 3.出发地点和到达地点的移动 4.模拟飞机起飞到降落这样一种动画效果
整体界面看起来非常炫酷,现在就让我们一起看看这是怎么做到的呢~
一般的图片切换主要是旧视图的淡出与新视图的淡入,在视觉上用户可以看见这样一个过程,而且如果旧视图下层还有别的UI控件,在旧视图透明度慢慢变为0时,下层的控件就显露出来,效果不是很好。而运用辅助视图后,将新视图透明度设置为0,并置于旧视图上层,当过渡时直接将新视图展现出来,这样就一下更新到了新视图。具体的实现如下:
func delay(seconds seconds: Double, completion:()->()) {
let popTime = dispatch_time(DISPATCH_TIME_NOW, Int64( Double(NSEC_PER_SEC) * seconds ))
dispatch_after(popTime, dispatch_get_main_queue()) {
completion()
}
}
func changeFlightDataAnimateTo(data:FlightData){
delay(seconds: 3) { () -> () in
self.changeFlightDataAnimateTo(data.isTakingOff ? parisToRome : londonToParis)
}
}
首先,为了达到航班信息轮播的效果,这里定义了一个delay函数,在一定时间seconds后调用操作completion。
实现动画1:
func fadeImageView(imageView:UIImageView, toImage: UIImage, showEffects:Bool){
let newImageView = UIImageView(image: toImage)
newImageView.frame = view.frame
newImageView.alpha = 0.0
view.insertSubview(newImageView, aboveSubview: imageView)
UIView.animateWithDuration(1.0, delay: 0.0, options: UIViewAnimationOptions.CurveEaseOut, animations: { () -> Void in
newImageView.alpha = 1.0
self.snowView.alpha = showEffects ? 1.0: 0.0
},completion: { _ in
imageView.image = newImageView.image
newImageView.removeFromSuperview()
})
}
这里先获取目前的旧背景imageView,再生成一个与此imageView有着相同frame但图片image不同的newImageView,设置透明,并将其添加到视图的指定位置,也就是背景imageView的上面(addSubview是一层一层往上加,新加的只能放到最上层,而insertSubView可以控制将view添加到指定的层),再调用动画慢慢将newImageView浮现,动画结束后,需要将负责过渡的newImageView的image赋给原本的背景,然后再移除newImageView。newImageView是一个辅助视图,及时将它的image传给背景imageView并移除它,防止视图叠加的越来越多,影响后面切换的效果。
实现动画2:
func cubeTransition(label:UILabel, text:String, direction:AnimationDirection){
let originalFrame = label.frame
let newLabel = UILabel(frame: originalFrame)
newLabel.text = text
newLabel.font = label.font
newLabel.textAlignment = label.textAlignment
newLabel.textColor = label.textColor
newLabel.backgroundColor = UIColor.clearColor()
let newLabelOffset = CGFloat(direction.rawValue) * originalFrame.size.height * 0.5
newLabel.transform = CGAffineTransformConcat(
CGAffineTransformMakeScale(1, 0),
CGAffineTransformMakeTranslation(0, newLabelOffset))
view.addSubview(newLabel)
UIView.animateWithDuration(0.5, delay: 0.0, options: .CurveEaseOut, animations: { () -> Void in
newLabel.transform = CGAffineTransformIdentity
label.transform = CGAffineTransformConcat(
CGAffineTransformMakeScale(1, 0.1),
CGAffineTransformMakeTranslation(0, -newLabelOffset))
}, completion: {_ in
label.text = newLabel.text
label.transform = CGAffineTransformIdentity
newLabel.removeFromSuperview()
})
}
在这里航班信息的切换实现了一种类似于立方体两个面翻转的感觉,先将一个与旧视图同宽当高非常非常小的辅助视图置于旧视图下面(不是视图层次的下面,而是看起来在旧视图下面压扁了),然后新视图慢慢长高,旧视图慢慢被压缩,最后新视图完全将旧视图顶出frame,达到一种翻转切换的感觉。如图:
newLabel与旧label属性完全相同,通过CGAffineTransformMakeScale(1,0)将newLabel的宽不变,高压缩到很小。再用direction的原始值设置newLabelOffset偏移方向(这里的direction是一个枚举类型,其设置了翻转的方向,从上往下翻或者从下往上翻)和偏移距离(旧label的frame高的一半),通过CGAffineTransformMakeTranslation(0,newLabelOffset)将newLabel向上或向下偏移一段距离。CGAffineTransformConcat(,)就是把这两种变化效果联系起来,有个动态的过渡过程。设置好newLabel后,将其添加到图层最上层。
切换动画操作主要是使用CGAffineTransformIdentity()将newLabel的形变重置还原,将其置于旧label的位置,同时,旧label也实现newLabel之前相同的形变,不同的是偏移的方向相反,感觉像是被newLabel顶出去了。
动画结束时,将newLabel的text赋给旧label,并移除,重置旧label的形变,完成翻转。
实现动画3:
func moveLabel(label:UILabel, text:String, offset:CGPoint){
let originalFrame = label.frame
let newLabel = UILabel(frame: originalFrame)
newLabel.text = text
newLabel.font = label.font
newLabel.textAlignment = label.textAlignment
newLabel.textColor = label.textColor
newLabel.backgroundColor = UIColor.clearColor()
newLabel.transform = CGAffineTransformMakeTranslation(offset.x, offset.y)
newLabel.alpha = 0.0
view.addSubview(newLabel)
UIView.animateWithDuration(0.5, delay: 0.0, options: .CurveEaseOut, animations: { () -> Void in
label.transform = CGAffineTransformMakeTranslation(offset.x, offset.y)
label.alpha = 0.0
}, completion: nil)
UIView.animateWithDuration(0.25, delay: 0.25, options: .CurveEaseIn, animations: { () -> Void in
newLabel.transform = CGAffineTransformIdentity
newLabel.alpha = 1.0
}, completion: {(finished: Bool)->Void in
newLabel.removeFromSuperview()
label.text = newLabel.text
label.transform = CGAffineTransformIdentity
label.alpha = 1.0
})
}
航班始发地与目的地的切换主要用到label的偏移,与之前相同,先生成与旧label完全相同但透明度为0、偏移至指定距离的newLabel,并置于View最上层。动画开始时,偏移旧label,在这个动画进行一半的时候,将newLabel显现并归位至旧label的位置,形成一种label偏移后回归原位的感觉。结束时,将newLabel的text赋给旧label,并移除,重置旧label的形变,完成变换。
实现动画4:
func planeDepart(){
let originalCenter = planeImage.center
UIView.animateKeyframesWithDuration(1.5, delay: 0.0, options: UIViewKeyframeAnimationOptions.CalculationModeLinear, animations: { () -> Void in
UIView.addKeyframeWithRelativeStartTime(0.0, relativeDuration: 0.25, animations: { () -> Void in
self.planeImage.center.x += 80.0
self.planeImage.center.y -= 10.0
})
UIView.addKeyframeWithRelativeStartTime(0.25, relativeDuration: 0.25, animations: { () -> Void in
self.planeImage.center.x += 100.0
self.planeImage.center.y -= 50.0
self.planeImage.alpha = 0.0
})
UIView.addKeyframeWithRelativeStartTime(0.1, relativeDuration: 0.4, animations: { () -> Void in
self.planeImage.transform = CGAffineTransformMakeRotation(CGFloat(-M_PI_4/2))
})
UIView.addKeyframeWithRelativeStartTime(0.51, relativeDuration: 0.01, animations: { () -> Void in
self.planeImage.transform = CGAffineTransformIdentity
self.planeImage.center = CGPoint(x: 0, y: originalCenter.y)
})
UIView.addKeyframeWithRelativeStartTime(0.55, relativeDuration: 0.45, animations: { () -> Void in
self.planeImage.alpha = 1.0
self.planeImage.center = originalCenter
})
}, completion: nil)
}
这里主要用到animateKeyframesWithDuration创建帧动画,在动画里通过addKeyframeWithRelativeStartTime添加一段帧动画,frameStartTime指这一段动画相对于整段帧动画时长的开始时间,relativeDuration指相对于整段帧动画时长这一小段的执行时间。通过4次改变planeImage的center,达到一种“飞行”的效果,其中,在“飞”第一段距离的时候,用CGAffineTransformMakeRotation(CGFloat(-M_PI_4/2))将飞机逆时针旋转了22.5度(+顺时针旋转,-逆时针旋转,M_PI_4是π/4度)。之后将旋转形变重置,形成平稳降落的感觉。