非死book POP,邁向大師操作之路
非死book 發布 Paper 之后,似乎還不滿足于只是將其作為一個概念性產品,反而更進一步開源了其背后的動畫引擎 POP(https://github.com/非死book/pop),此舉大有三年前發布的 iOS UI 框架 Three20(https://github.com/非死bookarchive/three20)的意味。而 POP 開源后不負 非死book 的厚望,在 GitHub 上不足一個月的時間,就已擁有了 6000 多個 Star,非常火爆。
POP 背后的開發者是 Kimon Tsinteris,他是 Push Pop Press 的聯合創始人,曾在蘋果擔任高級工程師,并參與了 iPhone 和 iPad 上的軟件研發(iPhone 的指南針及地圖)。2011 年,非死book 收購了他的公司,此后他便加入了 非死book 負責 非死book iOS 版本的開發。
如果你打開 Push Pop Press 開發的《AI Gore》這款 App,就會發現它的交互和動畫與 Paper 幾乎如出一轍,原因就在于,它們都是由 Kimon Tsinteris 開發的。由于不滿足于蘋果自身動畫框架的單調,Push Pop Press 致力于創造一個逼真的、充滿物理效應的體驗。POP 就是在這個理念下催生出來的新一代成果。
POP 使用 Objective-C++編寫。Objective-C++是對 C++ 的擴展,就像 Objective-C 是C的擴展一樣。而至于為什么他們用 Objective-C++而不是純粹的 Objective-C,原因在于他們更喜歡 Objective-C++的語法特性所提供的便利。
POP 的架構
POP 目前由四個部分組成(如圖 1 所示),即 Animations、Engine、Utility、WebCore。
圖 1 POP 架構圖
POP 動畫極為流暢,其秘密就在于這個引擎中的 POPAnimator。POP 通過 CADisplayLink 讓動畫實現了 60 FPS 的流暢效果,打造了一個游戲級的動畫引擎。
CADisplayLink 是類似 NSTimer 的定時器,不同之處在于,NSTimer 用于我們定義任務的執行周期及資料的更新周期,它的執行受 CPU 的阻塞所影響。而 CADisplayLink 則用于定義畫面的重繪和動畫的演變,它的執行是基于 Frames 的間隔。通過 CADisplayLink,蘋果允許開發者將 App 的重繪速度設定到與屏幕刷新頻率一致。因此開發者可以獲得非常流暢的交互動畫,這項技術的應用在游戲中非常常見,著名的 Cocos-2d 引擎也用到了這個重要的技術。
WebCore 里包含了一些從蘋果的開源的網頁渲染引擎里拿到的源文件(http://opensource.apple.com/source/WebCore),它與 Utility 里的組件一并為 POP 的各項復雜計算提供了基本支持。因此,通過 Engine、Utility、WebCore 三個基石,打造了 Animations。
POPAnimation 有著與 CALayer 非常相似的 API。如果你知道 CALayer 的動畫 API,那么你對下面的接口一定非常熟悉。說到這里,想必你一定開始迫不及待地想試試 POP 了(因篇幅所限,下面的代碼并不是完整代碼,你可以到 https://github.com/kevinzhow/pop-handapp 獲取示例 App)。
基本類型
· Spring Animation
圖 2 默認的兩種動畫模式以及他們的動畫節奏
POP 默認提供了兩個非常特別的動畫模式,第一個就是 Spring Animation(如圖 2 所示),另一個是 Decay Animation。讓我們先來看看 Spring Animation,控制其動畫效果的主要參數包括:
· Bounciness 反彈,影響動畫作用的參數的變化幅度;
· Speed 速度;
· Tension 拉力,影響回彈力度及速度;
· Friction 摩擦力,開啟后,動畫會不斷重復,并且幅度逐漸削弱,直到停止;
· Mass 質量,細微地影響動畫的回彈力度和速度。
實際上,Tension、Friction、Mass 這三個參數的作用很微妙,需要在示例程序中仔細體會。使用 Spring Animation 的方式非常簡單,如代碼 1 所示。
代碼1
通過[POPSpringAnimation animationWithPropertyNamed:kPOPLayerScaleXY]我們創建了一個在二維平面上分別沿著X軸和Y軸進行縮放的動畫。
下面我們介紹三個重要的參數。
· fromValue 將告訴 POP 物體被動畫操作的屬性從什么數值開始運行。如果不提供 fromValue,那么 POP 將默認使用當前數值。在這個例子中,就默認使用當前的比例。
· toValue 是我們希望動畫結束后,物體被動畫操作的屬性停留在什么值上,在這個例子中,toValue 告訴了 POP,我們希望沿著X軸和Y軸各縮放幾倍。
· completionBlock 提供了一個 Callback,動畫的執行過程會不斷調用這個 block,finished 這個布爾變量可以用來做動畫完成與否的判斷。
值得一提的是,這里 toValue 和 fromValue 的值應該和動畫所作用的屬性是一樣的數據結構。例如,如果我們的操作對象是 bounds,那么這里的 toValue 則應該是[NSValue valueWithCGRect:] 。
最后,我們使用 pop_addAnimation 來讓動畫開始生效,如果想刪除動畫的話,那么需要調用 pop_removeAllAnimations。
與 iOS 自帶的動畫不同,如果你在動畫的執行過程中刪除了物體的動畫,那么物體會停在動畫狀態的最后一個瞬間,而不是閃回開始前的狀態。
· Decay Animation
Decay Animation 可以實現衰減的動畫效果。這個動畫有一個重要的參數即 velocity(速率),這個參數一般并不用于物體的自發動畫,而是與用戶的交互共生。這和 iOS 7 引入的 UIDynamic 非常相似,如果你想實現一些物理效果,Decay Animation 也是不錯的選擇。
Decay 的動畫沒有 toValue 只有 fromValue,以 fromValue 作為原始值,按照 velocity 來做衰減操作。如果我們想做一個剎車效果,則可以像代碼 2 這樣操作:
代碼2
這個動畫會使得物體從X坐標的 25.0 開始做 100 點/秒的減速運動。如果 velocity 里的數字是負值,那么你的動畫就會反方向執行動畫效果。這里非常值得一提的是,velocity 也是必須和你操作的屬性有相同的數據結構,如果你操作的是 bounds,想實現一個水滴滴到桌面的擴散效果,那么 velocity 則應該是[NSValue valueWithRect:CGRectMake (0, 0, 20, 20)]。
deceleration(負加速度)是一個很少用到的值,它影響動畫被重力影響的效果。默認值就是我們地球的重力加速度 0.998。如果你程序里的動畫開發給火星人看,那么使用 0.376 這個值會更合適。
· Property Animation 和 Basic Animation
POP 號稱可以對物體的任何屬性進行動畫,其背后就是這個 Property Animation 驅動。Spring Animation 和 Decay Animation 都是繼承自這個類,接下來我們通過一個 Counting Label 的例子來演示 Property Animation 的神奇能力。在這個動畫中,我們也使用了 Basic Animation,動畫模式是經典的 ease-in-out,不使用 Spring Animation 是因為我們并不需要計數器的數值進行回彈,如代碼 3 所示。
代碼3
通過 POPBasicAnimation 的 timingFunction 我們定義了動畫的展現方式——漸入漸出。隨后通過 POPAnimatableProperty 來定義 POP 如何操作 Label 上的數值。
這里我們需要注意兩個函數,readBlock 和 writeBlock。readBlock 定義了動畫如何獲取要操作的屬性數值,writeBlock 定義了動畫如何修改要操作的屬性數值。在這兩個函數中,obj 就是我們的 Label,values 是動畫所操作的屬性數組,其值必須是 CGFloat。
你可能會問,什么是動畫所操作的屬性數組?回顧之前我們在 Decay Animation 中操作的 bounds 內容,可以看出 values[0]、values[1]、values[2]、values[3]分別對應了 CGRectMake(0, 0, 20.0, 20.0)的0、0、20.0、20.0。這里我們需要操作 Label 上顯示的文字,所以只需要一個 values[0]屬性即可。
通過 values[0]=[[obj description] floatValue]我們告訴 POP 如何獲取這個值。相應地,我們通過[obj setText: [NSString stringWithFormat:@"%.2f",values[0]]],告訴 POP 如何改變 Label 的屬性。
threshold 定義了動畫的變化閥值,如果這里使用1,那么我們就不會看到動畫執行時小數點后面的數字變化。
到這里,我們的 Counting Label 就完成了,是不是超簡單?
實戰
· PopUp 和 Decay Move
這個實例中,我將介紹一下如何將 Decay 動畫和用戶的手勢操作結合起來,實現一個推冰壺的效果。手勢的處理方式如代碼 4 所示。
代碼4
當用戶觸摸這個冰壺時,所有動畫會立刻停止,然后冰壺會跟隨用戶的手指移動。在用戶松開冰壺時,通過 [pan velocityInView:self.view]我們獲取了用戶手指移動的速率,在 addDecayPositionAnimationWithVelocity 中生成動畫,如代碼 5 所示。
代碼5
動畫生效后,冰壺就會在低摩擦的狀態下前進并逐漸停止。如果想增大摩擦力,則可以將速率乘以摩擦系數。
· Fly In
在這個實例中,我將介紹一下如何將兩個動畫相結合,實現一個像 Path 中卡片飛入的效果。如代碼 6 所示。
代碼6
第一個 Spring Animation 實現了卡片下落的效果,第二個 Basic Animation 實現了卡片的漸入效果,而最后的一個 Basic Animation 則實現了卡片傾斜的效果。
這里需要注意的是,我們使用了 duration 來定義 Basic Animation 的執行時間,并用 beginTime 來定義動畫的開始時間。beginTime 接受的是一個以秒為單位的時間,所以我們使用了 CACurrentMediaTime ()來獲取當前的時間,在此之上增加上了期望動畫延遲的時間。
· Transform
這個實例真的酷極了,我們將實現一個用戶點擊后播放按鈕轉換為進度條容器的變形效果。首先創建一個進度條,通過 lineCap lineWidth 調整進度條的樣式,然后使用 UIBezierPath 來定義進度條的走向,如代碼 7 所示。
代碼7
代碼 8 就是實現變形的代碼。從這段代碼不難看出,scale 和 bounds 的變化效果是一起進行的。這時,播放按鈕將縮小,然后改變外形成為進度條的容器。在變形結束后,將觸發進度條的動畫。
代碼8
這里我們使用 UIGraphicsBeginImageContext-WithOptions ()去開啟繪畫上下文,動畫結束后使用 UIGraphicsEndImageContext ()來清空繪畫的上下文。這兩個函數主要是影響畫板的大小。
作者周楷雯,廣州趣拼科技創始人。鐘情 iOS,粗通 Rails,偶爾做做設計,總是被有趣的人和美好的事所吸引。
<span id="shareA4" class="fl">
</span>