1. 程式人生 > >iOS之POP動畫使用和實戰

iOS之POP動畫使用和實戰

image
- POP是一個來自於Facebook,在iOS與OSX上通用的極具擴充套件性的動畫引擎。它在基本的靜態動畫的基礎上增加的彈簧動畫與衰減動畫,使之能創造出更真實更具物理性的互動動畫。
- Pop Animation在使用上和Core Animation很相似,都涉及Animation物件以及Animation的載體的概念
- 關於Core Animation的相關詳解, 可參考我的上一篇文章Core Animation(核心動畫)
- 不同的是Core Animation的載體只能是CALayer,而Pop Animation可以是任意基於NSObject的物件
- POP 使用 Objective-C++

編寫,Objective-C++ 是對 C++ 的擴充套件

一. POP自我介紹

  • POP 目前由四部分組成:1. Animations;2. Engine;3. Utility;4. WebCore。
  • POP 動畫極為流暢,主要在於Enimator 裡,POP 通過 CADisplayLink 高達 60 FPS 的特性,打造了一個遊戲級的動畫引擎。
  • CADisplayLink 是類似 NSTimer 的定時器,不同之處在於,NSTimer 用於我們定義任務的執行週期、資料的更新週期,他的執行受到 CPU 的阻塞影響,而 CADisplayLink 則用於定義畫面的重繪、動畫的演變,他的執行基於 frames 的間隔。
    • 通過 CADisplayLink,Apple 允許你將 App 的重繪速度設定到和螢幕重新整理頻率一致,由此你可以獲得非常流暢的互動動畫,這項技術的應用在遊戲中非常常見,著名的 Cocos-2D 也應用了這個重要的技術。
    • WebCore 裡包含了一些從 Apple 的開源的網頁渲染引擎裡拿出的原始檔,與 Utility 裡的元件一併,提供了 POP 的各項複雜計算的基本支援

二. POP引數介紹

POP預設支援三種動畫,但同時也支援自定義動畫
  • POPBasicAnimation //基礎動畫
  • POPSpringAnimation //彈簧動畫
  • POPDecayAnimation //衰減動畫
  • POPCustomAnimation //自定義動畫

1、相關屬性介紹

1-1、屬性簡單介紹

  • POP動畫大部分屬性和CoreAnimation(核心動畫)的含義和用法一樣
  • 每種動畫的特殊屬性會在下文中繼續介紹

1-2、動畫可配置屬性

  • CALayer層各屬性(比較簡單,就不加註釋了)
/**
 Common CALayer property names.
 */
extern NSString * const kPOPLayerBackgroundColor;
extern NSString * const kPOPLayerBounds;
extern NSString * const kPOPLayerCornerRadius;
extern NSString * const kPOPLayerBorderWidth;
extern NSString * const kPOPLayerBorderColor;
extern NSString * const kPOPLayerOpacity;
extern NSString * const kPOPLayerPosition;
extern NSString * const kPOPLayerPositionX;
extern NSString * const kPOPLayerPositionY;
extern NSString * const kPOPLayerRotation;
extern NSString * const kPOPLayerRotationX;
extern NSString * const kPOPLayerRotationY;
extern NSString * const kPOPLayerScaleX;
extern NSString * const kPOPLayerScaleXY;
extern NSString * const kPOPLayerScaleY;
extern NSString * const kPOPLayerSize;
extern NSString * const kPOPLayerSubscaleXY;
extern NSString * const kPOPLayerSubtranslationX;
extern NSString * const kPOPLayerSubtranslationXY;
extern NSString * const kPOPLayerSubtranslationY;
extern NSString * const kPOPLayerSubtranslationZ;
extern NSString * const kPOPLayerTranslationX;
extern NSString * const kPOPLayerTranslationXY;
extern NSString * const kPOPLayerTranslationY;
extern NSString * const kPOPLayerTranslationZ;
extern NSString * const kPOPLayerZPosition;
extern NSString * const kPOPLayerShadowColor;
extern NSString * const kPOPLayerShadowOffset;
extern NSString * const kPOPLayerShadowOpacity;
extern NSString * const kPOPLayerShadowRadius;
  • UIVIew層
/**
 Common UIView property names.
 */
extern NSString * const kPOPViewAlpha;
extern NSString * const kPOPViewBackgroundColor;
extern NSString * const kPOPViewBounds;
extern NSString * const kPOPViewCenter;
extern NSString * const kPOPViewFrame;
extern NSString * const kPOPViewScaleX;
extern NSString * const kPOPViewScaleXY;
extern NSString * const kPOPViewScaleY;
extern NSString * const kPOPViewSize;
extern NSString * const kPOPViewTintColor;
  • 其他層檢視層
/**
 Common UINavigationBar property names.
 */
extern NSString * const kPOPNavigationBarBarTintColor;

/**
 Common UIToolbar property names.
 */
extern NSString * const kPOPToolbarBarTintColor;

/**
 Common UITabBar property names.
 */
extern NSString * const kPOPTabBarBarTintColor;

/**
 Common UILabel property names.
 */
