summer的ios小记iOS Developer

动手打造Swift声明式动画框架-1

2017-08-07  本文已影响29人  微微笑的蜗牛

在iOS中做动画,还比较简单。因为苹果已经提供好了api,设置相应的参数就好。如下,一句简单的代码,就可做frame的动画。

UIView.animate(withDuration: 0.3) {
    button.frame.size = CGSize(width: 200, height: 200)
}

但是,如果我们想要在动画结束之后,再做其他的动画,就需要在completion block中嵌套。写法上不太直观,也不利于阅读。当这种需求越多,block的嵌套也就越深。代码会看的很费劲。

UIView.animate(withDuration: 0.3, animations: {
    button.alpha = 1
}, completion: { _ in
    UIView.animate(withDuration: 0.3) {
        button.frame.size = CGSize(width: 200, height: 200)
    }
})

于是,就出现了声明式的api。

声明式:简单,易读,便于理解。把我们要做的事情写好,一目了然。

声明式动画

下面的代码,看起来就清晰多了。

button.animate([
    .fadeIn(duration: 0.3),
    .resize(to: CGSize(width: 200, height: 200), duration: 0.3)
])

fadeIn:做alpha的动画。
resize:做缩放的动画。

实现

准备

新建playground。因为我们要看到实时的view,所以需要import PlaygroundSupport。新建view后,再添加一句。打开Assistant Editor(带2个圆圈的按钮),在右边就可以看得到view。

PlaygroundPage.current.liveView = view
数据结构

既然要做声明式的api,那就需要将UIView.animate这个方法封装起来,在内部调用。所以需要一个结构来存储动画时间,如何做动画(block)。

public struct Animation {
    public let duration: TimeInterval
    public let closure: (UIView) -> Void
}

fadeIn,resize动画,只是封装一层而已。

public extension Animation {
    static func fadeIn(duration: TimeInterval = 0.3) -> Animation {
        return Animation(duration: duration, closure: { $0.alpha = 1 })
    }

    static func resize(to size: CGSize, duration: TimeInterval = 0.3) -> Animation {
        return Animation(duration: duration, closure: { $0.bounds.size = size })
    }
}
动画

扩展UIView,添加animate方法,这样每个view就可以直接调用。参数是Animation的数组,目的是为了可以执行一系列的动画。在UIView.animate的completion block中递归调用animate,形成one by one的动画。

public extension UIView {
    func animate(_ animations: [Animation]) {
        // Exit condition: once all animations have been performed, we can return
        guard !animations.isEmpty else {
            return
        }

        // Remove the first animation from the queue
        var animations = animations
        let animation = animations.removeFirst()

        // Perform the animation by calling its closure
        UIView.animate(withDuration: animation.duration, animations: {
            animation.closure(self)
        }, completion: { _ in
            // Recursively call the method, to perform each animation in sequence
            self.animate(animations)
        })
    }
}
Demo

fadeIn先执行完,resize再执行。

let animationView = UIView(frame: CGRect(
    x: 0, y: 0,
    width: 50, height: 50
))

animationView.backgroundColor = .red
animationView.alpha = 0
view.addSubview(animationView)

animationView.animate([
    .fadeIn(duration: 3),
    .resize(to: CGSize(width: 200, height: 200), duration: 3)
])

并行动画

上面的实现是队列式的动画,一个动画执行完之后,才会执行另外一个。若要实现并行的动画呢?也简单,就不用等到动画结束后才去执行下一个,直接便利执行即可。

public extension UIView {
    func animate(inParallel animations: [Animation]) {
        for animation in animations {
            UIView.animate(withDuration: animation.duration) {
                animation.closure(self)
            }
        }
    }
}

调用:

animationView.animate(inParallel: [
    .fadeIn(duration: 3),
    .resize(to: CGSize(width: 200, height: 200), duration: 3)
])

参考:
Building a declarative animation framework in Swift - Part 1

上一篇 下一篇

猜你喜欢

热点阅读