博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Rebound动画框架简单介绍
阅读量:7209 次
发布时间:2019-06-29

本文共 13539 字,大约阅读时间需要 45 分钟。

Rebound动画框架简单介绍

菜鸟一枚,有不对的地方希望大家指出,谢谢。 

最近在接手了一个老项目,发现里面动画框架用的是facebook中的Rebound框架,由于以前没听说过,放假时闲得蛋痛,看看了源码,就顺手写这一篇吧。 
写了一个小Demo,具体效果如下: 
这是图片啊 
代码很简单,这是xml布局:


这是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//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类是一个对立的类,持有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菜鸟,很多时候不敢去分析源码,很为很多的东西都看不懂看不透,那就慢慢来吧,先从简单的开始吧,呵呵。

    本文转自 一点点征服   博客园博客,原文链接:http://www.cnblogs.com/ldq2016/p/7079770.html,如需转载请自行联系原作者

你可能感兴趣的文章
Oracle 数据定义
查看>>
百度分享自定义内容和图片
查看>>
关于代码评审的微博讨论汇集
查看>>
PHP vs Java
查看>>
C# 连接SQL Server数据库的几种方式--server+data source等方式
查看>>
Qt控件中的属性sizePolicy说明
查看>>
针对Properties中实时性要求不高的配置参数,用Java缓存起来
查看>>
Flex读取txt文件里的内容(二)
查看>>
mysql 变量set
查看>>
Deep Learning(深度学习)学习笔记整理系列
查看>>
【C解毒】缘木求鱼
查看>>
lua对模块接口扩展的一种方法
查看>>
DB,Cache和Redis应用场景分析
查看>>
CGI(通用网关接口)
查看>>
Cocos2d-x教程(28)-ttf 字体库的使用
查看>>
Mysql group by,order by,dinstict优化
查看>>
Notepad++ 经常使用快捷键 (MEMO)
查看>>
nyoj116士兵杀死(两)段树单点更新
查看>>
《Programming WPF》翻译 第3章 2.处理输入
查看>>
cdoj 1252 24点游戏 dfs
查看>>