Rebound源码分析

让动画不再僵硬:Facebook Rebound Android动画库介绍一文中介绍了rebound这个库。

对于想体验一下rebound的效果,又懒得clone和编译代码的,这里提供一个demo apk

今天看到了tumblr发布了基于reboundBackboard,本想直接分析一下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本身没关系,这里就不多说了。

最新文章

  1. Key/Value之王Memcached初探:三、Memcached解决Session的分布式存储场景的应用
  2. ABP之依赖注入
  3. mongoperf用法
  4. spring 源码
  5. iOS的nil,Null,NSNull的使用
  6. Mybatis Oracle 更新时报错17090
  7. DOM对象和JQuery对象
  8. scp跨主机拷贝工具
  9. Linux查找软件的安装路径
  10. JavaScript高级程序设计51.pdf
  11. 导出Excel文件,npoi方式和通过microsoft.visual basic.dll
  12. iOS多线程的三种方法
  13. 小程序for循环中通过index实现单个点击事件
  14. java Socket多线程聊天程序
  15. SpringBoot基础系列-SpringCache使用
  16. hdu 2149 (巴什博奕)
  17. DOM内容操作和自定义、样式改变
  18. 10.23JS日记
  19. UIWebView 大全
  20. JavaScript cookie操作实现点赞功能

热门文章

  1. cegui 编译过程详解(cegui-0.8.2)
  2. ASP.NET Core 利用中间件支持跨域请求
  3. MySQL一对一:一对多:多对多
  4. Mac OS 添加 MySQL 环境变量
  5. pythonic编程示例及简析
  6. random.nextInt方法用法
  7. [bzoj4259][bzoj4503] 残缺的字符串 [FFT]
  8. 荔枝丹(litchi)
  9. H5 语义化、基本事件 浅析 (含file对象、drag拖拽等)
  10. ClassLoader 提供了两个方法用于从装载的类路径中取得资源: