Android动画总结——View动画、属性动画、帧动画

在App中合理地使用动画能够获得友好愉悦的用户体验,Android中的动画有View动画、属性动画、帧动画、布局动画、转场动画等,在5.x以后有又新增了矢量动画,这些动画在平常开发中使用较为普遍,所以有必要做一次完整的总结。


一、View动画

View动画定义了渐变Alpha、旋转Rotate、缩放Scale、平移Translate四种基本动画,并且通过这四种基本动画的组合使用,可以实现多种交互效果。
View动画使用非常简单,不仅可以通过XML文件来定义动画,同样可以通过Java代码来实现动画过程。

1.Xml文件定义View动画

通过xml来定义View动画涉及到一些公有的属性(在AndroidStudio上不能提示):

android:duration     动画持续时间
android:fillAfter    为true动画结束时,View将保持动画结束时的状态
android:fillBefore   为true动画结束时,View将还原到开始开始时的状态
android:repeatCount  动画重复执行的次数
android:repeatMode   动画重复模式 ,重复播放时restart重头开始,reverse重复播放时倒叙回放,该属性需要和android:repeatCount一起使用
android:interpolator 插值器,相当于变速器,改变动画的不同阶段的执行速度

这些属性是从Animation中继承下来的,在alpharotatescaletranslate标签中都可以直接使用。
利用xml文件定义View动画需要在工程的res目录下创建anim文件夹,所有的xml定义的View动画都要放在anim目录下。
渐变view_anim_alpha.xml




旋转view_anim_rotate.xml




缩放view_anim_scale.xml




平移view_anim_translate.xml




rotatescale动画的android:pivotXandroid:pivotY属性、translate动画的android:toXDeltaandroid:toYDelta属性的取值都可以是都可以数值、百分数、百分数p,比如:5050%50%p,他们取值的代表的意义各不相同:
50表示以View左上角为原点沿坐标轴正方向(x轴向右,y轴向下)偏移50px的位置;
50%表示以View左上角为原点沿坐标轴正方向(x轴向右,y轴向下)偏移View宽度或高度的50%处的位置;
50%p表示以View左上角为原点沿坐标轴正方向(x轴向右,y轴向下)偏移父控件宽度或高度的50%处的位置(p表示相对于ParentView的位置)。

Android动画总结——View动画、属性动画、帧动画_第1张图片
"50"位置点

Android动画总结——View动画、属性动画、帧动画_第2张图片
"50%"位置点

Android动画总结——View动画、属性动画、帧动画_第3张图片
"50%p"位置点

通过定义xml动画资源文件,在Activity中调用:

public void clickToAlpha(View view) {
    Animation alphaAnim = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.view_anim_alpha);
    mTargetView.startAnimation(alphaAnim);
}

public void clickToRotate(View view) {
    Animation rotateAnim = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.view_anim_rotate);
    mTargetView.startAnimation(rotateAnim);
}

public void clickToScale(View view) {
    Animation scaleAnim = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.view_anim_scale);
    mTargetView.startAnimation(scaleAnim);
}

public void clickToTranslate(View view) {
    Animation translateAnim = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.view_anim_translate);
    mTargetView.startAnimation(translateAnim);
}

public void clickToSet(View view) {
    Animation setAnim = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.view_anim_set);
    mTargetView.startAnimation(setAnim);
}
2.Java代码实现View动画

在平常的业务逻辑中也可以直接用Java代码来实现Veiw动画,Android系统给我们提供了AlphaAnimationRotateAnimationScaleAnimationTranslateAnimation四个动画类分别来实现View的渐变、旋转、缩放、平移动画。
渐变:

public void clickToAlpha(View view) {
    AlphaAnimation alphaAnimation = new AlphaAnimation(1, 0);
    alphaAnimation.setDuration(2000);
    mTargetView.startAnimation(alphaAnimation);
}

旋转:

public void clickToRotate(View view) {
    RotateAnimation rotateAnimation = new RotateAnimation(
            0, 360,
            Animation.RELATIVE_TO_SELF, 0.5f,
            Animation.RELATIVE_TO_SELF, 0.5f);
    rotateAnimation.setDuration(2000);
    mTargetView.startAnimation(rotateAnimation);
}

缩放:

