效果图1
效果图2
一、属性动画:
在没有了解属性动画时,我们做动画一般用的就是View Animation,这样能简单实现位移、旋转、缩放以及alpha渐变等等效果,但是当我们用久了以后,总是会发现一些缺陷,例如:一些复杂动画无法实现;控件不会停留在动画结束位置等等。这个时候我们就需要了解属性动画了。
属性动画我是在之前郭神的一篇文章(点我查看)中所了解到的,在此就不一一赘述了。未了解属性动画的小伙伴可以去看看。
二、实现思路:
我们就可以开始动工了。做复杂的属性动画,我们要从Evarlutor入手:
public class GravityEvarlutor implements TypeEvaluator<Integer> {
@Override
public Float evaluate(float fraction, Float startValue, Float endValue) {
}
}
接下来我们分析下运动流程。从物理课本中我们知道,物体掉落时受重力加速度g的影响而加速掉落,当接触地面时到达最大速度vmax然后回弹,回弹到达最大高度时速度为0。由于动能损失,每次回弹的高度会越来越小,直到最后静止。已知值确定:由于是动画过程,所以总时间t我们也应该知道;起始下落高度_h1,动能损失过程复杂,为了简单实现效果,所以我们做如下规定:弹跳次数为3次,回弹高度我们确定为_h2=_h1/7,_h3=_h1/35,_h4=_h1/105。如图所示:
由于fraction的范围在0~1之间,因此我们可以把总时长看为1,接着我们可以得出如下代码:
public class GravityEvarlutor implements TypeEvaluator<Integer> {
@Override
public Float evaluate(float fraction, Integer startValue, Integer endValue) {
//_h1为初始下落高度,_h2,_h3,_h4为各次的回弹高度
int _h1 = endValue - startValue;
int _h2 = _h1/7;
int _h3 = _h1/35;
int _h4 = _h1/105;
//根据t = Math.sqrt(2 * h/a)以及t1 + 2*t2 + 2*t3 + 2*t4 = 1,可算出:
double t1 = 1 / 2.28917;
//从而得出重力加速度
double a = (2 * _h1) / (t1 * t1);
//再求出t2,t3,t4
double t2 = Math.sqrt(2 * _h2 / a);
double t3 = Math.sqrt(2 * _h3 / a);
double t4 = Math.sqrt(2 * _h4 / a);
//算出各个时间段的最大速度vt,因为每次在最大高度时满足v = 0,所以在接触地面时,达到最大速度vt = a * t
double vt1 = a * t1;
double vt2 = a * t2;
double vt3 = a * t3;
double vt4 = a * t4;
}
}
接下来就是分析各个阶段的运动分析。下落时,小球的起始高度为h0,某个时刻的高度为h,根据公式s=(gt^2)/2,算出小球在时刻t所掉落的距离s,因此可以得出某时刻的小球高度h=h0-s=h0-(gt^2)/2。回弹时,小球的起始速度为vt,由于此刻是减速运动,所以某时刻t小球的高度h=vt*t-(gt^2)/2。由上面的运动分析图中看出,小球运动大致分7个阶段,我们拆解为1-1、2-1、2-2、3-1、3-2、4-1、4-2,ok,分析完毕,我们可以开始动代码了。
首先,我们对fraction进行拆解,进行分段分析:
//将fraction进行分段,便于每段独立分析
double fraction2_1 = fraction - t1;
double fraction2_2 = fraction - t1 - t2;
double fraction3_1 = fraction - t1 - (2 * t2);
double fraction3_2 = fraction - t1 - (2 * t2) - t3;
double fraction4_1 = fraction - t1 - (2 * t2) - (2 * t3);
double fraction4_2 = fraction - t1 - (2 * t2) - (2 * t3) - t4;
接着,我们就可以算出各个时间段所对应的h值:
//分段算出最终值h
int h = 0;
if(fraction <= t1) {
h = (int)(_h1 - a * 0.5 * fraction * fraction);
}else if(fraction > t1 && fraction <= (t1 + t2)){//2-1
h = (int)(vt2 * fraction2_1 - a * 0.5 * fraction2_1 * fraction2_1);
}else if(fraction > (t1 + t2) && fraction <= (t1 + (2 * t2))){//2-2
h = (int)(_h2 - a * 0.5 * fraction2_2 * fraction2_2);
}else if(fraction > (t1 + (2 * t2)) && fraction <= (t1 + (2 * t2) + t3)){//3-1
h = (int)(vt3 * fraction3_1 - a * 0.5 * fraction3_1 * fraction3_1);
}else if(fraction > (t1 + (2 * t2) + t3) && fraction <= (t1 + (2 * t2) + (2 * t3))){//3-2
h = (int)(_h3 - a * 0.5 * fraction3_2 * fraction3_2);
}else if(fraction > (t1 + (2 * t2) + (2 * t3))&& fraction <= (t1 + (2 * t2) + (2 * t3) + t4)){//4-1
h = (int)(vt4 * fraction4_1 - a * 0.5 * fraction4_1 * fraction4_1);
}else{//4-2
h = (int)(_h4 - a * 0.5 * fraction4_2 * fraction4_2);
}
ok,大功告成,但是有个小问题,因为我们算出来的值为double类型,强转成int后会有偏差,因此最后我们需要做下偏差矫正:
if(fraction == 1){
h = 0;
}
最难的Evalutor到此就告一段落了,贴下总代码:
public class GravityEvarlutor implements TypeEvaluator<Integer> {
/**
* 因为fraction是从0~1,因此我们可以将总时长看成1
* @param fraction
* @param startValue
* @param endValue
* @return
*/
@Override
public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
//_h1为初始下落高度,_h2,_h3,_h4为各次的回弹高度
int _h1 = endValue - startValue;
int _h2 = _h1/7;
int _h3 = _h1/35;
int _h4 = _h1/105;
//根据t = Math.sqrt(2 * h/a)以及t1 + 2*t2 + 2*t3 + 2*t4 = 1,可算出:
double t1 = 1 / 2.28917;
//从而得出重力加速度
double a = (2 * _h1) / (t1 * t1);
//再求出t2,t3,t4
double t2 = Math.sqrt(2 * _h2 / a);
double t3 = Math.sqrt(2 * _h3 / a);
double t4 = Math.sqrt(2 * _h4 / a);
//算出各个时间段的最大速度vt,因为每次在最大高度时满足v = 0,所以在接触地面时,达到最大速度vt = a * t
double vt1 = a * t1;
double vt2 = a * t2;
double vt3 = a * t3;
double vt4 = a * t4;
//将fraction进行分段,便于每段独立分析
double fraction2_1 = fraction - t1;
double fraction2_2 = fraction - t1 - t2;
double fraction3_1 = fraction - t1 - (2 * t2);
double fraction3_2 = fraction - t1 - (2 * t2) - t3;
double fraction4_1 = fraction - t1 - (2 * t2) - (2 * t3);
double fraction4_2 = fraction - t1 - (2 * t2) - (2 * t3) - t4;
//分段算出最终值h
int h = 0;
if(fraction <= t1) {
h = (int)(_h1 - a * 0.5 * fraction * fraction);
}else if(fraction > t1 && fraction <= (t1 + t2)){//2-1
h = (int)(vt2 * fraction2_1 - a * 0.5 * fraction2_1 * fraction2_1);
}else if(fraction > (t1 + t2) && fraction <= (t1 + (2 * t2))){//2-2
h = (int)(_h2 - a * 0.5 * fraction2_2 * fraction2_2);
}else if(fraction > (t1 + (2 * t2)) && fraction <= (t1 + (2 * t2) + t3)){//3-1
h = (int)(vt3 * fraction3_1 - a * 0.5 * fraction3_1 * fraction3_1);
}else if(fraction > (t1 + (2 * t2) + t3) && fraction <= (t1 + (2 * t2) + (2 * t3))){//3-2
h = (int)(_h3 - a * 0.5 * fraction3_2 * fraction3_2);
}else if(fraction > (t1 + (2 * t2) + (2 * t3))&& fraction <= (t1 + (2 * t2) + (2 * t3) + t4)){//4-1
h = (int)(vt4 * fraction4_1 - a * 0.5 * fraction4_1 * fraction4_1);
}else{//4-2
h = (int)(_h4 - a * 0.5 * fraction4_2 * fraction4_2);
}
if(fraction == 1){
h = 0;
}
return h;
}
}
接下来我们只要运用到属性动画中即可。布局简单,我就不贴了,需要最后留意的一点是,我们的Evalutor是以y轴正方向为正值的场景来写的,而在屏幕坐标中是相反的,这时候我们需要加个负号,然而加了负号之后,发现还有一个高度为startValue-endValue的偏差值,此时我们进行相应处理就好。
public void play(View v){
ValueAnimator va = ValueAnimator.ofObject(new GravityEvarlutor(), 0, layout_main.getHeight() - btn_play.getHeight() - ball.getHeight());
va.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
ball.setY(layout_main.getHeight() - btn_play.getHeight() - ball.getHeight() - (Integer) animation.getAnimatedValue());
}
});
va.setDuration(800);
va.setInterpolator(new LinearInterpolator());
va.start();
}
至此,大功告成。