Android复杂自定义动画的实现思路

Posted Vigibord

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android复杂自定义动画的实现思路相关的知识,希望对你有一定的参考价值。

先上效果:

入场动画:

清理动画:

基本思路

自定义一个View,让该View执行普通的Animation动画,利用Animation来当计时器,控制整个动画流程,收到每个进度变化时,先计算每个动画元素的大小、位置、颜色等逻辑,再刷新View来显示(动画元素在draw方法中自绘)。

A.开始动画,创建一个Animation,设置好动画时间后,利用applyTransformation的回调来控制动画进度,interpolatedTime表示进度,取值范围为0到1;

progressAnimation = new Animation() 
            @Override
            protected void applyTransformation(float interpolatedTime, Transformation t) 
                super.applyTransformation(interpolatedTime, t);
                setProgress(interpolatedTime);
            
        ;
progressAnimation.setDuration(ANIMATION_TIME_OPENING);
this.startAnimation(progressAnimation);

B.处理动画元素的逻辑

private void setProgress(float progress)
    //处理各个动画元素的基本逻辑
    //  比如计算小球的位置和大小,x、y表示小球的实时位置,radius表示实时大小
    //  x = (int) (origin_x + (des_x_one - origin_x) * progress);
    //  y = (int) (origin_y + (des_y_one - origin_y) * progress);
    //  radius = (int) (origin_radius + (des_radius_one - origin_radius) * progress);

    //刷新view
    this.invalidate();

C. 在View的onDraw方法中绘制动画元素

protected void onDraw(Canvas canvas) 
    //以小球为例
    //绘制小球,x,y坐标,radius半径
    canvas.drawCircle( x,  y,  radius, mPaint);

动画分解

入场动画

总共分为5个部分,在公共时间轴之内完成动画,公共时间轴的进度为从0到1;
1. 4个小球
分为3个阶段,往外走(0-0.3),往中心走(0.3-0.7),再往外走(0.7-1)
其中每个阶段有个私有时间轴的概念,会把公共轴映射到私有轴上
比如从位置A到位置B,公共轴为(0-0.3),映射到私有轴(0-1)上,这样就能在(0-0.3)的时间内从A位移到B;
2. 2个中间的大球
从时间轴0.7开始绘制,内圈的时间轴为(0.7-0.9),外圈的时间轴为(0.7-1),这样两个圈一前一后有膨胀的感觉,同样也是需要将公共轴的进度映射到私有轴上
3. 分数
时间轴为(0.7-1),映射到私有轴(0-1)后,比如分数从0分到35分,每次计算一个分值并绘制,就能实现一个分数动态上涨的效果
4. 大球和小球的颜色
沿用公共轴的进度,每个进度算一个颜色值并绘制;颜色计算的基本原理为有N个色值,每个色值对应一个分数值,比如35分处于N[3]到N[4]色值之间,那么最终色值为N[3]到N[4]之间的一个渐变色;
5. 描述文案

清理动画

运动的分为3个部分,由于要求动画的连贯性,故在一个Animation中完成,duration设置较长时间(30s),自己划分时间周期,如将1划分为30个时间周期,一个周期的时间为1/30,在不同的时间周期内完成不同的动作;
1. 4个小球
4小球分为两组,两两绕相同轨道转动;运动阶段1为缩小、加速过程,阶段2为匀速转动等待清理结果响应,阶段3为放大、减速过程;
2. 背景色
3. 分数

难点

  1. 对于各个动画元素私有时间轴的计算、控制
  2. 4个小球两两分组,同组小球绕同一椭圆轨道环绕大球转动;
  3. 小球在椭圆轨道运行时,要区分在正面轨道和背面轨道;处于背面时需要在大球绘制之前绘制避免遮挡大球、没有环绕效果;
  4. 在收到动画结束的响应之后,需要等待小球匀速完成一个循环回归原位后,再做阶段3的运动;

代码

计算小球运动的类

