函数式编程之动画组合

前言

本篇文章的提纲
1.举一个例子,理解函数式编程
2.用函数式编程的思想,做一个可以随意组合的动画库

什么是函数式编程?

关于函数式编程,网上有很多。浏览众多的文章后,对于函数式编程有了深刻的概念(其实就只记住了一句话): 函数是第一公民(函数可以当作参数,返回值。可以由多个低级的函数组合成一个高级的函数)。

下面我们来看一个例子

在图-1中,我们定义一个叫 AddGroup(加法组合) 的函数 。


图-1.png

很多人第一眼看到就觉得这就是一个闭包或者block嘛,还故意打码,搞出一副很深奥的样子。
虽然这是一个block,但是不能把它理解为block,因为当你认为这是block时,你就会想到回调。(反正我在网上看到这些例子的时候,就陷死在block的思维里了)。
这里我们把它理解为函数,或者公式,一套加法组合的公式。

在图-2中,我们定义生成一个加法组合公式的func ,我暂时把实现的细节马赛克了,因为我第一次看到这些例子的时候,陷死在了那些实现的细节里。

图-2

接下来就是开始使用这些函数

图-3

再用个高级一点的

图-4

上图-4中有一个叫compose的方法,作用是将add2和add3结合起来,组合成一个新的AddGroup。
第一个例子已经举完,下面就是源代码,大家可以直接复制粘贴到playground上去运行。在看下面源代码前可以先尝试着自己去实现。

//: Playground - noun: a place where people can play

import UIKit

typealias AddGroup = (Int)->Int //理解为的一系列加法组合的函数

func compose(_ addGroup1 : @escaping AddGroup , _ addGroup2 : @escaping AddGroup) -> AddGroup {
    return { a in
        return addGroup1(addGroup2(a))
    }
}

func add(_ num : Int) -> AddGroup {
    return { a in
        return a + num
    }
}

extension Int {
    //给Int扩展一个执行AddGroup这个函数的方法
    func excute(addGroup : AddGroup) -> Int {
        return addGroup(self)
    }
}

let add2 : AddGroup = add(2)//生成一个叫add2(加2)的AddGroup
let add3 : AddGroup = add(3)//生成一个叫add3(加3)的AddGroup
let addNew : AddGroup = compose(add2, add3)
let result = 1.excute(addGroup: addNew)//让1去执行这个AddGroup,得到结果为6
print("结果为 :",result)

开始做一个可以任意组合的动画库

先看一看这个库是如何调用的。

图-5

如上图-5所示。=> 这个符号表示,执行完前一个动画后再执行下一个动画。 + 加号符号表示 ,两个动画一起执行 。 + 的优先级高于 => , 先执行加号 ,在执行 =>。

看一看执行的效果

图-6

这算是动画比较多的了,但是调用起来还是比较简单,可读性也比较高的。

在来一个:

图-7
代码部分

首先我们定义一个要存储动画的类型

typealias AnimatState = (UIView) -> UIView

生成一个移动动画的方法,这里有个view.animCount , 我在后面会讲。

func move(to point: CGPoint , with duration : TimeInterval) -> AnimatState {
    return { view in

        view.animCount += 1
        
        UIView.animate(withDuration: duration, delay: 0, options: [] , animations: {
            view.frame.origin = point
        }, completion: { _ in
            view.frame.origin = point
            view.animCount -= 1
        })
    
        return view
    }
}

生成透明,旋转,缩放一系列的动画

透明
func fade(_ alpha: CGFloat , with duration : TimeInterval) -> AnimatState{
    return { view in
            
        view.animCount += 1
        
        UIView.animate(withDuration: duration, delay: 0, options: [] , animations: {
            view.alpha = alpha
        }, completion: { _ in
            view.alpha = alpha
            view.animCount -= 1
        })
        
        return view
    }
}
缩放
func scale(to scale: CGFloat , with duration : TimeInterval) -> AnimatState {
    return { view in
    
        view.animCount += 1
        
        UIView.animate(withDuration: duration, delay: 0, options: [] , animations: {
            view.transform = CGAffineTransform.init(scaleX: scale, y: scale)
        }, completion: { _ in
            view.transform = CGAffineTransform.init(scaleX: scale, y: scale)
            view.animCount -= 1
        })
        
        return view
    }
}
旋转
func rotate(by angle: CGFloat , with duration : TimeInterval ) -> AnimatState {
    return { view in
        
        view.animCount += 1
        UIView.animate(withDuration: duration, delay: 0, options: [] , animations: {
            view.transform = .init(rotationAngle: angle)
        }, completion: { _ in
            view.transform = .init(rotationAngle: angle)
            view.animCount -= 1
        })
        
        return view
    }
}

再生成一个空动画

func emptyAnim(_ duration : TimeInterval , closure : (()->Void)?) -> AnimatState{
    return { view in
    
        view.animCount += 1
        closure?()
        DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + duration, execute: { 
            view.animCount -= 1
        })
        return view
    }
}

一些最基本的动画就做完了。
接下来就开始做重载 + 操作符 , 加号表示的是将两个动画结合成一个能同时执行这两个小动画的大动画

func + (left : @escaping  AnimatState , right : @escaping AnimatState) -> AnimatState {
    return { view in
        return right(left(view))
    }
}

重载 => 操作符 ,=> 表示的是先执行左边的,然后再执行右边的。

func => (left : @escaping AnimatState , right :@escaping AnimatState) -> AnimatState {
    return { view in
        view.animCompleteHandles.append( { [weak view] in
            guard view != nil else { return }
            _ = right(view!)
        })
      return left(view)
    }
}

animCompleteHandles 是UView extension 的一个用来存放动画完成以后要做的动画回调的数组。 看一看它的实现

fileprivate var anim_comptele_handle_key : Void!
extension UIView {
  var animCompleteHandles : [AnimComleteHandle] {
        set{
            objc_setAssociatedObject(self, &anim_comptele_handle_key, ( newValue as Any ), .OBJC_ASSOCIATION_COPY_NONATOMIC)
        }
        get{
            return (objc_getAssociatedObject(self, &anim_comptele_handle_key) as? [AnimComleteHandle]) ?? [AnimComleteHandle]()
        }
    }
    
}

接下来,我们还需要一个东西来判断何时做完动画。这里我用的是模仿引用计数的方式, 就是前面提到的view.animCount,当执行一个动画的时候,计数 +1 , 执行执行完后计数 -1。当计数等于0时,我们要调用animCompleteHandles数组里面的最后一个元素进行回调,然后销毁。

extension UIView {
    fileprivate var animCount : Int {
        set{
            
            if newValue < 0 {
                objc_setAssociatedObject(self, &anim_count_key, 0, .OBJC_ASSOCIATION_COPY_NONATOMIC)
                return 
            }
            
            objc_setAssociatedObject(self, &anim_count_key, newValue, .OBJC_ASSOCIATION_COPY_NONATOMIC)
            
            if newValue == 0 {
                animCompleteHandles.popLast()?()
            }
            
        }
        get{
            return ( objc_getAssociatedObject(self, &anim_count_key) as? Int ) ?? 0
        }
    }
}
献上demo

https://github.com/Yuanjihua1/BuildingAnimation

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,468评论 5 473
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,620评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,427评论 0 334
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,160评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,197评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,334评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,775评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,444评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,628评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,459评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,508评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,210评论 3 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,767评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,850评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,076评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,627评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,196评论 2 341

推荐阅读更多精彩内容