美文网首页iOS 技能
iOS 动画(一)

iOS 动画(一)

作者: gaookey | 来源:发表于2020-06-23 13:05 被阅读0次

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

    • 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
    • 使用 viewframe 属性会触发 layoutSubviews
    • 直接调用 setNeedsDisplay()

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

    • 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)
    }
    

    相关文章

      网友评论

        本文标题:iOS 动画(一)

        本文链接:https://www.haomeiwen.com/subject/zkthfktx.html