返回 登录
0

利用属性动画,打造一个重力弹跳的效果

阅读1226

效果图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();
}

至此,大功告成。

评论