public void clickToScale(View view) {
    ScaleAnimation scaleAnimation = new ScaleAnimation(
            1, 0.5f,
            1, 0.5f,
            Animation.RELATIVE_TO_SELF, 0.5f,
            Animation.RELATIVE_TO_SELF, 0.5f);
    scaleAnimation.setDuration(2000);
    mTargetView.startAnimation(scaleAnimation);
}

平移:

public void clickToTranslate(View view) {
    TranslateAnimation translateAnimation = new TranslateAnimation(
            Animation.RELATIVE_TO_SELF, 0,
            Animation.RELATIVE_TO_SELF, 1,
            Animation.RELATIVE_TO_SELF, 0,
            Animation.RELATIVE_TO_SELF, 1);
    translateAnimation.setDuration(2000);
    mTargetView.startAnimation(translateAnimation);
}

组合:

public void clickToSet(View view) {
    AlphaAnimation alphaAnimation = new AlphaAnimation(1, 0);
    alphaAnimation.setDuration(2000);

    RotateAnimation rotateAnimation = new RotateAnimation(
            0, 360,
            Animation.RELATIVE_TO_SELF, 0.5f,
            Animation.RELATIVE_TO_SELF, 0.5f);
    rotateAnimation.setDuration(2000);

    ScaleAnimation scaleAnimation = new ScaleAnimation(
            1, 0.5f,
            1, 0.5f,
            Animation.RELATIVE_TO_SELF, 0.5f,
            Animation.RELATIVE_TO_SELF, 0.5f);
    scaleAnimation.setDuration(2000);

    TranslateAnimation translateAnimation = new TranslateAnimation(
            Animation.RELATIVE_TO_SELF, 0,
            Animation.RELATIVE_TO_SELF, 1,
            Animation.RELATIVE_TO_SELF, 0,
            Animation.RELATIVE_TO_SELF, 1);
    translateAnimation.setDuration(2000);

    AnimationSet animationSet = new AnimationSet(true);
    animationSet.addAnimation(alphaAnimation);
    animationSet.addAnimation(rotateAnimation);
    animationSet.addAnimation(scaleAnimation);
    animationSet.addAnimation(translateAnimation);

    mTargetView.startAnimation(animationSet);
}
View动画效果

View动画可以设置一个动画执行的监听器:

animation.setAnimationListener(new Animation.AnimationListener() {
    @Override
    public void onAnimationStart(Animation animation) {
        // 动画开始
    }

    @Override
    public void onAnimationEnd(Animation animation) {
        // 动画结束
    }

    @Override
    public void onAnimationRepeat(Animation animation) {
        //动画重复
    }
});

通过设置监听器可以在动画执行的开始、结束、重复时做一些其他的业务逻辑。


二、属性动画

所谓属性动画,就是改变对象Object的属性来实现动画过程。属性动画是对View的动画的扩展,通过它可以实现更多漂亮的动画效果。同时属性动画的作用对象不仅仅是View,任何对象都可以。
属性动画的作用效果就是:在一个指定的时间段内将对象的一个属性的属性值动态地变化到另一个属性值。

1.ObjectAnimator

ObjectAnimator是最常用的属性动画执行类。

private void startJavaPropertyAnimator() {
    ObjectAnimator
            .ofFloat(mImageView, "rotationY", 0f, 360f)
            .setDuration(2000)
            .start();
}

上面的代码就是通过ObjectAnimator在2000ms内将mImageViewrotationY属性的属性值从0f变化的360f

ObjectAnimator实现属性动画

ObjectAnimtor可以用 ofIntofFloatofObject等静态方法,传入动画作用的 目标Object、属性字段、属性开始值、属性中间值、属性结束值等参数来构造动画对象。
在动画更新的过程中,通过不断去调用对象属性的 setter方法改变属性值,不断重绘实现动画过程。如果没有给定动画开始属性值,那么系统会通过反射去获取 Object对象的初始值作为动画的开始值。
属性动画也同样可以通过xml文件来定义,同样在工程的res目录下创建animator文件夹,xml文件定义的 objectAnimator动画要放在该文件夹下。

property_animator.xml




Java代码调用:

private void startXmlPropertyAnimator() {
    Animator animator = AnimatorInflater.loadAnimator(getApplicationContext(), 
    R.animator.property_animator);
    animator.setTarget(mImageView);
    animator.start();
}