extern NSString * const kPOPLabelTextColor;

以上僅僅列出了常用的一些屬性,更多控制元件/更多參考框架裡面類
POPAnimatableProperty.h

1-3、POPBasicAnimation可配置的屬性與預設值為

POPBasicAnimation *basic = [POPBasicAnimation linearAnimation];
basic.fromValue = @(0);//0開始    basic.toValue = @(3*60);//180秒後結束
basic.duration = 3*60;//持續3分鐘
[lab pop_addAnimation:basic forKey:nil];
let basic1 = POPBasicAnimation(propertyNamed: kPOPLayerPositionX)
basic1?.fromValue = redView.layer.position.x
basic1?.toValue = 300
basic1?.beginTime = CFTimeInterval() + 1.0
redView.pop_add(basic1, forKey: "position.x")

三. POPBasicAnimation基礎動畫

1. 先看一下效果, 其動畫效果如下

image

2. 示例程式碼

let basic1 = POPBasicAnimation(propertyNamed: kPOPLayerPositionX)
basic1?.toValue = 300
//開始時間
basic1?.beginTime = CFTimeInterval() + 1.0
redView.pop_add(basic1, forKey: "position.x")

3. 可以看到,新增一個動畫最少僅需三步

  • 1)定義一個animation物件,並指定對應的動畫屬性(kPOPLayerPositionX
  • 2)設定初始值結束值(初始值可以不指定,會預設從當前值開始)
  • 3)新增到想產生動畫的物件上

4. Core Animation 和 POP 執行動畫對比

  • 由於 POP 是基於定時器定時重新整理新增動畫的原理,那麼如果將動畫庫執行在主執行緒上,會由於執行緒阻塞的問題導致動畫效果出現卡頓、不流暢的情況。
  • 更為關鍵的是,你不能將動畫效果放在子執行緒,因為你不能將對 view 和 layer 的操作放到主執行緒之外
  • POP 受主執行緒阻塞的影響很大,在使用過程中,應避免在有可能發生主執行緒阻塞的情況下使用 POP ,避免製作卡頓的動畫效果,產生不好的使用者體驗

四. POPSpringAnimation彈性動畫

1. 屬性介紹

  • velocity: 設定動畫開始速度
  • springBounciness: 振幅, 可以設定的範圍是0-20,預設為4。值越大振動的幅度越大
  • springSpeed: 速度, 可以設定的範圍是0-20,預設為12.值越大速度越快,結束的越快
  • dynamicsMass: 質量, 質量越大,動畫的速度越慢,振動的幅度越大,結束的越慢
  • dynamicsTension: 拉力 拉力越大,動畫的速度越快,結束的越快
  • dynamicsFriction: 摩擦力, 摩擦力越大,動畫的速度越慢,振動的幅度越小。

注意: 以上的六個屬性中一般只會設定springBouncinessspringSpeed, 如有特殊需求才會設定其他屬性

2. 程式碼示例

let spring = POPSpringAnimation(propertyNamed: kPOPViewScaleXY)
//注意: 這裡改變的是x和y的比例,引數賦值也要傳兩個; 若只需要其中一個,則可設定
//`spring?.fromValue = 0.4`即可
spring?.fromValue = CGSize(width: 0.3, height: 0.3)
spring?.toValue = CGSize(width: 2, height: 2)
spring?.springSpeed = 5
spring?.springBounciness = 15
lightBlue.pop_add(spring, forKey: "scale")

