Facebook Rebound 弹性动画库 源码分析
设计的时候老是闲动画太生硬,于是找到了这个弹性动画。这个弹性动画是facebook开源的,Rebound项目地址:https://github.com/facebook/rebound.git。来看下开源的DEMO提供效果。
使用方法
publicclass MainActivity extendsActivity {
privatefinal BaseSpringSystem mSpringSystem = SpringSystem.create();
privatefinal ExampleSpringListener mSpringListener = new ExampleSpringListener();
private FrameLayout mRootView;
private Spring mScaleSpring;
private View mImageView;
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mRootView = (FrameLayout) findViewById(R.id.root_view);
mImageView = mRootView.findViewById(R.id.image_view);
// Create the animation spring.
mScaleSpring = mSpringSystem.createSpring();
// Add an OnTouchListener to the root view.
mRootView.setOnTouchListener(new View.OnTouchListener() {
@Override
publicbooleanonTouch(View v, MotionEvent event){
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// When pressed start solving the spring to 1.
mScaleSpring.setEndValue(1);
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
// When released start solving the spring to 0.
mScaleSpring.setEndValue(0);
break;
}
returntrue;
}
});
}
@Override
public void onResume(){
super.onResume();
// Add a listener to the spring when the Activity resumes.
mScaleSpring.addListener(mSpringListener);
}
@Override
publicvoidonPause(){
super.onPause();
// Remove the listener to the spring when the Activity pauses.
mScaleSpring.removeListener(mSpringListener);
}
privateclass ExampleSpringListener extendsSimpleSpringListener {
@Override
publicvoidonSpringUpdate(Spring spring){
// On each update of the spring value, we adjust the scale of the image view to match the
// springs new value. We use the SpringUtil linear interpolation function mapValueFromRangeToRange
// to translate the spring's 0 to 1 scale to a 100% to 50% scale range and apply that to the View
// with setScaleX/Y. Note that rendering is an implementation detail of the application and not
// Rebound itself. If you need Gingerbread compatibility consider using NineOldAndroids to update
// your view properties in a backwards compatible manner.
float mappedValue = (float) SpringUtil.mapValueFromRangeToRange(spring.getCurrentValue(), 0, 1, 1, 0.5);
mImageView.setScaleX(mappedValue);
mImageView.setScaleY(mappedValue);
}
}
}
源码解析
1.输出值分析
public void onSpringUpdate(Spring spring){
Log.v("tag" , spring.getCurrentValue())
}
输出值 主要看这里 我们将 回调的输出值输入Excle表格 在做成图表。可以看出,如果将这个值放入 位移变化 很明显可以形成一个弹性的小球的样子。大家可以想象。
2.触发而触发输出值,看如代码。
case MotionEvent.ACTION_DOWN:
// When pressed start solving the spring to 1.
mScaleSpring.setEndValue(1);
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
// When released start solving the spring to 0.
mScaleSpring.setEndValue(0);
break;
在由上面的代码,看出触发回调的是 mScaleSpring.setEndValue(1);
我们将更近代码,进入Spring类,
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;
}
这里执行一些回调,主要看 mSpringSystem.activateSpring(this.getId());
void activateSpring(String springId) {
Spring spring = mSpringRegistry.get(springId);
if (spring == null) {
thrownew IllegalArgumentException("springId " + springId + " does not reference a registered spring");
}
mActiveSprings.add(spring);
if (getIsIdle()) {
mIdle = false;
mSpringLooper.start();
}
}
这里 SpringSystem将当前的spring取出 加入到mActiveSprings列表中,然后mSpringLooper.start(); 这里mSpringLooper 你可以先想象成一个简单的loop循环。在这个loop就开始疯狂的输出算法每隔一段时间的值了。我们找到这个start的实现就一清二楚了。当然loop是怎么生成的我们要看下SpringSystem的构造,
3.SpringSystem 和loop的形成
public class SpringSystem extendsBaseSpringSystem {
/**
* Create a new SpringSystem providing the appropriate constructor parameters to work properly
* in an Android environment.
* @return the SpringSystem
*/
public static SpringSystem create() {
returnnew SpringSystem(AndroidSpringLooperFactory.createSpringLooper());
}
private SpringSystem(SpringLooper springLooper) {
super(springLooper);
}
}
瞧loop就是它了AndroidSpringLooperFactory.createSpringLooper()跟进。
publicstaticSpringLooper createSpringLooper(){
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
return ChoreographerAndroidSpringLooper.create();
} else {
return LegacyAndroidSpringLooper.create();
}
}
我们选择的是
LegacyAndroidSpringLooper.create();
publicLegacyAndroidSpringLooper(Handler handler){
mHandler = handler;
mLooperRunnable = new Runnable() {
@Override
publicvoidrun(){
if (!mStarted || mSpringSystem == null) {
return;
}
long currentTime = SystemClock.uptimeMillis();
mSpringSystem.loop(currentTime - mLastTime);
mLastTime = currentTime;
mHandler.post(mLooperRunnable);
}
};
}
@Override
publicvoidstart(){
if (mStarted) {
return;
}
mStarted = true;
mLastTime = SystemClock.uptimeMillis();
mHandler.removeCallbacks(mLooperRunnable);
mHandler.post(mLooperRunnable);
}
@Override
publicvoidstop(){
mStarted = false;
mHandler.removeCallbacks(mLooperRunnable);
}
这里就 循环的post,如果没有stop的话,就是个死循环了。而循环里执行的内如无非是,mSpringSystem.loop(currentTime - mLastTime); 那么我们回到SpringSystem 看下 loop函数
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();
}
}
好家伙这里也什么都没有做 ,也只是一些判断和回调 但是这里的判断可 重要了 这里判断了循环停止的条件– 在最后面(等下分析)。 这里主要看下advance(elapsedMillis);这个算法。
voidadvance(double deltaTime){
for (Spring spring : mActiveSprings) {
// advance time in seconds
if (spring.systemShouldAdvance()) {
spring.advance(deltaTime / 1000.0);
} else {
mActiveSprings.remove(spring);
}
}
}
这样整个系统就一目了然了 ,简单的概括过程如下
- 建立loop循环 然后一直post
- 然后SpringSystem 调用每个spring.advance(deltaTime / 1000.0);输出算法值。
- 停止 什么时候停止。mActiveSprings.remove(spring);移除到empty的时候。什么时候移除要看 spring.systemShouldAdvance()
3.主要算法解析
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;
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;
while (mTimeAccumulator >= SOLVER_TIMESTEP_SEC) {
mTimeAccumulator -= SOLVER_TIMESTEP_SEC;
if (mTimeAccumulator < SOLVER_TIMESTEP_SEC) {
mPreviousState.position = position;
mPreviousState.velocity = velocity;
}
aVelocity = velocity;
aAcceleration = (tension * (mEndValue - tempPosition)) - friction * velocity;
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;
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;
...
我们先简化下上面的算法,提取我们需要的值,主要是想知道第1步中得到的值是怎么改变的
publicdoublegetCurrentValue(){
return mCurrentState.position;
}
我们看到 在倒数第二行改变了, mCurrentState.position = position; 那么按到position 是如何被赋值的 position += dxdt * SOLVER_TIMESTEP_SEC;SOLVER_TIMESTEP_SEC是个常亮量,dxdt 这个是个平均值 dxdt = 1.0/6.0 * (aVelocity + 2.0 * (bVelocity + cVelocity) + dVelocity); 那么我想如果不用平均值怎么样,我让dxdt = aVelocity ;重新启动demo 也是可以的。我们就讲b,c,d 全删除了,算法就等同下面的了。
aVelocity = velocity;
aAcceleration = (tension * (mEndValue - tempPosition)) - friction * velocity;
tempPosition = position + aVelocity * SOLVER_TIMESTEP_SEC * 0.5;
tempVelocity = velocity + aAcceleration * SOLVER_TIMESTEP_SEC * 0.5;
dxdt = aVelocity;
position += dxdt * SOLVER_TIMESTEP_SEC;
这里大概就是在模拟弹力公式
F = k△x - F阻
4.总结
- 使用起来方便 设置下阻力和弹性系数就可以得到需要的弹性值
- 可以将弹性公式扩展成其他公式,以满足其他的需求。