Facebook Rebound 彈性動畫庫 源碼分析
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從mStartValue
到mEndValue
進行運動,內部維護了當前狀態、前值狀態,以及臨時狀態,每個狀態由通過位置和速度來描述,而運動的推進邏輯則在
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
的狀態,實際調用了BaseSpringSystem
的loop
方法:
/**
* 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);
}
會在SpringSystem
的loop
方法開始和結束時候調用onBeforeIntegrate
以及onAfterIntegrate
,比如可以在所有Spring loop完之後檢查它們的值,並進行速度限制,暫停等操作,相對於綁定到Spring
的SpringListener
,這個更全局一些。
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 彈性動畫庫 源碼分析