最终效果如上图。
属性动画也同样可以组合使用,通过AnimatorSet类和xml文件的set标签都可以同时改变对象的多个属性,实现更加丰富的动画效果。
通过AnimatorSet创建动画集:

private void startJavaPropertyAnimatorSet() {
    Animator scaleXAnimator = ObjectAnimator.ofFloat(mImageView, "scaleX", 1, 0.5f);
    scaleXAnimator.setDuration(2000);
    Animator scaleYAnimator = ObjectAnimator.ofFloat(mImageView, "scaleY", 1, 0.5f);
    scaleYAnimator.setDuration(2000);
    Animator rotationXAnimator = ObjectAnimator.ofFloat(mImageView, "rotationX", 0, 360);
    rotationXAnimator.setDuration(2000);
    Animator rotationYAnimator = ObjectAnimator.ofFloat(mImageView, "rotationY", 0, 360);
    rotationYAnimator.setDuration(2000);
    AnimatorSet animatorSet = new AnimatorSet();
    animatorSet.play(scaleXAnimator)
            .with(scaleYAnimator)
            .before(rotationXAnimator)
            .after(rotationYAnimator);
    animatorSet.start();
}

AnimatorSet通过beforewithafter三个方法可以组合多个属性动画,with表示与给定动画同时执行,before在给定动画执行之前执行,after表示在给定动画执行之后执行。

AnimatorSet构建属性动画集

通过xml文件定义属性动画集:
property_animator_set.xml



    
    
    

在Java代码中调用属性动画集:

private void startxmlPropertyAnimatorSet() {
    Animator animator = AnimatorInflater.loadAnimator(getApplicationContext(), 
    R.animator.property_animator_set);
    animator.setTarget(mImageView);
    animator.start();
}
xml定义属性动画集

同样,属性动画也可以添加动画执行监听器:

animator.addListener(new Animator.AnimatorListener() {
    @Override
    public void onAnimationStart(Animator animation) {
        // 动画开始
    }

    @Override
    public void onAnimationEnd(Animator animation) {
        // 动画结束
    }

    @Override
    public void onAnimationCancel(Animator animation) {
        // 动画取消
    }

    @Override
    public void onAnimationRepeat(Animator animation) {
        // 动画重复
    }
});

在监听到属性动画开始、结束、取消、重复时可以去做一些其他的逻辑业务。

2.ValueAnimator

ValueAnimatorObjectAnimator的父类,它继承自AnimatorValueAnimaotor同样提供了ofIntofFloatofObject等静态方法,传入的参数是动画过程的开始值、中间值、结束值来构造动画对象。可以将ValueAnimator看着一个值变化器,即在给定的时间内将一个目标值从给定的开始值变化到给定的结束值。在使用ValueAnimator时通常需要添加一个动画更新的监听器,在监听器中能够获取到执行过程中的每一个动画值。

private void startValueAnimator() {
    ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 1);
    valueAnimator.setDuration(300);
    valueAnimator.start();
    valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            // 动画更新过程中的动画值,可以根据动画值的变化来关联对象的属性,实现属性动画
            float value = (float) animation.getAnimatedValue();
            Log.d("ValueAnimator", "动画值:" + value);
        }
    });
}

在300ms内将数值0变化到1的动画值的变化log:

02-25 23:16:57.586 D/ValueAnimator: 动画值:0.0
02-25 23:16:57.596 D/ValueAnimator: 动画值:0.007902175
02-25 23:16:57.616 D/ValueAnimator: 动画值:0.029559612
02-25 23:16:57.636 D/ValueAnimator: 动画值:0.066987276
02-25 23:16:57.646 D/ValueAnimator: 动画值:0.118102014
02-25 23:16:57.666 D/ValueAnimator: 动画值:0.18128797
02-25 23:16:57.686 D/ValueAnimator: 动画值:0.2545482
02-25 23:16:57.706 D/ValueAnimator: 动画值:0.33063102
02-25 23:16:57.716 D/ValueAnimator: 动画值:0.4166157
02-25 23:16:57.736 D/ValueAnimator: 动画值:0.5052359
02-25 23:16:57.746 D/ValueAnimator: 动画值:0.5936906
02-25 23:16:57.766 D/ValueAnimator: 动画值:0.67918396
02-25 23:16:57.786 D/ValueAnimator: 动画值:0.7545208
02-25 23:16:57.796 D/ValueAnimator: 动画值:0.82671034
02-25 23:16:57.826 D/ValueAnimator: 动画值:0.88857293
02-25 23:16:57.836 D/ValueAnimator: 动画值:0.93815327
02-25 23:16:57.856 D/ValueAnimator: 动画值:0.9721882
02-25 23:16:57.876 D/ValueAnimator: 动画值:0.99384415
02-25 23:16:57.886 D/ValueAnimator: 动画值:1.0

