Rebound動畫框架簡單介紹
Android菜鳥一枚,有不對的地方希望大家指出,謝謝。
最近在接手了一個老項目,發現里面動畫框架用的是facebook中的Rebound框架,由於以前沒聽說過,放假時閑得蛋痛,看看了源碼,就順手寫這一篇吧。
寫了一個小Demo,具體效果如下:
代碼很簡單,這是xml布局:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context=".MainActivity" > <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/image" android:src="@drawable/a1" /> </RelativeLayout>
這是MainActivity:
package com.micro.mytest_button; import android.app.Activity; import android.os.Bundle; import android.view.Menu; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.widget.ImageView; import com.facebook.rebound.Spring; import com.facebook.rebound.SpringListener; import com.facebook.rebound.SpringSystem; import com.nineoldandroids.view.ViewHelper; public class MainActivity extends Activity { private ImageView image; private Spring spring; private final float mScale = 1.0f; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); image = (ImageView) findViewById(R.id.image); SpringSystem springSystem = SpringSystem.create(); spring = springSystem.createSpring(); spring.addListener(new SpringListener() { @Override public void onSpringUpdate(Spring spring) { float value = (float) spring.getCurrentValue(); float scale = 1f - (value * mScale); System.out.println("the value is " + value + "--the scale is --" + scale); ViewHelper.setScaleX(image, scale); ViewHelper.setScaleY(image, scale); } @Override public void onSpringEndStateChange(Spring spring) { } @Override public void onSpringAtRest(Spring spring) { } @Override public void onSpringActivate(Spring spring) { } }); image.setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: spring.setEndValue(1.0f); break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: spring.setEndValue(0.0f); break; } return true; } }); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.main, menu); return true; } }
現在主要是來分析下Rebound的源碼,看看里面到底怎么走的,用法很簡單(這是最簡單的用法),就三句話:
SpringSystem springSystem = SpringSystem.create(); spring = springSystem.createSpring(); spring.addListener(new SpringListener() {}
- 1
主要是創建了三個對象,SpringSystem/spring/SpringListener,對於動畫的效果,就是這三個玩意搞出來的。具體的模式如下:
SpringSystem:繼承自BaseSpringSystem,其中包含了Spring對象引用的容器,控制Spring對象的操作,存在一個SpringLooper(是一個抽象方法,只有start()/stop()方法),這也是facebook自定義的類,與android.os.Looper無關,只是模擬了android.os.Looper的方法,內存start(),end()方法。其構造方法:
public static SpringSystem create() { return new SpringSystem(AndroidSpringLooperFactory.createSpringLooper()); }
- 1
打開SpringSystem(SpringLooper sl)源碼:
public BaseSpringSystem(SpringLooper springLooper) { if (springLooper == null) { throw new IllegalArgumentException("springLooper is required"); } mSpringLooper = springLooper; mSpringLooper.setSpringSystem(this); }
- 1
現在再看AndroidSpringLooperFactory.createSpringLooper()源碼:
public static SpringLooper createSpringLooper() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { return ChoreographerAndroidSpringLooper.create(); } else { return LegacyAndroidSpringLooper.create(); } }
- 1
可以看到為了兼容JDK,高低版本創建的SpringLooper容器不經相同,這個不是考慮的重點。
現在來看第二句:
spring = springSystem.createSpring();
- 1
翻開SpringSytem.createSpring()源碼:
public Spring createSpring() { Spring spring = new Spring(this); registerSpring(spring); return spring; }
- 1
- 2
可以看到Spring類是一個對立的Java類,持有SpringSystem的引用,來看看Spring(SpringSystem ss)構造函數:
Spring(BaseSpringSystem springSystem) {
if (springSystem == null) { throw new IllegalArgumentException("Spring cannot be created outside of a BaseSpringSystem"); } mSpringSystem = springSystem; mId = "spring:" + ID++; setSpringConfig(SpringConfig.defaultConfig); }
再看setSpringConfig(SpringConfig.defaultConfig)源碼:
public Spring setSpringConfig(SpringConfig springConfig) { if (springConfig == null) { throw new IllegalArgumentException("springConfig is required"); } mSpringConfig = springConfig; return this; }
- 1
也只是初始化一些參數,並沒有有我們想要的源碼(這里的意思就是我點擊圖片時,它為什么會有動畫的意思),再看第三個方法吧:
spring.addListener(new SpringListener() {}
- 1
添加監聽器,用的多的人就會有感覺,這個也不會有關鍵代碼可以使我們圖片有縮放效果,那么問題來了,看到facebook寫的三句話,我們並沒有找到動畫縮放的想過函數啊,是不是有配置文件或者靜態/動態代碼塊呢??找了很久,沒有啊。那只有接下來再看image的代碼了,在看到Image的綁定的onTouch事件上,看到了這句話:
spring.setEndValue(1.0f);
- 1
啥也不說,進去看看再說吧:
public Spring setEndValue(double endValue) { if (mEndValue == endValue && isAtRest()) { return this; } mStartValue = getCurrentValue(); mEndValue = endValue; mSpringSystem.activateSpring(this.getId()); for (SpringListener listener : mListeners) { listener.onSpringEndStateChange(this); } return this; }
- 1
其他沒啥好看的,看到了這句話:
mSpringSystem.activateSpring(this.getId());
- 1
活了二十幾年,立馬感覺這句話有問題,進入看看:
void activateSpring(String springId) { Spring spring = mSpringRegistry.get(springId); if (spring == null) { throw new IllegalArgumentException("springId " + springId + " does not reference a registered spring"); } mActiveSprings.add(spring); if (getIsIdle()) { mIdle = false; mSpringLooper.start(); } }
解釋一下,剛才說的SpringSystem包含了Spring對象的引用,mSpringRegistry.get(springId)是找到SpringSystem中注冊的Spring, mActiveSprings.add(spring)是將該spring添加到活動的spring隊列中,那現在就看看這句話吧: mSpringLooper.start(),剛才說SpringLooper是一個抽象類,那好隨便找個其子類看看里面的方法吧,
private static class LegacyAndroidSpringLooper extends SpringLooper {}
- 1
剛才的兼容性代碼中,JELLY_BEAN(也就是4.1之前的代碼),那我們就看看mSpringLooper.start()是個什么鬼了吧:
private static class LegacyAndroidSpringLooper extends SpringLooper { private final Handler mHandler; private final Runnable mLooperRunnable; private boolean mStarted; private long mLastTime; /** * @return an Android spring looper using a new {@link Handler} instance */ public static SpringLooper create() { return new LegacyAndroidSpringLooper(new Handler()); } public LegacyAndroidSpringLooper(Handler handler) { mHandler = handler; mLooperRunnable = new Runnable() { @Override public void run() { if (!mStarted || mSpringSystem == null) { return; } long currentTime = SystemClock.uptimeMillis(); mSpringSystem.loop(currentTime - mLastTime); mHandler.post(mLooperRunnable); } }; } @Override public void start() { if (mStarted) { return; } mStarted = true; mLastTime = SystemClock.uptimeMillis(); mHandler.removeCallbacks(mLooperRunnable); mHandler.post(mLooperRunnable); } @Override public void stop() { mStarted = false; mHandler.removeCallbacks(mLooperRunnable); } }
對,沒錯我們看到了 mHandler.post(mLooperRunnable);金典的方法啊,那我們看看這mLooperRunnable方法里面在干嘛吧,
long currentTime = SystemClock.uptimeMillis(); mSpringSystem.loop(currentTime - mLastTime);
廢話不多說,立馬去找我們想要看到的SpringSystem.loop方法了:
public void loop(double ellapsedMillis) { for (SpringSystemListener listener : mListeners) { listener.onBeforeIntegrate(this); } advance(ellapsedMillis); if (mActiveSprings.isEmpty()) { mIdle = true; } for (SpringSystemListener listener : mListeners) { listener.onAfterIntegrate(this); } if (mIdle) { mSpringLooper.stop(); } }
對了,我們好像看到了advance(ellapsedMillis)啊,這個看起來比較吊,不說了進去看看再說啊:
void advance(double deltaTime) {
for (Spring spring : mActiveSprings) { // advance time in seconds if (spring.systemShouldAdvance()) { spring.advance(deltaTime / 1000.0); } else { mActiveSprings.remove(spring); } } }
尼瑪啊,我看到了
spring.advance(deltaTime / 1000.0);
哈哈,我感覺快要找到了,不說,趕快進去找:
void advance(double realDeltaTime) { boolean isAtRest = isAtRest(); if (isAtRest && mWasAtRest) { /* begin debug Log.d(TAG, "bailing out because we are at rest:" + getName()); end debug */ return; } // clamp the amount of realTime to simulate to avoid stuttering in the UI. We should be able // to catch up in a subsequent advance if necessary. double adjustedDeltaTime = realDeltaTime; if (realDeltaTime > MAX_DELTA_TIME_SEC) { adjustedDeltaTime = MAX_DELTA_TIME_SEC; } /* begin debug long startTime = System.currentTimeMillis(); int iterations = 0; end debug */ mTimeAccumulator += adjustedDeltaTime; double tension = mSpringConfig.tension; double friction = mSpringConfig.friction; double position = mCurrentState.position; double velocity = mCurrentState.velocity; double tempPosition = mTempState.position; double tempVelocity = mTempState.velocity; double aVelocity, aAcceleration; double bVelocity, bAcceleration; double cVelocity, cAcceleration; double dVelocity, dAcceleration; double dxdt, dvdt; // iterate over the true time while (mTimeAccumulator >= SOLVER_TIMESTEP_SEC) { /* begin debug iterations++; end debug */ mTimeAccumulator -= SOLVER_TIMESTEP_SEC; if (mTimeAccumulator < SOLVER_TIMESTEP_SEC) { // This will be the last iteration. Remember the previous state in case we need to // interpolate mPreviousState.position = position; mPreviousState.velocity = velocity; } // Perform an RK4 integration to provide better detection of the acceleration curve via // sampling of Euler integrations at 4 intervals feeding each derivative into the calculation // of the next and taking a weighted sum of the 4 derivatives as the final output. // This math was inlined since it made for big performance improvements when advancing several // springs in one pass of the BaseSpringSystem. // The initial derivative is based on the current velocity and the calculated acceleration aVelocity = velocity; aAcceleration = (tension * (mEndValue - tempPosition)) - friction * velocity; // Calculate the next derivatives starting with the last derivative and integrating over the // timestep tempPosition = position + aVelocity * SOLVER_TIMESTEP_SEC * 0.5; tempVelocity = velocity + aAcceleration * SOLVER_TIMESTEP_SEC * 0.5; bVelocity = tempVelocity; bAcceleration = (tension * (mEndValue - tempPosition)) - friction * tempVelocity; tempPosition = position + bVelocity * SOLVER_TIMESTEP_SEC * 0.5; tempVelocity = velocity + bAcceleration * SOLVER_TIMESTEP_SEC * 0.5; cVelocity = tempVelocity; cAcceleration = (tension * (mEndValue - tempPosition)) - friction * tempVelocity; tempPosition = position + cVelocity * SOLVER_TIMESTEP_SEC; tempVelocity = velocity + cAcceleration * SOLVER_TIMESTEP_SEC; dVelocity = tempVelocity; dAcceleration = (tension * (mEndValue - tempPosition)) - friction * tempVelocity; // Take the weighted sum of the 4 derivatives as the final output. dxdt = 1.0/6.0 * (aVelocity + 2.0 * (bVelocity + cVelocity) + dVelocity); dvdt = 1.0/6.0 * (aAcceleration + 2.0 * (bAcceleration + cAcceleration) + dAcceleration); position += dxdt * SOLVER_TIMESTEP_SEC; velocity += dvdt * SOLVER_TIMESTEP_SEC; } mTempState.position = tempPosition; mTempState.velocity = tempVelocity; mCurrentState.position = position; mCurrentState.velocity = velocity; if (mTimeAccumulator > 0) { interpolate(mTimeAccumulator / SOLVER_TIMESTEP_SEC); } // End the spring immediately if it is overshooting and overshoot clamping is enabled. // Also make sure that if the spring was considered within a resting threshold that it's now // snapped to its end value. if (isAtRest() || (mOvershootClampingEnabled && isOvershooting())) { // Don't call setCurrentValue because that forces a call to onSpringUpdate mStartValue = mEndValue; mCurrentState.position = mEndValue; setVelocity(0); isAtRest = true; } /* begin debug long endTime = System.currentTimeMillis(); long elapsedMillis = endTime - startTime; Log.d(TAG, "iterations:" + iterations + " iterationTime:" + elapsedMillis + " position:" + mCurrentState.position + " velocity:" + mCurrentState.velocity + " realDeltaTime:" + realDeltaTime + " adjustedDeltaTime:" + adjustedDeltaTime + " isAtRest:" + isAtRest + " wasAtRest:" + mWasAtRest); end debug */ // NB: do these checks outside the loop so all listeners are properly notified of the state // transition boolean notifyActivate = false; if (mWasAtRest) { mWasAtRest = false; notifyActivate = true; } boolean notifyAtRest = false; if (isAtRest) { mWasAtRest = true; notifyAtRest = true; } for (SpringListener listener : mListeners) { // starting to move if (notifyActivate) { listener.onSpringActivate(this); } // updated listener.onSpringUpdate(this); // coming to rest if (notifyAtRest) { listener.onSpringAtRest(this); } } }
- 1
代碼精簡一下,就變成這樣了:
void advance(double realDeltaTime) { boolean isAtRest = isAtRest(); if (isAtRest && mWasAtRest) { return; } double adjustedDeltaTime = realDeltaTime; if (realDeltaTime > MAX_DELTA_TIME_SEC) { adjustedDeltaTime = MAX_DELTA_TIME_SEC; } mTimeAccumulator += adjustedDeltaTime; while (mTimeAccumulator >= SOLVER_TIMESTEP_SEC) { /** inner change **/ } mTempState.position = tempPosition; mTempState.velocity = tempVelocity; mCurrentState.position = position; mCurrentState.velocity = velocity; for (SpringListener listener : mListeners) { // starting to move if (notifyActivate) { listener.onSpringActivate(this); } // updated listener.onSpringUpdate(this); // coming to rest if (notifyAtRest) { listener.onSpringAtRest(this); } } }
- 1
這下看得十分清楚了吧,在while循環里面變換之后,我們得到了 :
mCurrentState.position = position; mCurrentState.velocity = velocity;
然后在接口回調中我們使用到了這些參數,
spring.addListener(new SpringListener() { @Override public void onSpringUpdate(Spring spring) { float value = (float) spring.getCurrentValue(); float scale = 1f - (value * mScale); ViewHelper.setScaleX(image, scale); ViewHelper.setScaleY(image, scale); } @Override public void onSpringEndStateChange(Spring spring) {} @Override public void onSpringAtRest(Spring spring) {} @Override public void onSpringActivate(Spring spring) {} });
就這樣我么的變換終於就成功了,我們也就走通了相關的流程了,是不是很好玩呢???主要是facebook這個Rebound框架很小,類很少,我們可以在很短的時間對它通讀,是不是感覺到分析源碼很好玩呢?我也是一名android菜鳥,很多時候不敢去分析源碼,很為很多的東西都看不懂看不透,那就慢慢來吧,先從簡單的開始吧,呵呵。
最后,代碼地址