接着上次的笔记,后面的课程教大家实现了这样一个登录界面,好看又好玩,交互性很强,balabala……给大家看下⬇️
仔细看下,整个界面总体来说用到了4个动画效果:
1.”Bahama Login”heading、username文本框、password文本框的右滑出现
2.”Log In”button向上弹出,点击后,变宽变色并下滑
3.登录状态提示框翻转出现,状态更换后,右滑消失,在未登录成功”failed”时,左右旋转
4.背景里一直向右飘的朵朵白云(这个你发现了没~)
首先说明一点知识,我们需要在viewWillAppear(也就是控制器马上要显示到屏幕上时)中设定控件的起始位置,然后在viewDidAppear(当控制器已经显示在屏幕上时)中实现动画效果。
1.heading、username、password 的右滑出现,其中heading的动画因为没有延迟,所以无需在viewWillAppear中设定起始位置,通过CABasicAnimation函数设定起始值、终点值、动画时长,CABasicAnimation一次只能更改控件的一个属性,要想更改多个属性,可以用CAAnimationGroups这个方法。
//给heading加CALayer动画
let flyRight = CABasicAnimation(keyPath: "position.x")
flyRight.fromValue = -view.bounds.size.width/2
flyRight.toValue = view.bounds.size.width/2
flyRight.duration = 0.5
heading.layer.addAnimation(flyRight, forKey: nil)
username、password的动画因为有延迟,所以需要在viewWillAppear中设定起始位置:
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
//username,password的layer动画有延迟,所以需要规定起始位置
username.center.x -= view.bounds.width
password.center.x -= view.bounds.width
}
添加动画时,可以直接用刚刚添加给heading的flyRight动画:
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
//给heading加CALayer动画
let flyRight = CABasicAnimation(keyPath: "position.x")
flyRight.fromValue = -view.bounds.size.width/2
flyRight.toValue = view.bounds.size.width/2
flyRight.duration = 0.5
heading.layer.addAnimation(flyRight, forKey: nil)
// flyRight.removedOnCompletion = false
// flyRight.fillMode = kCAFillModeForwards
flyRight.delegate = self
flyRight.setValue("form", forKey: "name")
flyRight.setValue(username.layer, forKey: "layer")
flyRight.beginTime = CACurrentMediaTime() + 0.3
username.layer.addAnimation(flyRight, forKey: nil)
flyRight.setValue(password.layer, forKey: "layer")
flyRight.beginTime = CACurrentMediaTime() + 0.4
password.layer.addAnimation(flyRight, forKey: nil)
}
需要设定动画的beginTime属性值来到达延迟的效果。运行后发现,username和password在动画结束后消失了,需要设定动画的removedOnCompletion为false,也就是动画结束后不在屏幕上移除,并设定fillMode为出现在屏幕前方。
但是这样设置,虽然后面username、password在动画结束后虽然没有消失,但是,不能获取到焦点,文本框不能点击,只是出现在screen上,这是因为Layer动画关注的是控件画出来的过程,不能像UIView一样响应事件。这时就需要设置动画的代理,在结束动画后,将控件的属性更新为动画结束后的位置。
动画的代理方法很简单,只有两个,分别是animationDidStart和animationDidStop。
override func animationDidStop(anim: CAAnimation, finished flag: Bool) {
let nameValue = anim.valueForKey("name") as? String
if let name = nameValue {
if name == "form"{
let layer: CALayer = anim.valueForKey("layer")! as! CALayer
layer.position.x = view.bounds.size.width/2
anim.setValue(nil, forKey: "layer")
}
}
}
动画主要通过键值对的方式存储和取出对应的属性,在移除时可以直接使用Layer的removeAnimationForKey这个方法,十分方便。
2.“Log In”button向上弹出,点击后,变宽变色并下滑,这里需要在viewWillAppear中设定起始位置并将透明度改为0,让它先“消失”:
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
loginButton.center.y += 30.0
loginButton.alpha = 0.0
}
类似于“弹簧”效果的实现:
UIView.animateWithDuration(0.5, delay: 0.5, usingSpringWithDamping: 0.35, initialSpringVelocity: 0, options: .CurveEaseIn, animations: { () -> Void in
self.loginButton.center.y -= 30.0
self.loginButton.alpha = 1.0
}, completion: nil)
其中,usingSpringWithDamping的值表示“弹力“,在0到1之间,越接近0弹力越大; initialSpringVelocity的值表示弹簧初始速度。
点击后,loginButton向下飞,并变宽变色,loginButton上方出现显示登录信息的标签栏status,细心的童鞋可能还会发现loginButton上还有了一个环形进度条spinner开始旋转,代码如下:
UIView.animateWithDuration(1.5, delay: 0.0, usingSpringWithDamping: 0.2, initialSpringVelocity: 20, options: [], animations: { () -> Void in
let b = self.loginButton.bounds
self.loginButton.bounds = CGRect(x: b.origin.x - 20, y: b.origin.y, width: b.size.width + 80, height: b.size.height)
},completion: nil)
UIView.animateWithDuration(0.33, delay: 0.0, options: .CurveEaseOut, animations: { () -> Void in
if self.status.hidden{
self.loginButton.center.y += 60
}
self.spinner.center = CGPoint(x: 40, y: self.loginButton.frame.size.height/2)
self.spinner.alpha = 1.0
self.loginButton.backgroundColor = UIColor(red: 217.0/255.0, green: 211.0/255.0, blue: 114.0/255.0, alpha: 1.0)
}, completion: nil)
3.登录状态提示框翻转出现,状态更换后,右滑消失,在未登录成功”failed”时,左右旋转。在showMessages这个方法显示登录信息标签栏,通过index标记不同信息text。
func showMessages(index index:Int){
let labelOriginalFrame = self.status.frame
UIView.animateWithDuration(0.33, delay: 0.0, usingSpringWithDamping: 1.0, initialSpringVelocity: 0, options: [], animations: { () -> Void in
//oldLabel向右滑动
self.status.center.x += self.view.frame.size.width
}, completion: {(finished: Bool) -> Void in
//初始化newLabel
self.status.hidden = true
self.status.frame = labelOriginalFrame
self.label.text = self.messages[index]
//newLabel翻转出现
UIView.transitionWithView(self.status, duration: 0.33, options: .TransitionFlipFromBottom, animations: { () -> Void in
self.status.hidden = false
}, completion: {(finished: Bool) -> Void in
//如果显示完所以状态,则重置loginButton,让loginButton变短变色;若没有,则延迟调用(这个函数的定义第一篇笔记中提到过)自身显示下一条状态信息
delay(seconds: 1.0, completion: { () -> () in
if index < self.messages.count - 1 {
self.showMessages(index: index + 1)
}else{
//重置loginButton
}
})
})
})
}
这个动画效果的切换主要是旧状态栏向右滑,结束时,新的状态栏翻转出现,这个动画结束时在检查状态栏是否都展示完,没有则调用自身,展示新的状态栏;展示完了则重置loginButton回到之前状态。
在未登录成功”failed”时,状态栏左右旋转,十分好玩,让我们看看是怎么实现的:
let bounce = CAKeyframeAnimation(keyPath: "transform.rotation.z")
bounce.values = [0.0, -M_PI_4/2, 0.0, M_PI_4/2, 0.0]
bounce.keyTimes = [0.0, 0.25, 0.5, 0.75, 1.0]
bounce.additive = true
bounce.repeatCount = 4
status.layer.addAnimation(bounce, forKey: "wobble")
主要用到CAKeyframeAnimation方法更改控件的transform.rotation.z属性,values设定旋转角度,keyTimes设定旋转每个角度的时长,additive为true时,动画属性值会叠加在当前的present层中的keyPath,repeatCount表示动画执行次数。
4.背景里一直向右飘的朵朵白云,方法很简单,但是效果感觉还不错:
func animateCloud(cloud: UIImageView) {
let cloudSpeed = 20.0 / Double(view.frame.size.width)
let duration: NSTimeInterval = Double(view.frame.size.width - cloud.frame.origin.x) * cloudSpeed
UIView.animateWithDuration(duration, delay: 0.0, options: .CurveLinear, animations: { () -> Void in
cloud.frame.origin.x = self.view.bounds.size.width
}, completion: {(finished: Bool) -> Void in
cloud.frame.origin.x = -self.cloud1.frame.size.width
self.animateCloud(cloud)
})
}
比较特别的是,在设定动画时长的时候,用到这样一个公式
这样设定时长,就可以使云朵缓缓向右飘去了……