ValueAnimator的使用一般会结合更新监听器AnimatorUpdateListener,大多数时候是在自定义控件时使用。
下面是用ValueAnimator自定义控件实现动画打开关闭效果。
expanded_veiw.xml




    

        

        

    


    

public class ExpandedView extends FrameLayout {

    private TextView mTvAnswer;
    private boolean isClosed;
    private ImageView mIvIndicator;

    public ExpandedView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    private void init(Context context) {
        View view = LayoutInflater.from(context).inflate(R.layout.expanded_view, this, true);
        LinearLayout llQuestion = (LinearLayout) view.findViewById(R.id.ll_expanded_question);
        llQuestion.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                anim();
            }
        });
        mTvAnswer = (TextView) view.findViewById(R.id.tv_expanded_answer);
        mIvIndicator = (ImageView) view.findViewById(R.id.iv_expanded_indicator);
    }

    private void anim() {
        // 指示器旋转
        ValueAnimator valueAnimator1 = isClosed
                ? ValueAnimator.ofFloat(180, 0)
                : ValueAnimator.ofFloat(0, 180);
        valueAnimator1.setDuration(500);
        valueAnimator1.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                float value = (float) animation.getAnimatedValue();
                mIvIndicator.setRotation(value);
            }
        });
        valueAnimator1.start();

        // 打开开关闭操作
        final int answerHeight = mTvAnswer.getMeasuredHeight();
        ValueAnimator valueAnimator2 = isClosed
                ? ValueAnimator.ofInt(-answerHeight, 0)
                : ValueAnimator.ofInt(0, -answerHeight);
        valueAnimator2.setDuration(500);
        final MarginLayoutParams params = (MarginLayoutParams) mTvAnswer.getLayoutParams();
        valueAnimator2.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                int value = (int) animation.getAnimatedValue();
                params.bottomMargin = value;
                mTvAnswer.setLayoutParams(params);
            }
        });
        valueAnimator2.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {

            }

            @Override
            public void onAnimationEnd(Animator animation) {
                isClosed = !isClosed;
            }

            @Override
            public void onAnimationCancel(Animator animation) {

            }

            @Override
            public void onAnimationRepeat(Animator animation) {

            }
        });
        valueAnimator2.start();
    }
}
ValueAnimator自定义控件效果图
3.TypeEvaluator

ObjectAnimatorValueAnimator都有ofObject方法,传入的都有一个TypeEvaluator类型的参数。TypeEvaluator是一个接口,里面也只有一个抽象方法:

public T evaluate(float fraction, T startValue, T endValue);

再看ofInt方法中没有传入该参数,但实际上调用ofInt方法时,系统已经有实现了TypeEvaluator接口的IntEvaluator,它的源码也非常简单:

public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
    int startInt = startValue;
    return (int)(startInt + fraction * (endValue - startInt));
}

fraction范围为0到1,表示动画执行过程中已完成程度。
泛型T即为动画执行的属性类型。
所以我们要用属性动画来执行复杂对象的动画过程,就需要自定义TypeEvaluator,实现动画逻辑。
先来定义一个对象

public class Circle {

    private int raduis;         // 半径
    private int color;          // 颜色
    private int elevation;      // 高度

    public Circle(int raduis, int color, int elevation) {
        this.raduis = raduis;
        this.color = color;
        this.elevation = elevation;
    }

    public int getRaduis() {
        return raduis;
    }

    public void setRaduis(int raduis) {
        this.raduis = raduis;
    }

    public int getColor() {
        return color;
    }

    public void setColor(int color) {
        this.color = color;
    }

    public int getElevation() {
        return elevation;
    }

    public void setElevation(int elevation) {
        this.elevation = elevation;
    }
}

CircleEvaluator,实现TypeEvaluator接口:

