AnimationSeries 1.4.1

AnimationSeries 1.4.1

维护者: gearmamn06.



  • gearmamn06

AnimationSeries

Platform Language License

创建动画链的简单方法。

func drunkAnimation() {        
    let headbanging = 🧠.rotate(degree: 360, duration: 0.1)
    let stumble = (🚶.move(position: CGPoint(x: -10, y: 0), duration: 0.5) + 🚶.move(position: CGPoint(x: 10, y: 0), duration: 0.5)) * 10
    let puke = (🤢.sizing(scale: (100, 100), duration: 0.4) + 🤢.sizing(scale: (0.1, 0.1), duration: 0.2)) * 10
        
    let drunk = (headbanging | stumble | puke) * Int.max
    drunk.start()
 }



需求

iOS 10.0+

安装

CocoaPods

pod 'AnimationSeries'

或者将 AnimationSeries.framework 文件添加到您要使用的项目中。(如果需要请检查“如果需要则复制项目”)

为什么选择 AnimationSeries?

在 iOS 项目中使用循环动画并非易事。
例如,编写一个重复三次闪烁视图的动画代码

    private func blinkView3times() {
        func appear(_ v: UIView, duration: TimeInterval, completed: ((Bool) -> Void)?) {
            UIView.animate(withDuration: duration, animations: {
                v.alpha = 1.0
            }, completion: completed)
        }
        
        func disappear(_ v: UIView, duration: TimeInterval, completed: ((Bool) -> Void)?) {
            UIView.animate(withDuration: duration, animations: {
                v.alpha = 0.0
            }, completion: completed)
        }
        
        func blink(_ v: UIView, duration: TimeInterval, completed: ((Bool) -> Void)?) {
            disappear(v, duration: 1.0, completed: { _ in
                appear(v, duration: 1.0, completed: completed)
            })
        }
        
        blink(mView, duration: 1.0, completed: { _ in
            blink(self.mView, duration: 1.0, completed: { _ in
                blink(self.mView, duration: 1.0, completed: nil)
            })
        })
    }

如果需要闪烁视图 100 次,这种方法就不酷了... 或者您可以在循环中给每个动画设置延迟来解决此问题。但是,这种方法计算起来很繁琐。

我们可以使用 AnimationSeries 来直观地解决这个问题。

    private func blinkView100times() {
        let blink = myView.disappear(duration: 1.0) + myView.appear(duration: 1.0)
        let anim = blink * 100
        anim.start()
    }

它是这样的?这是否看起来更直观和简单?
AnimationSeries 项目是在类似上述困难的情况下制作的。有了这个工具,你可以更容易地制作视图动画。

基础知识

在此项目视图扩展 (.appear, .disappear, .discolor, .move, .rotate, sizing) 中声明了默认动画。
可以使用 + (顺序连接) 和 * (重复) 操作符连接这些动画函数。

单动画

以下任何一个动画都会返回一个 AnimationSeries 实例。调用 start 函数以启动动画。通过设置 complete 回调,你可以获取动画的结束回调。

/// view.alpha -> 1.0 with flat parameters
    public func appear(duration: TimeInterval, delay: TimeInterval = 0.0, options: UIView.AnimationOptions = [], _ complete:   CompleteCallback? = nil) -> AnimationSeries {
        let anim = Appear(self, params:  ViewAnimation.Parameter(duration, delay: delay, options: options), complete)
        return anim
    }
    
    
    /// view.alpha -> 1.0 with AnimationParameter
    public func appear(_ params:  ViewAnimation.Parameter, _ complete: CompleteCallback? = nil) -> AnimationSeries {
        return self.appear(duration: params.duration, delay: params.delay, options: params.options, complete)
    }

(AnimationParameter 是一个包含时间、延迟和选项的结构体。)

组合动画

可以使用加号 (+) 运算符将 AnimationSeries 实例与其他 AnimationSeries 实例组合。
组合实例返回一个新的 AnimationSeries。
调用新实例的 start 方法以启动动画序列。同样,设置新实例的 animationDidFinish 回调可以让你在所有动画完成后获取回调。
(如果你将 CompleteCallback 设置到一个单个的 AnimationSeries,你可以收到一个当它结束时触发的回调。)

    private func startInitialAnim() {
        let anim = animView.sizing(scale: (40, 40), duration: 0) + animView.sizing(scale: (0.6, 0.6), duration: 1.6, { _ in
            print("shrink(single animation) end.")
        }) + animView.sizing(scale: (1.0, 1.0), duration: 0.3)

        anim.animationDidFinish = { [weak anim] in
            print("Intial animation(animation series) end. -> release point")
            AnimationPool.shared.release(anim)
        }
        anim.start()
    }

