这是我参与11月更文挑战的第13天,活动详情查看:2021最后一次更文挑战
在我之前的文章 —— 系统学习iOS动画—— BahamaAirLogin(UIKit动画) 中,用的是UIView.animate来进行alpha的变化实现添加透明度变化动画。layer 动画的工作方式很像UIView 动画;只需在定义的时间段内,在起始值和结束值之间设置属性的动画,并让Core Animation处理其间的渲染。然而,layer动画比UIView动画有更多的可动画属性;在设计效果时,这给了开发者很多选择和灵活性。许多专门的CALayer子类添加了可以在动画中使用的其他属性。这篇文章就将把上篇文章的一些UIView动画改成Layer动画。
首先修改的是标题,usernameTextField 以及passwordTextField的进场效果。 这里创建一个CABasicAnimation,然后设置其fromValue,toValue以及间隔,然后设置不同的开始时间。这里记得把viewDidAppear里面对这三个视图的center.x 的处理去掉。
let flyRight = CABasicAnimation(keyPath: "position.x")
flyRight.fromValue = -view.bounds.size.width/2
flyRight.toValue = view.bounds.size.width/2
flyRight.duration = 0.5
titleLabel.layer.add(flyRight, forKey: nil)
flyRight.beginTime = CACurrentMediaTime() + 0.3
flyRight.fillMode = .both
usernameTextField.layer.add(flyRight, forKey: nil)
flyRight.beginTime = CACurrentMediaTime() + 0.4
passwordTextField.layer.add(flyRight, forKey: nil)
接着处理cloud的透明度变化的动画,把在viewWillAppear处理could alpha的代码去掉,然后去掉viewDidAppear对cloud透明度的动画,之后添加代码:
let fadeIn = CABasicAnimation(keyPath: "opacity")
fadeIn.fromValue = 0.0
fadeIn.toValue = 1.0
fadeIn.duration = 0.5
fadeIn.fillMode = .backwards
fadeIn.beginTime = CACurrentMediaTime() + 0.5
cloud1.layer.add(fadeIn, forKey: nil)
fadeIn.beginTime = CACurrentMediaTime() + 0.7
cloud2.layer.add(fadeIn, forKey: nil)
fadeIn.beginTime = CACurrentMediaTime() + 0.9
cloud3.layer.add(fadeIn, forKey: nil)
fadeIn.beginTime = CACurrentMediaTime() + 1.1
cloud4.layer.add(fadeIn, forKey: nil)
封装tintBackgroundColor和roundCorners来添加背景色和圆角的动画。
func tintBackgroundColor(layer: CALayer, toColor: UIColor) {
let tint = CABasicAnimation(keyPath: "backgroundColor")
tint.fromValue = layer.backgroundColor
tint.toValue = toColor.cgColor
tint.duration = 0.5
layer.add(tint, forKey: nil)
layer.backgroundColor = toColor.cgColor
}
func roundCorners(layer: CALayer, toRadius: CGFloat) {
let round = CABasicAnimation(keyPath: "cornerRadius")
round.fromValue = layer.cornerRadius
round.toValue = toRadius
round.duration = 0.5
layer.add(round, forKey: nil)
layer.cornerRadius = toRadius
}
并且在handleLogin中添加
let tintColor = UIColor(red: 0.85, green: 0.83, blue: 0.45, alpha: 1.0)
tintBackgroundColor(layer: loginButton.layer, toColor: tintColor)
roundCorners(layer: loginButton.layer, toRadius: 25.0)
以及在resetForm中添加
let tintColor = UIColor(red: 0.63, green: 0.84, blue: 0.35, alpha: 1.0)
tintBackgroundColor(layer: self.loginButton.layer, toColor: tintColor)
roundCorners(layer: self.loginButton.layer, toRadius: 10.0)
CABasicAnimation各参数意义:
- fromValue: 动画的开始值(Any类型, 根据动画不同可以是CGPoint、NSNumber等)
- toValue: 动画的结束值, 和fromValue类似
- beginTime: 动画的开始时间
- duration : 动画的持续时间
- repeatCount : 动画的重复次数
- fillMode: 用于表示动画在开始和结束时的状态
- kCAFillModeRemoved: 这个是fillMode的默认值,表示在到达beginTime时才显示动画的第一帧,动画结束时,删除CALayer做的变化
- kCAFillModeBackwards: 无论是否到达beginTime,动画开始后,立刻显示动画的第一帧
- kCAFillModeForwards: 在到达beginTime时显示动画的第一帧,在动画结束时,保持动画最后一帧的状态,直到动画被删除。
- kCAFillModeBoth: 相当于kCAFillModeBackwards与kCAFillModeForwards的共同合集,即无论是否到达beginTime,动画开始后,立刻显示动画的第一帧,并且在动画结束时保持最后一帧的状态,直到动画被删除。
- isRemovedOnCompletion: 完成后是否删除动画
- autoreverses: 执行的动画按照原动画返回执行
- path:关键帧动画中的执行路径
- values: 关键帧动画中的关键点数组
- animations: 组动画中的动画数组
- delegate : 动画代理, 封装了动画的执行和结束方法
- timingFunction: 控制动画的显示节奏, 系统提供五种值选择,分别是:
- 1.kCAMediaTimingFunctionDefault( 默认,中间快)
- 2.kCAMediaTimingFunctionLinear (线性动画)
- 3.kCAMediaTimingFunctionEaseIn (先慢后快 慢进快出)
- 4.kCAMediaTimingFunctionEaseOut (先块后慢快进慢出)
- 5.kCAMediaTimingFunctionEaseInEaseOut (先慢后快再慢)
- type: 过渡动画的动画类型,系统提供了多种过渡动画, 分别是:
- 1: fade (淡出 默认)
- 2: moveIn (覆盖原图)
- 3: push (推出)
- 4: fade (淡出 默认)
- 5: reveal (底部显示出来)
- 6: cube (立方旋转)
- 7: suck (吸走)
- 8: oglFlip (水平翻转 沿y轴)
- 9: ripple (滴水效果)
- 10: curl (卷曲翻页 向上翻页)
- 11: unCurl (卷曲翻页返回 向下翻页)
- 12: caOpen (相机开启)
- 13: caClose (相机关闭)
- subtype : 过渡动画的动画方向, 系统提供了四种,分别是:
- 1.fromLeft( 从左侧)
- 2.fromRight (从右侧)
- 3.fromTop (有上面)
- 4.fromBottom (从下面)
完整代码:
import UIKit
class ViewController: UIViewController {
let screenWidth = UIScreen.main.bounds.size.width
let screenHeight = UIScreen.main.bounds.size.height
let titleLabel = UILabel()
let backgroundImage = UIImageView()
let usernameTextField = TextField()
let passwordTextField = TextField()
let loginButton = UIButton()
let cloud1 = UIImageView()
let cloud2 = UIImageView()
let cloud3 = UIImageView()
let cloud4 = UIImageView()
let spinner = UIActivityIndicatorView(style: .whiteLarge)
let status = UIImageView(image: UIImage(named: "banner"))
let label = UILabel()
let messages = ["Connecting ...", "Authorizing ...", "Sending credentials ...", "Failed"]
var statusPosition = CGPoint.zero
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
view.addSubview(backgroundImage)
view.addSubview(titleLabel)
view.addSubview(usernameTextField)
view.addSubview(passwordTextField)
view.addSubview(loginButton)
view.addSubview(cloud1)
view.addSubview(cloud2)
view.addSubview(cloud3)
view.addSubview(cloud4)
loginButton.addSubview(spinner)
let textFieldWidth = screenWidth - 60
let buttonWidth = 260
backgroundImage.image = UIImage(named: "bg-sunny")
backgroundImage.frame = CGRect(x: 0, y: 0, width: screenWidth, height: screenHeight)
titleLabel.text = "Bahama Login"
titleLabel.textColor = .white
titleLabel.font = UIFont.systemFont(ofSize: 28)
let titleWidth = titleLabel.intrinsicContentSize.width
titleLabel.frame = CGRect(x: (screenWidth - titleWidth) / 2 , y: 120, width: titleWidth, height: titleLabel.intrinsicContentSize.height)
usernameTextField.backgroundColor = .white
usernameTextField.layer.cornerRadius = 5
usernameTextField.placeholder = " Username"
usernameTextField.frame = CGRect(x: 30, y: 202, width: textFieldWidth, height: 40)
passwordTextField.backgroundColor = .white
passwordTextField.layer.cornerRadius = 5
passwordTextField.placeholder = " Password"
passwordTextField.frame = CGRect(x: 30, y: 263, width: textFieldWidth, height: 40)
loginButton.frame = CGRect(x: (Int(screenWidth) - buttonWidth) / 2, y: 343, width: buttonWidth, height: 50)
loginButton.setTitle("Login", for: .normal)
loginButton.setTitleColor(.red, for: .normal)
loginButton.layer.cornerRadius = 5
loginButton.backgroundColor = UIColor(red: 0.63, green: 0.84, blue: 0.35, alpha: 1.0)
loginButton.addTarget(self, action: #selector(handleLogin), for: .touchUpInside)
spinner.frame = CGRect(x: -20.0, y: 6.0, width: 20.0, height: 20.0)
spinner.startAnimating()
spinner.alpha = 0.0
cloud1.frame = CGRect(x: -120, y: 79, width: 160, height: 50)
cloud1.image = UIImage(named: "bg-sunny-cloud-1")
cloud2.frame = CGRect(x: 256, y: 213, width: 160, height: 50)
cloud2.image = UIImage(named: "bg-sunny-cloud-2")
cloud3.frame = CGRect(x: 284, y: 503, width: 74, height: 35)
cloud3.image = UIImage(named: "bg-sunny-cloud-3")
cloud4.frame = CGRect(x:22 , y: 545, width: 115, height: 50)
cloud4.image = UIImage(named: "bg-sunny-cloud-4")
status.isHidden = true
status.center = loginButton.center
view.addSubview(status)
label.frame = CGRect(x: 0.0, y: 0.0, width: status.frame.size.width, height: status.frame.size.height)
label.font = UIFont(name: "HelveticaNeue", size: 18.0)
label.textColor = UIColor(red: 0.89, green: 0.38, blue: 0.0, alpha: 1.0)
label.textAlignment = .center
status.addSubview(label)
statusPosition = status.center
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let flyRight = CABasicAnimation(keyPath: "position.x")
flyRight.fromValue = -view.bounds.size.width/2
flyRight.toValue = view.bounds.size.width/2
flyRight.duration = 0.5
titleLabel.layer.add(flyRight, forKey: nil)
flyRight.beginTime = CACurrentMediaTime() + 0.3
flyRight.fillMode = .both
usernameTextField.layer.add(flyRight, forKey: nil)
flyRight.beginTime = CACurrentMediaTime() + 0.4
passwordTextField.layer.add(flyRight, forKey: nil)
//
// cloud1.alpha = 0.0
// cloud2.alpha = 0.0
// cloud3.alpha = 0.0
// cloud4.alpha = 0.0
loginButton.center.y += 30.0
loginButton.alpha = 0.0
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// UIView.animate(withDuration: 0.5) {
// self.titleLabel.center.x += self.view.bounds.width
// }
//
// UIView.animate(withDuration: 0.5, delay: 0.3, options: [], animations: {
// self.usernameTextField.center.x += self.view.bounds.width
// }, completion: nil
// )
//
// UIView.animate(withDuration: 0.5, delay: 0.5, options: [], animations: {
// self.passwordTextField.center.x += self.view.bounds.width
// }, completion: nil)
// UIView.animate(withDuration: 0.5, delay: 0.5, options: [], animations: {
// self.cloud1.alpha = 1.0
//
// }, completion: nil)
// UIView.animate(withDuration: 0.5, delay: 0.7, options: [], animations: {
// self.cloud2.alpha = 1.0
//
// }, completion: nil)
// UIView.animate(withDuration: 0.5, delay: 0.9, options: [], animations: {
// self.cloud3.alpha = 1.0
//
// }, completion: nil)
// UIView.animate(withDuration: 0.5, delay: 1.1, options: [], animations: {
// self.cloud4.alpha = 1.0
//
// }, completion: nil)
let fadeIn = CABasicAnimation(keyPath: "opacity")
fadeIn.fromValue = 0.0
fadeIn.toValue = 1.0
fadeIn.duration = 0.5
fadeIn.fillMode = .backwards
fadeIn.beginTime = CACurrentMediaTime() + 0.5
cloud1.layer.add(fadeIn, forKey: nil)
fadeIn.beginTime = CACurrentMediaTime() + 0.7
cloud2.layer.add(fadeIn, forKey: nil)
fadeIn.beginTime = CACurrentMediaTime() + 0.9
cloud3.layer.add(fadeIn, forKey: nil)
fadeIn.beginTime = CACurrentMediaTime() + 1.1
cloud4.layer.add(fadeIn, forKey: nil)
UIView.animate(withDuration: 0.5, delay: 0.5, usingSpringWithDamping: 0.5,
initialSpringVelocity: 0.0,
animations: {
self.loginButton.center.y -= 30.0
self.loginButton.alpha = 1.0
},
completion: nil
)
animateCloud(cloud1)
animateCloud(cloud2)
animateCloud(cloud3)
animateCloud(cloud4)
}
@objc func handleLogin() {
view.endEditing(true)
UIView.animate(withDuration: 1.5, delay: 0.0, usingSpringWithDamping: 0.2, initialSpringVelocity: 0.0, options: [], animations: {
self.loginButton.bounds.size.width += 80.0
}, completion: nil)
UIView.animate(withDuration: 0.33, delay: 0.0, usingSpringWithDamping: 0.7, initialSpringVelocity: 0.0, options: [], animations: {
self.loginButton.center.y += 60.0
self.spinner.center = CGPoint(
x: 40.0,
y: self.loginButton.frame.size.height/2
)
self.spinner.alpha = 1.0
}, completion: { _ in
self.showMessage(index:0)
})
let tintColor = UIColor(red: 0.85, green: 0.83, blue: 0.45, alpha: 1.0)
tintBackgroundColor(layer: loginButton.layer, toColor: tintColor)
roundCorners(layer: loginButton.layer, toRadius: 25.0)
}
func showMessage(index: Int) {
label.text = messages[index]
UIView.transition(with: status, duration: 0.33, options: [.curveEaseOut, .transitionFlipFromTop], animations: {
self.status.isHidden = false
}, completion: { _ in
//transition completion
delay(2.0) {
if index < self.messages.count-1 {
self.removeMessage(index: index)
} else {
self.resetForm()
}
}
})
}
func removeMessage(index: Int) {
UIView.animate(withDuration: 0.33, delay: 0.0, options: [], animations: {
self.status.center.x += self.view.frame.size.width
}, completion: { _ in
self.status.isHidden = true
self.status.center = self.statusPosition
self.showMessage(index: index+1)
})
}
func resetForm() {
UIView.transition(with: status, duration: 0.2, options: .transitionFlipFromTop, animations: {
self.status.isHidden = true
self.status.center = self.statusPosition
}, completion: { _ in
let tintColor = UIColor(red: 0.63, green: 0.84, blue: 0.35, alpha: 1.0)
tintBackgroundColor(layer: self.loginButton.layer, toColor: tintColor)
roundCorners(layer: self.loginButton.layer, toRadius: 10.0)
})
UIView.animate(withDuration: 0.2, delay: 0.0, options: [], animations: {
self.spinner.center = CGPoint(x: -20.0, y: 16.0)
self.spinner.alpha = 0.0
self.loginButton.bounds.size.width -= 80.0
self.loginButton.center.y -= 60.0
}, completion: nil)
}
func animateCloud(_ cloud: UIImageView) {
let cloudSpeed = 60.0 / view.frame.size.width
let duration = (view.frame.size.width - cloud.frame.origin.x) * cloudSpeed
UIView.animate(withDuration: TimeInterval(duration), delay: 0.0, options: .curveLinear, animations: {
cloud.frame.origin.x = self.view.frame.size.width
}, completion: { _ in
cloud.frame.origin.x = -cloud.frame.size.width
self.animateCloud(cloud)
})
}
}
func delay(_ seconds: Double, completion: @escaping ()->Void) {
DispatchQueue.main.asyncAfter(deadline: .now() + seconds, execute: completion)
}
func tintBackgroundColor(layer: CALayer, toColor: UIColor) {
let tint = CABasicAnimation(keyPath: "backgroundColor")
tint.fromValue = layer.backgroundColor
tint.toValue = toColor.cgColor
tint.duration = 0.5
layer.add(tint, forKey: nil)
layer.backgroundColor = toColor.cgColor
}
func roundCorners(layer: CALayer, toRadius: CGFloat) {
let round = CABasicAnimation(keyPath: "cornerRadius")
round.fromValue = layer.cornerRadius
round.toValue = toRadius
round.duration = 0.5
layer.add(round, forKey: nil)
layer.cornerRadius = toRadius
}
class TextField: UITextField {
let padding = UIEdgeInsets(top: 0, left: 5, bottom: 0, right: 5)
override open func textRect(forBounds bounds: CGRect) -> CGRect {
return bounds.inset(by: padding)
}
override open func placeholderRect(forBounds bounds: CGRect) -> CGRect {
return bounds.inset(by: padding)
}
override open func editingRect(forBounds bounds: CGRect) -> CGRect {
return bounds.inset(by: padding)
}
}