1. 程式人生 > >Facebook Rebound 彈性動畫庫 源碼分析

Facebook Rebound 彈性動畫庫 源碼分析

管理 介紹 doc 結束 ace animation ont system main

Rebound源碼分析

讓動畫不再僵硬:Facebook Rebound Android動畫庫介紹一文中介紹了rebound這個庫。

對於想體驗一下rebound的效果,又懶得clone和編譯代碼的,這裏提供一個demo apk。

今天看到了tumblr發布了基於rebound的Backboard,本想直接分析一下Backboard對rebound做了些什麽,不過考慮到rebound還沒有仔細分析過,所以這裏做一下源碼分析。

對外部來說,首先接觸的就是SpringSystem了,但在說它之前,先讓我們看看spring是什麽。

Spring

Spring通過可設置的摩擦力(Friction)和張力(tension)實現了胡克定律,通過代碼模擬了物理場景:

private static class PhysicsState {
  double position;
  double velocity;
}

private final PhysicsState mCurrentState = new PhysicsState();
private final PhysicsState mPreviousState = new PhysicsState();
private final PhysicsState mTempState = new PhysicsState();
private double mStartValue;
private double mEndValue;

每個spring從mStartValuemEndValue進行運動,內部維護了當前狀態、前值狀態,以及臨時狀態,每個狀態由通過位置和速度來描述,而運動的推進邏輯則在

void advance(double realDeltaTime)
  • 1

advance方法中,SpringSystem會遍歷由其管理的所有Spring實例,對它們進行advance

SpringListener

每個Spring內部都維護著一個SpringListener數組,這也是我們經常會需要去實現的一個接口:

public interface SpringListener {
  void onSpringUpdate(Spring spring);
  void onSpringAtRest(Spring spring);
  void onSpringActivate(Spring spring);
  void onSpringEndStateChange(Spring spring);
}

可以看到create方法裏面默認給了一個SpringLooper的工廠類創建實例(內部根據系統版本是否>=3.0返回了不同的子類實例),而SpringLooper顧名思義是一個Looper,做的就是不斷地更新SpringSystem的狀態,實際調用了BaseSpringSystemloop方法:

/**
 * loop the system until idle
 * @param elapsedMillis elapsed milliseconds
 */
public void loop(double elapsedMillis) {
  for (SpringSystemListener listener : mListeners) {
    listener.onBeforeIntegrate(this);
  }
  advance(elapsedMillis);
  if (mActiveSprings.isEmpty()) {
    mIdle = true;
  }
  for (SpringSystemListener listener : mListeners) {
    listener.onAfterIntegrate(this);
  }
  if (mIdle) {
    mSpringLooper.stop();
  }
}

即通過每次elapse的時間,來把system往前advance(有點類似遊戲裏,每一幀的運動,如果不夠快就會掉幀,這裏對應地,elapsedMillis則可能會很大)。

大部分的邏輯其實在BaseSpringSystem:

public class BaseSpringSystem {

  private final Map<String, Spring> mSpringRegistry = new HashMap<String, Spring>();
  private final Set<Spring> mActiveSprings = new CopyOnWriteArraySet<Spring>();
  private final SpringLooper mSpringLooper;
  private final CopyOnWriteArraySet<SpringSystemListener> mListeners = new CopyOnWriteArraySet<SpringSystemListener>();
  private boolean mIdle = true;
  • 1

mSpringRegistry保存了所有由該SpringSystem管理的Spring實例,鍵值String則是Spring內的一個自增id,每個Spring實例的id都會不同。通過createSpring創建的Spring實例都會直接被加到該HashMap。

mActiveSprings內放的是被激活的Spring,實際在調用Spring.Java:

public Spring setCurrentValue(double currentValue, boolean setAtRest);
public Spring setEndValue(double endValue);
public Spring setVelocity(double velocity);

三個方法的時候才會進行激活,且在實際loop過程中,也只會對激活的Spring進行advance。

mSpringLooper是該SpringSystem綁定的Looper。

mListeners是註冊在該SpringSystem上的SpringSystemListener

public interface SpringSystemListener {
  void onBeforeIntegrate(BaseSpringSystem springSystem);
  void onAfterIntegrate(BaseSpringSystem springSystem);
}

會在SpringSystemloop方法開始和結束時候調用onBeforeIntegrate以及onAfterIntegrate,比如可以在所有Spring loop完之後檢查它們的值,並進行速度限制,暫停等操作,相對於綁定到SpringSpringListener,這個更全局一些。

SpringChain

顧名思義,SpringChain就是連鎖Spring,由數個Spring結合而成,且兩兩相連,可以用來做一些連鎖的效果,比如數個圖片之間的牽引效果。

每個SpringChain都會有一個control spring來作為帶頭大哥,在鏈中前後的Spring都會被他們的前任所拉動。比如我們有 1 2 3 4 5五個Spring,選擇3作為帶頭大哥,則3開始運動後,會分別拉動2和4,然後2會拉1,4則去拉動5。

