基于 UIView 的 transform 属性
缩放
该方法有两个参数,分别控制 UI 组件宽高的缩放比
button.transform = CGAffineTransform(scaleX: 0.5, y: 0.8)
旋转
rotationAngle 方法以弧度为单位进行旋转
button.transform = CGAffineTransform(rotationAngle: .pi / 4)
位移
设置当前位置相对于x、y轴移动了多少
button.transform = CGAffineTransform(translationX: -20, y: 50)
动画常用的属性
UIView.animate(withDuration: 0.25, delay: 2, options: UIView.AnimationOptions.repeat, animations: {
self.button.transform = CGAffineTransform(translationX: -20, y: 50)
}) { (finish) in
}
withDuration:
动画时长
delay:
动画延迟执行时间间隔
AnimationOptions
�:
-
layoutSubviews
子控件和父控件一起动画 -
allowUserInteraction
动画进行时,允许交互 -
beginFromCurrentState
从当前状态下开始动画 -
repeat
动画无限的重复 -
autoreverse
执行动画回路,前提是设置动画无限重复 -
overrideInheritedDuration
忽略外层动画嵌套的执行时间 -
overrideInheritedCurve
忽略外层动画嵌套的时间变化曲线 -
allowAnimatedContent
通过改变属性和重绘实现动画效果,如果key没有提交动画将使用快照 -
showHideTransitionViews
用显隐的方式替代添加移除图层的动画 -
overrideInheritedOptions
忽略嵌套继承的动画 -
curveEaseInOut
由慢到快的进入 -
curveEaseIn
由慢到特别快的进入 -
curveEaseOut
由快到慢的退出 -
curveLinear
匀速进入 -
transitionFlipFromLeft
从左边翻转动画 -
transitionFlipFromRight
从右边翻转动画 -
transitionCurlUp
向上翻书动画 -
transitionCurlDown
向下翻树动画 -
transitionCrossDissolve
交叉消失动画 -
transitionFlipFromTop
从上向下翻转动画 -
transitionFlipFromBottom
从下向上翻转动画 -
preferredFramesPerSecond60
每秒60帧的刷新频率动画 -
preferredFramesPerSecond30
每秒30帧的刷新频率动画
关键帧动画
简单的帧动画
UIView.animateKeyframes(withDuration: 2, delay: 0, options: UIView.KeyframeAnimationOptions.calculationModeLinear, animations: {
self.button.transform = CGAffineTransform(translationX: -20, y: 50)
}) { (finish) in
}
KeyframeAnimationOptions
:
-
layoutSubviews
子控件和父控件一起动画 -
allowUserInteraction
动画进行时,允许交互 -
beginFromCurrentState
从当前状态下开始动画 -
repeat
动画无限的重复 -
autoreverse
执行动画回路,前提是设置动画无限重复 -
overrideInheritedDuration
忽略外层动画嵌套的执行时间 -
overrideInheritedOptions
忽略外层动画嵌套的时间变化曲线 -
calculationModeLinear
在帧动画之间采用线性过渡 -
calculationModeDiscrete
在帧动画之间不过渡,直接执行各自动画 -
calculationModePaced
将不同帧动画的效果尽量融合为一个比较流畅的动画 -
calculationModeCubic
不同动画之间平滑过渡 -
calculationModeCubicPaced
帧动画平滑均匀动画
addKeyframe 方法
UIView.animateKeyframes(withDuration: 2, delay: 0, options: UIView.KeyframeAnimationOptions.calculationModeLinear, animations: {
UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 1 / 2) {
self.button.transform = CGAffineTransform(translationX: -60, y: -30)
}
UIView.addKeyframe(withRelativeStartTime: 1 / 2, relativeDuration: 1 / 2) {
self.button.transform = CGAffineTransform(translationX: 20, y: 50)
}
}) { (finish) in
}
addKeyframe
方法中:
-
withRelativeStartTime
:关键帧起始时间 -
relativeDuration
:关键帧相对持续时间
逐帧动画
基于 Timer 的逐帧动画效果
这种方法经常使用在动画帧率不高,且帧率之间的时间间隔并不身份严格的情况下。
比如实现图片按照连续的顺序和一定的时间间隔显示图片
Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(refreshImage), userInfo: nil, repeats: true)
基于 CADisplayLink 的逐帧动画
iOS设备的屏幕刷新频率是60Hz,而 CADisplayLink
可以保持和屏幕刷新率相同的频率将内容渲染到屏幕上,因此它的精确非常高。CADisplayLink
在使用的时候需要注册到 runloop
中,每当发送一次指定的 selector
消息,相应的 selector
中的方法会被调用一次。
preferredFramesPerSecond
设置为 1 时表示当前的刷帧周期为1/60s。当设置为 2 时表示当前的刷帧周期为1/60 * 2。
let displayLink = CADisplayLink(target: self, selector: #selector(refreshImage))
displayLink.preferredFramesPerSecond = 1
displayLink.add(to: RunLoop.current, forMode: .default)
基于 draw 方法的逐帧动画
draw()
方法的触发机制
- 使用
addSubview
会触发layoutSubviews
- 使用
view
的frame
属性会触发layoutSubviews
- 直接调用
setNeedsDisplay()
通过 Timer
或 CADisplayLink
逐帧渲染来实现
GIF动画效果
借助 ImageIO
框架实现图片的分解和合成
GIF 分解单帧图片
if let path = Bundle.main.path(forResource: "image", ofType: "gif") {
// 本地读取GIF图片,将其转换为 Data 数据类型
let data = try! Data(contentsOf: URL(fileURLWithPath: path))
// 将 Data 作为 ImageIO 模块的输入
let dataSource = CGImageSourceCreateWithData(data as CFData, nil)
let imageCount = CGImageSourceGetCount(dataSource!)
for i in 0..<imageCount {
let imageRef = CGImageSourceCreateImageAtIndex(dataSource!, i, nil)
// 获取 ImageIO 的输出数据:UIImage
let image = UIImage(cgImage: imageRef!, scale: UIScreen.main.scale, orientation: .up)
// 将获取到的 UIImage 数据存储为JPG或PNG格式保存到本地
let imageData = image.pngData()
try? imageData?.write(to: URL(fileURLWithPath: NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true).first! + "/\(i)" + ".png"), options: .atomic)
}
}
序列图像合成GIF图像
// 读取全部png图片
var images = [UIImage]()
for i in 0...20 {
let imageName = "\(i).png"
let image = UIImage(named: imageName)!
images.append(image)
}
// 创建gif文件
let gifPath = NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true).first! + "/image.gif"
let url = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, gifPath as CFString, .cfurlposixPathStyle, false)
// import CoreServices
let destion = CGImageDestinationCreateWithURL(url!, kUTTypeGIF, images.count, nil)
// 设置gif图片属性
let delayTime = [kCGImagePropertyGIFDelayTime : 0.1] // 每帧之间播放时间
let property = [kCGImagePropertyGIFDictionary : delayTime]
for cgImage in images {
CGImageDestinationAddImage(destion!, cgImage.cgImage!, property as CFDictionary)
}
var gifProperties = [CFString : Any]()
gifProperties[kCGImagePropertyColorModel] = kCGImagePropertyColorModelRGB
gifProperties[kCGImagePropertyDepth] = 16 //设置图像的颜色深度。颜色深度为1,表示2的一次方。
gifProperties[kCGImagePropertyGIFLoopCount] = 1 //设置gif执行次数
let dict = [kCGImagePropertyGIFDictionary : gifProperties]
CGImageDestinationSetProperties(destion!, dict as CFDictionary)
CGImageDestinationFinalize(destion!)
GIF图片展示:基于UIImageView
var images = [UIImage]()
for i in 0...20 {
let imageName = "\(i).png"
let image = UIImage(named: imageName)!
images.append(image)
}
imageView.animationImages = images
imageView.animationDuration = 5
imageView.animationRepeatCount = 1
imageView.startAnimating()
CABasicAnimation
位置动画
let animation = CABasicAnimation()
animation.keyPath = "position"
animation.toValue = NSValue(cgPoint: CGPoint(x: 50, y: 80))
animation.duration = 2
animation.fillMode = .forwards
animation.isRemovedOnCompletion = false
button.layer.add(animation, forKey: nil)
position
表明当前是为了修改控件位置信息。
-
toValue
从现在的位置移动到指定的位置 -
byValue
在控件原来位置的基础上移动多少位置 -
fromValue
从指定的位置移动到现在的位置
缩放动画
transform.scale
属性可实现挤压效果。scale
还有x、y两个属性,x
属性表明当前UI控件的 width
的缩放系数。y
属性表明当前UI控件的 height
的缩放系数。
let animation = CABasicAnimation()
animation.keyPath = "transform.scale.x"
animation.fromValue = 1.0
animation.toValue = 0.5
animation.duration = 2
animation.fillMode = .forwards
animation.isRemovedOnCompletion = false
button.layer.add(animation, forKey: nil)
旋转动画
transform.rotation
属性可实现旋转动画
let animation = CABasicAnimation()
animation.keyPath = "transform.rotation"
animation.toValue = Double.pi / 2
animation.duration = 2
animation.fillMode = .forwards
animation.isRemovedOnCompletion = false
button.layer.add(animation, forKey: nil)
位移动画
Translation
还有x、y两个属性分别表示在x、y方向上移动
let animation = CABasicAnimation()
animation.keyPath = "transform.translation.x"
animation.toValue = 100
animation.duration = 2
animation.fillMode = .forwards
animation.isRemovedOnCompletion = false
button.layer.add(animation, forKey: nil)
圆角动画
cornerRadius
设置 Layer
图层圆角属性
let animation = CABasicAnimation()
animation.keyPath = "cornerRadius"
animation.toValue = 20
animation.duration = 2
animation.fillMode = .forwards
animation.isRemovedOnCompletion = false
button.layer.add(animation, forKey: nil)
边框动画
button.layer.borderColor = UIColor.orange.cgColor
let animation = CABasicAnimation()
animation.keyPath = "borderWidth"
animation.toValue = 5
animation.duration = 2
animation.fillMode = .forwards
animation.isRemovedOnCompletion = false
button.layer.add(animation, forKey: nil)
颜色渐变动画
let animation = CABasicAnimation()
// 边框颜色的渐变
// animation.keyPath = "borderColor"
animation.keyPath = "backgroundColor"
animation.fromValue = UIColor.orange.cgColor
animation.toValue = UIColor.red.cgColor
animation.duration = 2
animation.fillMode = .forwards
animation.isRemovedOnCompletion = false
button.layer.add(animation, forKey: nil)
淡入淡出动画
let animation = CABasicAnimation()
animation.keyPath = "opacity"
animation.fromValue = 0
animation.toValue = 1
animation.duration = 10
animation.fillMode = .forwards
animation.isRemovedOnCompletion = false
button.layer.add(animation, forKey: nil)
阴影渐变动画
button.layer.shadowColor = UIColor.red.cgColor
button.layer.shadowOpacity = 0.5
let animation = CABasicAnimation()
animation.keyPath = "shadowOffset"
animation.toValue = NSValue(cgSize: CGSize(width: 10, height: 10))
animation.duration = 10
animation.fillMode = .forwards
animation.isRemovedOnCompletion = false
button.layer.add(animation, forKey: nil)
CAKeyframeAnimation
淡出动画
let animation = CAKeyframeAnimation()
animation.keyPath = "opacity"
animation.values = [0.95, 0.8, 0.5, 0.05, 0.00]
animation.duration = 6
animation.fillMode = .forwards
animation.isRemovedOnCompletion = false
button.layer.add(animation, forKey: nil)
任意路径动画
addArc
:以 center
为圆心,radius
为半径,startAngle
为起始角度,endAngle
为最终角度,clockwise
为顺时针
let pathLine = CGMutablePath()
pathLine.move(to: CGPoint(x: 50, y: 50))
pathLine.addLine(to: CGPoint(x: 100, y: 200))
pathLine.addArc(center: CGPoint(x: 200, y: 300), radius: 50, startAngle: 0, endAngle: .pi / 2, clockwise: true)
let animation = CAKeyframeAnimation()
animation.keyPath = "position"
animation.duration = 6
animation.path = pathLine
animation.fillMode = .forwards
animation.isRemovedOnCompletion = false
button.layer.add(animation, forKey: nil)
CAAnimationGroup组合动画
let rotation = CABasicAnimation()
rotation.keyPath = "transform.rotation"
rotation.toValue = Double.pi
let scale = CABasicAnimation()
scale.keyPath = "transform.scale"
scale.toValue = 0.2
let move = CABasicAnimation()
move.keyPath = "transform.translation"
move.toValue = NSValue(cgPoint: CGPoint(x: 50, y: 50))
let animationGroup = CAAnimationGroup()
animationGroup.animations = [rotation, scale, move]
animationGroup.duration = 6
animationGroup.fillMode = .forwards
animationGroup.isRemovedOnCompletion = false
button.layer.add(animationGroup, forKey: nil)
实践:水纹按钮动画效果
AnimationButton
import UIKit
class AnimationButton: UIButton {
private var countNum = 0
private var circleCenterX: CGFloat = 0
private var circleCenterY: CGFloat = 0
private var radius: CGFloat = 0
private var animationColor = UIColor.red
private var timer = Timer()
override init(frame: CGRect) {
super.init(frame: CGRect(x: 20, y: 200, width: UIScreen.main.bounds.size.width - 40, height: 44))
backgroundColor = .lightGray
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func startAnimation(_ sender: UIButton, _ event: UIEvent) {
isUserInteractionEnabled = false
let touchSet = event.touches(for: sender)
let touch = touchSet?.first
let point = touch?.location(in: sender)
circleCenterX = point!.x
circleCenterY = point!.y
timer = Timer.scheduledTimer(timeInterval: 0.2, target: self, selector: #selector(timerAction), userInfo: nil, repeats: true)
RunLoop.main.add(timer, forMode: .common)
}
@objc func timerAction() {
countNum += 1
DispatchQueue.main.asyncAfter(deadline: .now()) {
self.radius += 10
self.setNeedsDisplay()
}
if countNum > 50 {
countNum = 0
radius = 0
timer.invalidate()
DispatchQueue.main.asyncAfter(deadline: .now()) {
self.radius = 0
self.setNeedsDisplay()
}
isUserInteractionEnabled = true
}
}
override func draw(_ rect: CGRect) {
let ctx = UIGraphicsGetCurrentContext()
ctx?.addArc(center: CGPoint(x: circleCenterX, y: circleCenterY), radius: radius, startAngle: 0, endAngle: .pi * 2, clockwise: true)
animationColor.setStroke()
animationColor.setFill()
ctx?.fillPath()
}
}
使用
override func viewDidLoad() {
super.viewDidLoad()
let button = AnimationButton()
button.addTarget(self, action: #selector(buttonClick(_:_:)), for: .touchUpInside)
view.addSubview(button)
}
@objc func buttonClick(_ sender: UIButton, _ event: UIEvent) {
let button = sender as! AnimationButton
button.startAnimation(sender, event)
}
网友评论