public class CircleEvaluator implements TypeEvaluator {
    @Override
    public Circle evaluate(float fraction, Circle startValue, Circle endValue) {

        int startRaduis = startValue.getRaduis();
        int endRaduis = endValue.getRaduis();
        int raduis = (int) (startRaduis + fraction * (endRaduis - startRaduis));

        int startColor = startValue.getColor();
        int endColor = endValue.getColor();
        int startColorRed = Color.red(startColor);
        int startColorGreen = Color.green(startColor);
        int startColorBlue = Color.blue(startColor);
        int endColorRed = Color.red(endColor);
        int endColorGreen = Color.green(endColor);
        int endColorBlue = Color.blue(endColor);
        int colorRed = (int) (startColorRed + fraction * (endColorRed - startColorRed));
        int colorGreen = (int) (startColorGreen + fraction * (endColorGreen - startColorGreen));
        int colorBlue = (int) (startColorBlue + fraction * (endColorBlue - startColorBlue));
        int color = Color.rgb(colorRed, colorGreen, colorBlue);

        int startElevation = startValue.getElevation();
        int endElevation = endValue.getElevation();
        int elevation = (int) (startElevation + fraction * (endElevation - startElevation));

        return new Circle(raduis, color, elevation);
    }
}

自定义控件CircleView,将Circle作为它的一个属性:

public class CircleView extends View {
    private Circle circle;
    private Paint mPaint;

    public CircleView(Context context, AttributeSet attrs) {
        super(context, attrs);
        circle = new Circle(168, Color.RED, 0);
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
    }

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        setElevation(circle.getElevation());
        mPaint.setColor(circle.getColor());
        canvas.drawCircle(getMeasuredHeight() / 2, getMeasuredHeight() / 2, circle.getRaduis(), mPaint);
    }

    public void setCircle(Circle circle) {
        this.circle = circle;
        postInvalidate();
    }

    public Circle getCircle() {
        return circle;
    }
}

ObjectAnimator使用:

private void start1() {
    Circle startCircle = new Circle(168, Color.RED, 0);
    Circle middleCircle = new Circle(300, Color.GREEN, 15);
    Circle endCircle = new Circle(450, Color.BLUE, 30);
    ObjectAnimator.ofObject(mCircleView, "circle", new CircleEvaluator(), startCircle, middleCircle, endCircle)
            .setDuration(5000)
            .start();
}

ValueAnimator使用:

private void start2() {
    Circle startCircle = new Circle(168, Color.RED, 0);
    Circle middleCircle = new Circle(300, Color.GREEN, 15);
    Circle endCircle = new Circle(450, Color.BLUE, 30);
    ValueAnimator valueAnimator = ValueAnimator.ofObject(new CircleEvaluator(), startCircle, middleCircle, endCircle);
    valueAnimator.setDuration(5000);
    valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            Circle circle = (Circle) animation.getAnimatedValue();
            mCircleView.setCircle(circle);
        }
    });
    valueAnimator.start();
}

自定义TypeEvaluator效果图

需要注意的是,系统调用获取控件setter、getter方法是通过反射获取的,属性的名称必须和getter、setter方法名称后面的字符串一致,比如上面的getter、setter方法分别为 getCirclesetCircle,那么属性名字就必须为 circle


三、帧动画

帧动画需要开发者制定好动画每一帧,系统一帧一帧的播放图片。

private void start1() {
    AnimationDrawable ad = new AnimationDrawable();
    for (int i = 0; i < 7; i++) {
        Drawable drawable = getResources().getDrawable(getResources().getIdentifier("ic_fingerprint_" + i, "drawable", getPackageName()));
        ad.addFrame(drawable, 100);
    }
    ad.setOneShot(false);
    mImageView.setImageDrawable(ad);
    ad.start();
}

帧动画同样也可以在xml文件中配置,直接在工程drawable目录新建animation-list标签:











private void start2() {
    mImageView.setImageResource(R.drawable.frame_anim);
    AnimationDrawable animationDrawable = (AnimationDrawable) mImageView.getDrawable();
    animationDrawable.start();
}

其中android:onshot属性和setOneShot方法表示是否只执行一次。
AnimationDrawable实际是上是一个Drawable动画,动画执行的过程总会给你不断重绘Drawable的每一帧图像,实现动态播放效果。

Android动画总结——View动画、属性动画、帧动画_第4张图片
帧动画效果图


源码:https://github.com/xiaoyanger0825/AnimationSummary

你可能感兴趣的:(Android动画总结——View动画、属性动画、帧动画)