并行动画

如果你想同时启动某些动画,可以使用竖线符号 (|) 连接动画。

    let drunk = (headbanging | stumble | puke) * Int.max

循环动画

可以使用乘号 (*) 运算符重复 AnimationSeries 实例。

    let series = view.discolor(to: .orange, duration: 1) + view.discolor(to: .yellow, duration: 1) + view.discolor(to: .green, duration: 1) + view.discolor(to: .blue, duration: 1) + view.discolor(to: .purple, duration: 1)

    let repeating = series * 10
    repeating.start()

清除动画

您可以使用清除函数来停止动画。(需要额外工作才能将视图恢复到原始外观。)

    private func clearCurrentAnimation() {
        self.currentAnimations.forEach{ $0.clear() }
        self.initializeView()
    }

    private func initializeView() {
        animView.transform = CGAffineTransform(rotationAngle: 0)
        animView.transform = CGAffineTransform(scaleX: 1.0, y: 1.0)
        animView.transform = CGAffineTransform(translationX: 0, y: 0)
        animView.alpha = 1.0
        animView.backgroundColor = .red
    }

内存问题

当使用加号或乘号运算符创建 AnimationSeries 实例时,参与系列的 AnimationSeries 实例会保存在静态内存中,以防止它们形成循环引用或在下操作之前从内存中释放。

动画结束后,强烈建议按照以下方式从内存中释放。 (AnimationSeries.clear 函数也会释放内存。)

    let anim = self.view.disappear(duration: 1, delay: 0.0, options: .curveLinear, {  _ in
        // When the animation finishes, the completeCallback will be called.
        print("disappear animation end")
    })

    // single animation flushing
    anim.animationDidFinish = { [weak anim] in
        // animationDidFinish closure is used to connect with the following animation(recurable) instance.
        // If no animation is linked behind, you can flush it from the static memory when this closure is called.
        AnimationPool.shared.release(anim)
    }


    let series = (self.view.disappear(duration: 1) + self.view.appear(duration: 1)) * 10
    series.animationDidFinish = { [weak series] in
        // animationDidFinish closure of the series is called when the animation ends.
        // In this case, release the series from the static memory.
        AnimationPool.shared.release(anim)
    }
    series.start()

注意

调用清除函数后,所有的 AnimationSeries 实例都不能重新启动。
它也是一个引用类型,所以不会被复制。

 
    private func wrongUsage() {
        
        // wrong: blink will not be copied
        let blink = animView.disappear(duration: 1) + animView.appear(duration: 1)
        let blinks3Times = blink + blink + blink
        blinks3Times.start()
    }

自定义

您可以创建一个继承自 AnimationSeries 的类来定义您想要的动画。或者将动画添加到视图的扩展中。

    import UIKit
    import AnimationSeries

    extension UIView {
        
        public func move(path: [(CGPoint, ViewAnimation.Parameter)]) -> AnimationSeries? {
            guard !path.isEmpty else { return nil }
            var sender: AnimationSeries!
            path.forEach { tp in
                if sender == nil {
                sender = self.move(position: tp.0, params: tp.1) + self.move(position: tp.0, params: ViewAnimation.Parameter(0.0))
                }else{
                    sender = sender + self.move(position: tp.0, params: tp.1)
                }
            }
            return sender
        }
        
    }

    ....

    private func customMoveAnimation() {
        let params = ViewAnimation.Parameter(0.2)
        let paths = (0..<10).reduce(into: [(CGPoint, ViewAnimation.Parameter)](), { ary, n in
            ary.append((CGPoint(x: ary.count + 10, y: 0), params))
        })
        let anim = animView.move(path: paths)
        anim?.animationDidFinish = { [weak anim] in
            print("moving all end..")
            AnimationPool.shared.release(anim)
        }
        anim?.start()
    }