在上一篇博文Android 图片弹跳动画里,我用两种方法实现了一个弹跳的动画,实现效果上篇博文里面有,这里就不再贴了,虽说是两种方法,但实现机制是大同小异,核心思想就是递归的不断启动动画,来实现View的上升和下降,后来发现还有另一种方法,不需要递归的去启动多个动画,只需要启动一个动画即可。
这里核心思想是自定义Interpolator,也就是插值器,关于插值器是什么网上有很多详细的介绍,我们上篇博文也用到了,只不过用的是系统定义好的。这里简单说就是它不会改变动画的执行顺序,但可以改变动画的执行效果,它的原理就是实现一个插值器的公共接口,完后具体实现getInterpolation方法,这个方法有一个参数是input,它表示了动画执行的过程,它的值从0到1,在动画执行过程中会不断调用这个方法,并且input线性递增,通过这个值,我们就可以根据自己动画的函数来返回我们想要的效果。
在上篇博文中,我们说过这个动画的几个要点,这里重新贴下:
1. 动画弹起的高度越来越小,我这里是第一次弹起屏幕的高度的1/2,第二次弹起1/4,第三次弹起1/8,以此类推
2. 我们将图片的一次落下或弹起看成一次动画,动画的时间越来越短,假设第一次落下动画需要1秒,那第一次弹起就需要1/2秒,第二次落下也是1/2秒,第二次弹起则需要1/4秒,以此类推
3. 下落的时候,速度越来越快,弹起的时候,速度越来越慢
其实Android本身已经写好了一个弹起落下的Interpolator,叫BounceInterpolator,我们先简单看看
public class BounceInterpolator implements Interpolator, NativeInterpolatorFactory {
public BounceInterpolator() {
}
@SuppressWarnings({"UnusedDeclaration"})
public BounceInterpolator(Context context, AttributeSet attrs) {
}
private static float bounce(float t) {
return t * t * 8.0f;
}
public float getInterpolation(float t) {
// _b(t) = t * t * 8
// bs(t) = _b(t) for t < 0.3535
// bs(t) = _b(t - 0.54719) + 0.7 for t < 0.7408
// bs(t) = _b(t - 0.8526) + 0.9 for t < 0.9644
// bs(t) = _b(t - 1.0435) + 0.95 for t <= 1.0
// b(t) = bs(t * 1.1226)
t *= 1.1226f;
if (t < 0.3535f) return bounce(t);
else if (t < 0.7408f) return bounce(t - 0.54719f) + 0.7f;
else if (t < 0.9644f) return bounce(t - 0.8526f) + 0.9f;
else return bounce(t - 1.0435f) + 0.95f;
}
/** @hide */
@Override
public long createNativeInterpolator() {
return NativeInterpolatorFactoryHelper.createBounceInterpolator();
}
}
这里我们要关注的就是getInterpolation这个方法,上面已经解释过它的实现原理,我们来看看对应的图
这是网上找的图片,这里x轴是时间,也就是上面提到的input,y轴是动画的过程,对照上面的实现,我们可以看到t < 0.3535f时,是一个递增的抛物线,在0.3535f时,y为1,也就是动画第一次落下,而t < 0.7408f时,是一个先递减,再递增的抛物线,也就是弹起又落下,而t < 0.9644f类似的,再一次弹起又落下,最后的0.9644到1是最后一次弹起落下,至于这里的函数为什么这样定义,说实话我没看懂,物理渣伤不起,估计可能跟重力感应还多少有些联系,不管这些,物理不是我们研究的重点,我们自己来实现类似的。为了简化问题,我们就以图片弹起两次为例进行说明,至于弹起三次、四次、五次,原理都是类似的。
我们将动画过程进行分解,图片弹起两次,经历了5个过程:
1. 图片第一次落下,从屏幕的顶部出现,直到屏幕底部
2. 图片第一次弹起,从屏幕的底部弹起到屏幕高度的1/2
3. 图片第二次落下,从屏幕高度的1/2落下到屏幕底部
4. 图片第二次弹起,从屏幕的底部弹起到屏幕高度的1/4
5. 图片第三次落下,从屏幕高度的1/4落下到屏幕底部
按照上面提到的动画的要点2,我们假设第一步花费时间为x,则第二步和第三步花费时间分别为x / 2,第四步和第五步花费时间分别为x / 4,则有公式:
x + x / 2 * 2 + x / 4 * 2 = 1
一元方程, x的值为 2 / 5
有了这个值后,我们就可以得到类似上图的一个分段抛物线,之前有免费画抛物线的软件,现在开始收费了,其它软件画有点麻烦,所以我就用文字描述下,对应上面动画过程的五步:
1. 在x轴的【0,2/5】这个区间,是一个递增的抛物线,y从0变为1,也就是图片第一次落下,我们可以得到抛物线的函数为 y = 25 / 4 * x * x(至于这个函数怎么来的,可能需要大家懂一点抛物线的知识,我们初中还是高中肯定学过,只不过时间长了忘了,我们可以先回忆一个简单的抛物线,y = x * x,也就是AccelerateInterpolator的实现中用到的,x轴从0开始匀速到1,y轴的值从0开始加速到1,我们这里只不过是将x的区间换成了【0,2/5】而已)
2. 在x轴的【2/5,3/5】这个区间,是一个递减的抛物线,y从1变为1/2,也就是图片第一次弹起
3. 在x轴的【3/5,4/5】这个区间,是一个递增的抛物线,y从1/2变为1,也就是图片第二次落下,2、3步是同一个抛物线,函数为y = 1/ 2 + 25 / 2 * (x - 3 / 5) * (x - 3 / 5)
4. 在x轴的【4/5,9/10】这个区间,是一个递减的抛物线,y从1变为3/4,也就是图片的第二次弹起
5. 在x轴的【9/10,1】这个区间,是一个递增的抛物线,y从3/4变为1,也就是图片的第三次落下,4、5步是同一个抛物线,函数为y = 3/4 + 25 * (x - 9/10) * (x - 9/10)
通过上面的分析,我们实现自己的插值器就很简单了
public class JumpInterpolator implements TimeInterpolator {
@Override
public float getInterpolation(float input) {
if (input <= 2/5f) {
return 25 / 4f * input * input;
} else if (input <= 4/5f) {
return 1 / 2f + 25 / 2f * (input - 3 / 5f) * (input - 3 / 5f);
} else {
return 3 / 4f + 25 * (input - 9 / 10f) * (input - 9 / 10f);
}
}
}
看下MainActivity的实现
public class MainActivity extends Activity {
private ImageView jump;
private int mHeight;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
jump = (ImageView) findViewById(R.id.move);
}
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (hasFocus) {
Rect outRect = new Rect();
getWindow().findViewById(Window.ID_ANDROID_CONTENT).getDrawingRect(outRect);
mHeight = outRect.height();
beginTransAnimation();
}
}
private void beginTransAtranslationYnimation() {
ObjectAnimator animator = ObjectAnimator.ofFloat(jump, "translationY", -mHeight, 0);
animator.setInterpolator(new JumpInterpolator());
animator.setDuration(5000);
animator.start();
}
}
在页面加载完成时,我们首先获取屏幕的高度,并赋值给mHeight变量,完后启动动画,这里动画还是用的属性动画,指定动画变化的部分为translationY,也就是y轴的坐标,初始值为-mHeight,也就是初始时图片的下边缘跟屏幕的上边缘重合(所以初始时看不到图片),而动画的终点为0,也就是图片完全显示在屏幕中,这里最重要的是我们指定了插值器为我们刚刚定义的JumpInterpolator,完后指定动画时间,最后启动动画即可,比较上一篇博客的两种实现方式,我们发现过程还是简化了许多,没有递归,也不会多次启动动画,一次就搞定。 源码下载