iOS 技能

iOS 动画(一)

2020-06-23  本文已影响0人  gaookey

iOS 动画(一)
iOS 动画(二)

基于 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�:

关键帧动画

简单的帧动画

UIView.animateKeyframes(withDuration: 2, delay: 0, options: UIView.KeyframeAnimationOptions.calculationModeLinear, animations: {
    
    self.button.transform = CGAffineTransform(translationX: -20, y: 50)
}) { (finish) in
    
}

KeyframeAnimationOptions

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 方法中:

逐帧动画

基于 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() 方法的触发机制

通过 TimerCADisplayLink 逐帧渲染来实现

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 表明当前是为了修改控件位置信息。

缩放动画

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)
}
上一篇下一篇

猜你喜欢

热点阅读