private class CircleItemSmall 
        int x;
        int y;
        int alpha;
        float radius;
        int color;
        float origin_x;
        float origin_y;
        float des_x_one;
        float des_y_one;
        float des_x_two = 0;
        float des_y_two = 0;
        float des_x_three;
        float des_y_three;
        float origin_radius;
        float des_radius_one;
        float des_radius_two;
        float des_radius_three;
        int origin_alpha;
        int des_alpha;

        float progress_step_one = 0.6f;
        float progress_step_two = 0.85f;

        boolean isUp;//是否显示是前面
        int pos = 0;//0右上 1左上 2左下 3右下
        double cos_del;//椭圆倾斜角
        double sin_del;//椭圆倾斜角
        double a;//椭圆长轴

        float oval_center_x;
        float oval_center_y;

        public CircleItemSmall(float x, float y, float des_x_one, float des_y_one, float des_x_three, float des_y_three
                , float radius, float des_radius_one , float des_radius_two, float des_radius_three
                , int originAlpha, int desAlpha)
            this.origin_x = x;
            this.origin_y = y;
            this.des_x_one = des_x_one;
            this.des_y_one = des_y_one;
            this.des_x_three = des_x_three;
            this.des_y_three = des_y_three;
            calPos();

            this.origin_radius = radius;
            this.des_radius_one = des_radius_one;
            this.des_radius_two = des_radius_two;
            this.des_radius_three = des_radius_three;

            this.origin_alpha = originAlpha;
            this.des_alpha = desAlpha;
        

        private void calPos()
            if(des_x_three > 0 && des_y_three < 0)
                oval_center_x = (mSmallFinalDesPos[0][0] + mSmallFinalDesPos[2][0]) / 2;
                oval_center_y = (mSmallFinalDesPos[0][1] + mSmallFinalDesPos[2][1]) / 2;
                a = Math.sqrt((mSmallFinalDesPos[0][0] - oval_center_x) * (mSmallFinalDesPos[0][0] - oval_center_x)
                        + (mSmallFinalDesPos[0][1] - oval_center_y) * (mSmallFinalDesPos[0][1] - oval_center_y));
                cos_del = ( mSmallFinalDesPos[0][0] - oval_center_x )/ a;
                sin_del = ( mSmallFinalDesPos[0][1] - oval_center_y) / a;
                pos = 0;
            else if(des_x_three < 0 && des_y_three < 0)
                oval_center_x = (mSmallFinalDesPos[1][0] + mSmallFinalDesPos[3][0]) / 2;
                oval_center_y = (mSmallFinalDesPos[1][1] + mSmallFinalDesPos[3][1]) / 2;
                a = Math.sqrt((mSmallFinalDesPos[1][0] - oval_center_x) * (mSmallFinalDesPos[1][0] - oval_center_x)
                        + (mSmallFinalDesPos[1][1] - oval_center_y) * (mSmallFinalDesPos[1][1] - oval_center_y));
                cos_del = -( mSmallFinalDesPos[1][0] - oval_center_x ) / a;
                sin_del = -( mSmallFinalDesPos[1][1] - oval_center_y) / a;
                pos = 1;
            else if(des_x_three < 0 && des_y_three > 0)
                oval_center_x = mSmallCircleList.get(0).oval_center_x;//减小计算
                oval_center_y = mSmallCircleList.get(0).oval_center_y;
                a = mSmallCircleList.get(0).a;
                cos_del = -( mSmallFinalDesPos[2][0] - oval_center_x ) / a;
                sin_del = -( mSmallFinalDesPos[2][1] - oval_center_y ) / a;
                pos = 2;
            else if(des_x_three > 0 && des_y_three > 0)
                oval_center_x = mSmallCircleList.get(1).oval_center_x;//减小计算
                oval_center_y = mSmallCircleList.get(1).oval_center_y;
                a = mSmallCircleList.get(1).a;
                cos_del = ( mSmallFinalDesPos[3][0] - oval_center_x ) / a;
                sin_del = ( mSmallFinalDesPos[3][1] - oval_center_y ) / a;
                pos = 3;
            
        

        public void handlerProgress(float progress)
            if(state == STATE_OPENING) 
                if (progress <= progress_step_one) 
                    handlerAnimStepOne(progress);
                 else if (progress <= progress_step_two) 
                    handlerAnimStepTwo(progress);
                 else 
                    handlerAnimStepThree(progress);
                
                handlerAlpha(progress);
            else if(state == STATE_SCAN || state == STATE_CLEAN)
                handlerScaleAnim(loop, ratio);
                handlerOvalAnim(loop, ratio);
            else if( state == STATE_CLEAN_ENDING)
                handlerCleanEndScaleAnim(loop, ratio);
                handlerOvalAnim(loop, ratio);
            
            if(progress == 1)
                reset();
            
        

        private void reset() 
            x = (int) des_x_three;
            y = (int) des_y_three;
            radius = des_radius_three;
            alpha = des_alpha;
        

        private void handlerCleanEndScaleAnim(int loop, float ratio) 
            if(current_rotate_loop == ANIMATION_ROTATE_LOOP_COUNT) 
                radius = (int) (origin_radius + (des_radius_three - origin_radius) * ratio);
            
        

        private void handlerScaleAnim(int loop, float ratio) 
            if(loop < ANIMATION_ROTATE_LOOP_COUNT) 
                radius = (int) (des_radius_three + (origin_radius - des_radius_three) * ratio);
            
        

        private void handlerOvalAnim(int loop, float ratio) 
            if(current_rotate_loop > ANIMATION_ROTATE_LOOP_COUNT)//收到结束消息后循环完一周停止运动
                return;
            
            double angle;
            if(pos == 0 || pos == 3) 
                angle = 2 * Math.PI * ratio;
                if(angle < Math.PI)
                    isUp = false;
                else
                    isUp = true;
                
            else
                angle = Math.PI + 2 * Math.PI * ratio;
                if(angle < 2 * Math.PI)
                    isUp = true;
                else
                    isUp = false;
                
            

            double cos_angle = Math.cos(angle);
            double sin_angle = Math.sin(angle);
            x = (int) (a * cos_angle * cos_del + a / 3 * sin_angle * sin_del + oval_center_x);
            y = (int) (a * cos_angle * sin_del + a / 3 * sin_angle * cos_del + oval_center_y);
        

        private void handlerAlpha(float progress) 
            alpha = (int) (origin_alpha + (des_alpha - origin_alpha) * progress);
        

        private void handlerAnimStepOne(float progress)
            float ratio = progress / progress_step_one;
            x = (int) (origin_x + (des_x_one - origin_x) * ratio);
            y = (int) (origin_y + (des_y_one - origin_y) * ratio);

            radius = (int) (origin_radius + (des_radius_one - origin_radius) * ratio);
        

        private void handlerAnimStepTwo(float progress)
            float ratio = ( progress - progress_step_one ) / (progress_step_two - progress_step_one);
            x = (int) (des_x_one + (des_x_two - des_x_one) * ratio);
            y = (int) (des_y_one + (des_y_two - des_y_one) * ratio);

            radius = (int) (des_radius_one + (des_radius_two - des_radius_one) * ratio);
        

        private void handlerAnimStepThree(float progress)
            float ratio = ( progress - progress_step_two ) / (1 - progress_step_two);
            x = (int) (des_x_two + (des_x_three - des_x_two) * ratio);
            y = (int) (des_y_two + (des_y_three - des_y_two) * ratio);

            radius = (int) (des_radius_two + (des_radius_three - des_radius_two) * ratio);
        