五. POPDecayAnimation

  • POPDecayAnimation提供一個過阻尼效果(其實Spring是一種欠阻尼效果)可以實現類似UIScrollView的滑動衰減效果(是的你可以靠它來自己實現一個UIScrollView

屬性介紹
- deceleration (負加速度, 衰減係數(越小則衰減得越快)) 是一個你會很少用到的值,預設是就是我們地球的 0.998,如果你開發APP給火星人用,那麼這個值你使用 0.376 會更合適
- velocity 也是必須和你操作的屬性有相同的結構,如果你操作的是 bounds, 傳CGRect型別;如果 velocity 是負值,那麼就會反向遞減

程式碼示例

let decay = POPDecayAnimation(propertyNamed: kPOPViewSize)
decay?.velocity = CGSize(width: 300, height: pictureBtn.frame.height)
//延遲1秒後執行
decay?.beginTime = CACurrentMediaTime() + 1.0
pictureBtn.pop_add(decay, forKey: "size")

六. 自定義屬性

POP預設支援的三種動畫都繼承自POPPropertyAnimation, POPPropertyAnimation中定義了一個叫property的屬性(之前沒有用到它是因為POP根據不同的預設動畫屬性幫你生成了預設的property這個屬性則是用來驅動POP的動畫效果中的重要一環

1. 實力模組

if let proper = POPAnimatableProperty.property(withName: "prop", initializer: { (prop) in
    guard let prop = prop else { return }
    //read
    prop.readBlock = { (obj, values) in

    }

    //write
    prop.writeBlock = {(obj, values) in

    }
    prop.threshold = 0.01

}) as? POPAnimatableProperty {
    anim.property = proper
}

2. 屬性介紹

其組成就是一個readBlock一個writeBlock和一個threashold
- readBlock告訴POP當前的屬性值
- writeBlock中修改變化後的屬性值
- threashold決定了動畫變化間隔的閾值 值越大writeBlock的呼叫次數越少

POPAnimatableProperty其實是POP中一個比較重要的東西 像上面提到的POP自帶的動畫屬性 檢視原始碼可以看到也只是POP自動幫你設定好了POPAnimatableProperty而已 其作用就是當動畫的某個時間片被觸發時 告訴系統如何根據當前時間片做出變化

還是以一個實際的例子來說明如何使用自定義屬性 比如我們要實現一個像系統的時鐘APP裡秒錶計時的一個效果

計時器效果

3. 完整程式碼示例

if let proper = POPAnimatableProperty.property(withName: "prop", initializer: { (prop) in
    guard let prop = prop else { return }
    //read
    prop.readBlock = { (obj, values) in
        guard let array = values else { return }
        print(array[0])
    }

    //write
    prop.writeBlock = {(obj, values) in
        guard let button = obj as? UIButton, let array = values else { return }
        let value = array[0]
        button.setTitle(String(format: "%02d:%02d:%02d", Int(value / 60), Int(value.truncatingRemainder(dividingBy: 60)), Int((value * 100).truncatingRemainder(dividingBy: 100))), for: .normal)
    }
    prop.threshold = 0.01

}) as? POPAnimatableProperty {
    if let popBasic = POPBasicAnimation.linear() {
        //秒錶用線性的時間函式初始化
        popBasic.property = proper
        popBasic.fromValue = 0 //從0開始
        popBasic.toValue = 18  //到18秒
        popBasic.duration = 18 //持續18秒
        popBasic.beginTime = CACurrentMediaTime() + 2 //延遲2秒開始
        pictureBtn.pop_add(popBasic, forKey: "linear")
    }
}

4. 注意:

  • 在Swift4.0版本(4.0之前版本未知)中,初始化出來的物件都是可選型別
  • POP官方的建議是新增if條件判斷,詳情可到GitHub上檢視示例
  • 正如上段程式碼所示: 閉包中涉及的可選型別都添加了guard判斷

七. 類似微博中間釋出按鈕彈出動畫

先看一下效果吧

Simulator Screen Shot - iPhone X - 2017-10-17 at 19.51.48.png

動畫分為兩個部分

  • 中間六個按鈕依次執行動畫彈出
  • 上面標題圖片最後動畫落下

下面來看一下部分的核心程式碼

1. 六個按鈕的彈出和消失動畫
for i in 0..<titles.count {
    let button = BaseButton()
    button.setTitle(titles[i], for: .normal)
    button.setImage(UIImage(named: images[i]), for: .normal)
    button.addTarget(self, action: #selector(buttonClick(button:)), for: .touchUpInside)
    addSubview(button)

    //計算X/Y
    let row = i / maxCols
    let col = i % maxCols
    let buttonX = btnStsrtX + CGFloat(col) * (xMargin + buttonW)
    let buttonEndY = btnStartY + CGFloat(row) * buttonH
    let buttonStartY = buttonEndY - kScreenHeight

    //按鈕動畫
    let popSpring = POPSpringAnimation(propertyNamed: kPOPViewFrame)
    popSpring?.fromValue = CGRect(x: buttonX, y: buttonStartY, width: buttonW, height: buttonH)
    popSpring?.toValue = CGRect(x: buttonX, y: buttonEndY, width: buttonW, height: buttonH)
    popSpring?.springBounciness = kSpringFactor
    popSpring?.springSpeed = kSpringFactor
    popSpring?.beginTime = CACurrentMediaTime() + kAnimationDelay * Double(i)
    button.pop_add(popSpring, forKey: "spring")
}
2. 最上部分標語的彈出和消失
//z執行動畫
let imagePOP = POPSpringAnimation(propertyNamed: kPOPViewCenter)
imagePOP?.fromValue = CGPoint(x: kScreenWidth * 0.5, y: 0.2 * kScreenHeight - kScreenHeight)
imagePOP?.toValue = CGPoint(x: kScreenWidth * 0.5, y: 0.2 * kScreenHeight)
imagePOP?.springSpeed = kSpringFactor
imagePOP?.springBounciness = kSpringFactor
imagePOP?.beginTime = CACurrentMediaTime() + Double(btnCount) * kAnimationDelay
imagePOP?.completionBlock = { popAnim, finished in
    //所有動畫執行完畢,回覆View點選事件
    kRootView?.isUserInteractionEnabled = true
    self.isUserInteractionEnabled = true
}
topImage.pop_add(imagePOP, forKey: nil)

以上是類似微博動畫的部分核心程式碼, 具體程式碼詳見GitHub專案, 喜歡請star

  • 摺疊圖片
  • 音量震動條
  • 活動指示器
  • 微博動畫
  • 倒計時-計時器
  • 類似QQ資訊條數的粘性動畫
  • 類似雷達-水波紋動畫

注: 專案持續更新中……