  private SpringChain(
      int mainTension,
      int mainFriction,
      int attachmentTension,
      int attachmentFriction) {
    mMainSpringConfig = SpringConfig.fromOrigamiTensionAndFriction(mainTension, mainFriction);
    mAttachmentSpringConfig =
        SpringConfig.fromOrigamiTensionAndFriction(attachmentTension, attachmentFriction);
    registry.addSpringConfig(mMainSpringConfig, "main spring " + id++);
    registry.addSpringConfig(mAttachmentSpringConfig, "attachment spring " + id++);
  }
  • 1
  • 2

即ControlSpring摩擦力和張力都會相對小一些。

SpringChain本身實現了SpringListener,並使用那些接口來進行整個chain的更新。

@Override
public void onSpringUpdate(Spring spring) {
    // 獲得control spring的索引,並更新前後Spring的endValue,從而觸發連鎖影響
    int idx = mSprings.indexOf(spring);
    SpringListener listener = mListeners.get(idx);
    int above = -1;
    int below = -1;
    if (idx == mControlSpringIndex) {
        below = idx - 1;
        above = idx + 1;
    } else if (idx < mControlSpringIndex) {
        below = idx - 1;
    } else if (idx > mControlSpringIndex) {
        above = idx + 1;
    }
    if (above > -1 && above < mSprings.size()) {
        mSprings.get(above).setEndValue(spring.getCurrentValue());
    }
    if (below > -1 && below < mSprings.size()) {
        mSprings.get(below).setEndValue(spring.getCurrentValue());
    }
    listener.onSpringUpdate(spring);
}

@Override
public void onSpringAtRest(Spring spring) {
    int idx = mSprings.indexOf(spring);
    mListeners.get(idx).onSpringAtRest(spring);
}

@Override
public void onSpringActivate(Spring spring) {
    int idx = mSprings.indexOf(spring);
    mListeners.get(idx).onSpringActivate(spring);
}

@Override
public void onSpringEndStateChange(Spring spring) {
    int idx = mSprings.indexOf(spring);
    mListeners.get(idx).onSpringEndStateChange(spring);
}
  • 1

通常我們想要這個SpringChain進行運動會調用mSpringChain.setControlSpringIndex(0).getControlSpring().setEndValue(1);

ControlSpring便會開始運動,並調用到SpringChain作為SpringListener的那些方法,進而整個系統作為一個鏈開始運動。

SpringConfiguratorView

SpringConfiguratorView繼承了FrameLayout,如果體驗過demo apk的同學,應該註意到屏幕底下上拉可以對Spring的參數進行配置,這就是由SpringConfiguratorView做的了。

AnimationQueue

同樣是用來做連鎖動畫的,不過Backboard沒有用到這個,Facebook自己的例子也沒有用過該類,以前做動畫的時候用過這個,結果貌似是有什麽坑,最後改成了SpringChain去實現。

AnimationQueue本身和Rebound沒有任何關系,內部定義了接口

public interface Callback {
    void onFrame(Double value);
}

原理倒是有點像rebound。由於和rebound本身沒關系,這裏就不多說了。

Facebook Rebound 彈性動畫庫 源碼分析