颜色计算方法

public class CleanColorHelper 

    /** 颜色列表,依次是红橙绿 */
    private static final int[] COLORS = 
            // 红
            0xffff5252, //
            0xfffe6530, //
            0xffff8d22, //
            0xffffb619, //
            // 黄
            0xfffcd038, //
            0xffb5eb12, //
            0xff7ad845, //
            // 绿
            0xff54d36f,
            //蓝
            0xff32a7de

    ;

    /**
     * 调用者控制变色的节奏
     *
     * @param percent 百分数
     */
    public static int manualUpdateColor(Colors startColor, Colors endColor, int percent) 
        if (percent > 100) 
            percent = 100;
         else if (percent < 0) 
            percent = 0;
        
        // 8->0 30%
        final int startColorIdx = getColorIdx(startColor);
        final int endColorIdx = getColorIdx(endColor);
        final float newPosition = startColorIdx + (float) (endColorIdx - startColorIdx) * percent / 100;

        int showColor ;
        if (newPosition == startColorIdx || newPosition == endColorIdx) 
            showColor = COLORS[(int) newPosition];
         else 
            int currentIdx ;
            int nextIdx ;
            float offset ;
            if (endColorIdx < startColorIdx) 
                currentIdx = (int) newPosition + 1;
                nextIdx = currentIdx - 1;
                offset = currentIdx - newPosition;
             else 
                currentIdx = (int) newPosition;
                nextIdx = currentIdx + 1;
                offset = newPosition - currentIdx;
            
            final int currentColor = COLORS[currentIdx];
            final int nextColor = COLORS[nextIdx];
            showColor = getShowColor(currentColor, nextColor, offset);
        
        return showColor;
    

    private static int getShowColor(int colorStart, int colorEnd, float offset) 
        final int startRed = Color.red(colorStart);
        final int startGreen = Color.green(colorStart);
        final int startBlue = Color.blue(colorStart);
        final int endRed = Color.red(colorEnd);
        final int endGreen = Color.green(colorEnd);
        final int endBlue = Color.blue(colorEnd);
        final int showRed = (int) (startRed + offset * (endRed - startRed));
        final int showGreen = (int) (startGreen + offset * (endGreen - startGreen));
        final int showBlue = (int) (startBlue + offset * (endBlue - startBlue));
        return Color.argb(255, showRed, showGreen, showBlue);
    

    private static int getColorIdx(Colors color) 
        switch (color) 
            case RED:
                return 0;
            case ORANGE:
                return 4;
            case GREEN:
                return 8;
        
        return 8;
    

    public static enum Colors 
        RED, ORANGE, GREEN
    

以上是关于Android复杂自定义动画的实现思路的主要内容,如果未能解决你的问题,请参考以下文章

Android自定义view之围棋动画(化繁为简)

Android技术分享|自定义View实现Material Design的Loading效果

Android技术分享|自定义View实现Material Design的Loading效果

Android 自定义上拉抽屉+组合动画效果

教你如何实现 Android TextView 文字轮播效果

Android 自定义录音播放动画View,让你的录音浪起来