【转载】Android 属性动画详解

引言:

   现在市面类似功能的App太多,如果你想要在类似的App中脱颖而出的话,那么你就要在增强用户体验方面下功夫,动画是你最好的选择。它可以给用户增加指引、吸引眼球、提醒用户跟着你一步步操作等等,动画是一个成熟App必不可少的。

Android动画分类:   

    先简单介绍一下Android中的几种动画,大致可分为三种:

 1.补间动画(也叫视图动画、Tween动画):

首先来了解下补间动画的分类,在android中补间动画可以分为四类:alpha(渐变)、scale(缩放)、translate(位移)、rotate(旋转)。在这四种动画里每种动画拥有它的独有的属性的同时又拥有相同的属性,其中

alpha:渐变透明度动画效果

scale:渐变缩放动画效果

translate:渐变位置移动动画效果

rotate:渐变旋转动画效果,这四种动画能够分别带来不同的效果体验,又能混合在一起完成酷炫的动画效果。

接下来我们就逐一详细的来学习这四种Tween动画。

结语:补间动画是android最早出现的动画,只支持以上四种简单的动画,并且补间动画只是视觉上的改变,不会影响控件的本身位置及大小,(就好像你在放大镜里看一个东西一样,只是视觉上的改变,原物体并不改变),所以现在很少用这种动画,它可以做的属性动画都可以做到。它的主要操作对象是Animation这个类,大家有兴趣的可以下去了解一下,这里主要介绍最常用的属性动画,所以就不多说了。

   2.帧动画

        帧动画,顾名思义就是像播放视频一样一帧一帧进行展示一样,其实视频也是一帧帧图片组成的,那么帧动画也一样, 将      一组运动的图分离开一个个小动作,然后组成,就是一个动画,这种动画一般是少量图片(如一个icon之类的)才会用到,因为大家都知道要缩小apk的体积的话,首先就的注意减少使用不必要的资源文件,所以只有少数情况会用这种动画,使用方法很简单,这里就不多说了。

   3.属性动画

    接下来就是我们的重头戏了,属性动画。首先得明白属性动画是真是改变控件本身的属性,跟上面所说的补间动画完全相反。

    学习属性动画先从ValueAnimator这个类学起,这个类非常关键,是属性动画机制中最核心的一个类,所有的操作直接或者间接都是用它来改变值进行动画效果。这句话是不是念起来有点拗口,大家有没有注意ValueAnimator中Value这个词,为什么叫Value呢,大家都知道Java中见名知意,所以这个Value肯定是这个动画的重点,Value:值的意思,那么我们可以叫值动画,没错就叫值动画。属性动画是通过不断改变值,再不断改变你要作用的对象,来达到动画的效果。是不是很好理解啊,下面我们来讲一下这个值动画,哈哈!

属性动画分类:属性动画大致又可以分为ValueAnimator,ObjectAnimator。

ValueAnimator:

     valueAnimator是通过不断改变值,手动进行赋值给对象,进行动画效果。大家注意这个“手动”二字。

    下面我们上代码具体来看,光说这些无聊的文字没意思,大家跟着我一步步来:

  Java代码写法:

  1.  
    //这个就是我们说的ValueAnimator这个值动画类,传入参数开始值跟结束值,表示你要从哪个值到哪个值进行变化,我们这个意思是我要让int值从0到100进行变化
  2.  
    ValueAnimator valueAnimator = ValueAnimator.ofInt(0,100);
  3.  
    //这里是设置动画执行时间,毫秒
  4.  
    valueAnimator.setDuration(2000);
  5.  
    //这里是设置动画的执行次数,为你填的n+1,-1表示一直执行
  6.  
    valueAnimator.setRepeatCount(0);
  7.  
    //动画的下次执行开始位置,RESTART表示动画每次从原始的状态执行,REVERSE表示动画第二次执行要从第一次改变后的状态逆向执行
  8.  
    valueAnimator.setRepeatMode(ValueAnimator.RESTART);
  9.  
    //设置数值变化监听器,重点在这里,上面我们动画的变化数值范围已经设置好了,那么动画变化时的值会在这个监听器中返回给我们,我们来手动赋值,来进行动画显示
  10.  
    valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
  11.  
    @Override
  12.  
    public void onAnimationUpdate(ValueAnimator animation) {
  13.  
    //获取当前变化时的值
  14.  
    int value = (int) animation.getAnimatedValue();
  15.  
    Log.e("lwd","value:" + value);
  16.  
    //这里我们给textview设置一个文本,就是我们的当前的数值,那么就会显示为从0到100一直变化的动画
  17.  
    mTextViewPro.setText(value + "");
  18.  
    }
  19.  
    });
  20.  
    //开始执行动画
  21.  
    valueAnimator.start();

    大家通过上面的代码跟注释是不是很好理解了,ValueAnimator这种动画说到底就是设置好开始值、结束值,然后设置好了之后大家在添加监听器之后,来捕获数值,然后手动赋值,进行自己的动画。

xml写法:

   大家在res下新建android资源文件夹animator,新建一个xml文件

  1.  
    <?xml version="1.0" encoding="utf-8"?>
  2.  
    <animator xmlns:android="http://schemas.android.com/apk/res/android"
  3.  
    android:valueFrom="0"
  4.  
    android:valueTo="100"
  5.  
    android:valueType="intType"
  6.  
    android:duration="3000"
  7.  
    android:fillBefore = "true"
  8.  
    android:repeatCount="0"
  9.  
    android:repeatMode="restart">
  10.  
     
  11.  
    </animator>

  大家看到了我们xml文件已经写好了,一些属性都很简单,见名知意,那么我们来看一下怎么加载这个xml文件。

  

  1.  
    //就俩行代码,加载xml文件,然后开始执行动画
  2.  
    Animator animator = AnimatorInflater.loadAnimator(this, R.animator.value_animator);
  3.  
    animator.start();

    大家也看到了xml文件写好之后数值就不会改变了,所以大多数复杂点的都用java来写。

   

   下面我们来引入一个新的东西,

   

   重点:TypeEvaluator(估值器)

     那么估值器是什么东西呢,我们先来看一段源吗,很简单

     ValueAnimator valueAnimator = ValueAnimator.ofInt(0,100);这行代码我到ofint看看它到底怎么实现的,

  1.  
    public static ValueAnimator ofInt(int... values) {
  2.  
    ValueAnimator anim = new ValueAnimator();
  3.  
    anim.setIntValues(values);
  4.  
    return anim;
  5.  
    }

   上面是ofInt的方法我们看到只是new了一个ValueAnimator对象,然后设置一下值,给我们返回了这个对象,那么操作这个这个值的过程应该跟这行代码有关系anim.setIntValues(values);我们进去再继续看。

  1.  
    public void setIntValues(int... values) {
  2.  
    if (values == null || values.length == 0) {
  3.  
    return;
  4.  
    }
  5.  
    if (mValues == null || mValues.length == 0) {
  6.  
    setValues(PropertyValuesHolder.ofInt("", values));
  7.  
    } else {
  8.  
    PropertyValuesHolder valuesHolder = mValues[0];
  9.  
    valuesHolder.setIntValues(values);
  10.  
    }
  11.  
    // New property/values/target should cause re-initialization prior to starting
  12.  
    mInitialized = false;
  13.  
    }

   从上面代码我们可以看到,首先判断传入的参数开始值跟结束值是否为空,为空则返回;否则的话设置值,那么具体设置值,我们还得到PropertyValuesHolder这个类中查看,下面我们来看。

  1.  
    void init() {
  2.  
    if (mEvaluator == null) {
  3.  
    // We already handle int and float automatically, but not their Object
  4.  
    // equivalents
  5.  
    mEvaluator = (mValueType == Integer.class) ? sIntEvaluator :
  6.  
    (mValueType == Float.class) ? sFloatEvaluator :
  7.  
    null;
  8.  
    }
  9.  
    if (mEvaluator != null) {
  10.  
    // KeyframeSet knows how to evaluate the common types - only give it a custom
  11.  
    // evaluator if one has been set on this class
  12.  
    mKeyframes.setEvaluator(mEvaluator);
  13.  
    }
  14.  
    }

大家看到了吗,这里初始化了一个IntEvaluator估值器,那么我们看一下这个估值器里面做了什么操作

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

   我们看到了一个fraction这个参数,这个参数大家可以理解为百分比的意思,估值器给我们不断返回了动画过程中不断变化的数值,那么到这里我们该清楚估值器的作用了吧。

  估值器顾名思义就是通过开始值跟结束值,给我们计算中间过渡值的一个东西。系统封装了一些常用的估值器(如FloatEvaluate、IntEvaluate等),那么接下来我们讲一下怎么自定义自己的估值器。

自定义估值器

   需求:画一个圆,让圆的进行线性移动。

   分析:首先要画个圆,那么我们可以自定义一个view,然后画个圆。那么素材有了,我们该怎么让它移动呢?用属性动画,那么属性动画要确定开始值跟结束值,那么,我们既然要移动的话,肯定是涉及到x、y坐标,那么我们开始值就可以定为圆当前的x、y坐标,结束值我们也可以随便定义一个,下面移动就是我们的重点了,我们带着问题来一起往下看。

   

  1.  
    /**
  2.  
    * 属性动画值变化实体
  3.  
    * Created by liweidong on 2019/1/8.
  4.  
    */
  5.  
     
  6.  
    public class Point {
  7.  
     
  8.  
    private float x;
  9.  
    private float y;
  10.  
     
  11.  
    public Point(float x, float y) {
  12.  
    this.x = x;
  13.  
    this.y = y;
  14.  
    }
  15.  
     
  16.  
     
  17.  
    public float getX() {
  18.  
    return x;
  19.  
    }
  20.  
     
  21.  
    public void setX(float x) {
  22.  
    this.x = x;
  23.  
    }
  24.  
     
  25.  
    public float getY() {
  26.  
    return y;
  27.  
    }
  28.  
     
  29.  
    public void setY(float y) {
  30.  
    this.y = y;
  31.  
    }
  32.  
    }

   首先我们创建一个x、y坐标的对象。

   接下来我们开始画圆,进行自定义view。

   

  1.  
    /**
  2.  
    * 我的圆
  3.  
    * Created by liweidong on 2019/1/8.
  4.  
    */
  5.  
     
  6.  
    public class MyCircleView extends View{
  7.  
     
  8.  
    private static final float RADIUS = 70f;//半径
  9.  
    private Paint mPaint;
  10.  
    private Point mPoint;
  11.  
     
  12.  
    public MyCircleView(Context context, @Nullable AttributeSet attrs) {
  13.  
    super(context, attrs);
  14.  
    mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
  15.  
    mPaint.setColor(Color.YELLOW);
  16.  
    }
  17.  
     
  18.  
    @Override
  19.  
    protected void onDraw(Canvas canvas) {
  20.  
    super.onDraw(canvas);
  21.  
    //如果当前点坐标为空,即第一次
  22.  
    if (mPoint == null){
  23.  
    mPoint = new Point(RADIUS, RADIUS);
  24.  
    float x = mPoint.getX();
  25.  
    float y = mPoint.getY();
  26.  
    canvas.drawCircle(x, y, RADIUS, mPaint);
  27.  
     
  28.  
    //将属性动画作用到view上
  29.  
    //步骤一,创建初始动画时的对象点,结束动画时的对象点
  30.  
    Point startPoint = new Point(RADIUS,RADIUS);
  31.  
    Point endPoint = new Point(700,700);
  32.  
    //步骤二,创建动画对象,设置初始值跟结束值
  33.  
    ValueAnimator valueAnimator = ValueAnimator.ofObject(new MyPointEvaluator(), startPoint, endPoint);
  34.  
    valueAnimator.setDuration(5000);
  35.  
    valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
  36.  
    @Override
  37.  
    public void onAnimationUpdate(ValueAnimator animation) {
  38.  
    //当前的点
  39.  
    mPoint = (Point) animation.getAnimatedValue();
  40.  
    //很重要,每次赋值要调用重新绘制
  41.  
    invalidate();
  42.  
    }
  43.  
    });
  44.  
    valueAnimator.start();
  45.  
    }else{
  46.  
    //接收到动画改变的点后,一直进行ondraw
  47.  
    float x = mPoint.getX();
  48.  
    float y = mPoint.getY();
  49.  
    canvas.drawCircle(x, y, RADIUS, mPaint);
  50.  
    }
  51.  
     
  52.  
    }
  53.  
    }

上面我们自定义了view,通过动画监听来触发一直进行绘制圆,上面用到了一个MyPointEvaluator这个估值器,那么大家看一下我们这个估值器做了些什么。

  1.  
    /**
  2.  
    * 我的估值器
  3.  
    * Created by liweidong on 2019/1/8.
  4.  
    */
  5.  
     
  6.  
    public class MyPointEvaluator implements TypeEvaluator {
  7.  
     
  8.  
    //复写估值器,在估值器中写动画过渡逻辑
  9.  
    @Override
  10.  
    public Object evaluate(float fraction, Object startValue, Object endValue) {
  11.  
    //将动画开始值跟结束值转换为point对象
  12.  
    Point startPoint = (Point) startValue;
  13.  
    Point endPoint = (Point) endValue;
  14.  
    //根据fraction计算当前动画的x跟y
  15.  
    float x = (float) (startPoint.getX() + fraction * (endPoint.getX() - startPoint.getX()));
  16.  
    float y = (float) (startPoint.getY() + fraction * (endPoint.getY() - startPoint.getY()));
  17.  
    //将当前的动画的坐标赋值到一个新的point对象
  18.  
    Point currentPoint = new Point(x, y);
  19.  
    return currentPoint;
  20.  
    }
  21.  
    }

这下我们在xml中引用自己的自定义view,就ok了,那么我们自定义的估值器就完成了,大家可以根据代码注释慢慢体会。

ObjectAnimator

   下面我们来介绍objectAnimator:

   objectAnimator:通过不断改变值,自动进行赋值给对象。注意这里是自动哦,可以理解为ValueAnimator的升级版,自动化,不用进行手动进行赋值了。

   先讲一下单个动画:

   1.translate(平移)

   

  1.  
    float currentX = mButtonTranslate.getTranslationX();
  2.  
    ObjectAnimator translateAnimator = ObjectAnimator.ofFloat(mButtonTranslate, "translationX", currentX, 50, currentX);
  3.  
    translateAnimator.setDuration(2000);
  4.  
    translateAnimator.setRepeatCount(0);
  5.  
    translateAnimator.setRepeatMode(ValueAnimator.RESTART);
  6.  
    translateAnimator.start();

2.rotate(旋转)

  1.  
    ObjectAnimator rotateAnimator = ObjectAnimator.ofFloat(mButtonRotate, "rotation", 0f, 360f);
  2.  
    rotateAnimator.setDuration(2000);
  3.  
    rotateAnimator.setRepeatCount(0);
  4.  
    rotateAnimator.setRepeatMode(ValueAnimator.RESTART);
  5.  
    rotateAnimator.start();

3.scale(缩放)

  1.  
    ObjectAnimator scaleAnimator = ObjectAnimator.ofFloat(mButtonScale, "scaleX", 1f, 3f, 1f);
  2.  
    scaleAnimator.setDuration(2000);
  3.  
    scaleAnimator.setRepeatCount(0);
  4.  
    scaleAnimator.setRepeatMode(ValueAnimator.RESTART);
  5.  
    scaleAnimator.start();

4.alpha(透明度)

  1.  
    ObjectAnimator aphaAnimator = ObjectAnimator.ofFloat(mButtonApha, "alpha" ,0 , 1);
  2.  
    aphaAnimator.setDuration(2000);
  3.  
    aphaAnimator.setRepeatCount(0);
  4.  
    aphaAnimator.setRepeatMode(ValueAnimator.RESTART);
  5.  
    aphaAnimator.start();

上面只是单个的简单动画,一般我们用动2020-08-03画都是组合动画。

下面提供几个api供大家进行动画组合:

AnimatorSet.play(Animator anim)   :播放当前动画
AnimatorSet.after(long delay)   :将现有动画延迟x毫秒后执行
AnimatorSet.with(Animator anim)   :将现有动画和传入的动画同时执行
AnimatorSet.after(Animator anim)   :将现有动画插入到传入的动画之后执行
AnimatorSet.before(Animator anim) :  将现有动画插入到传入的动画之前执行
  1.  
    ObjectAnimator translateAnimator = ObjectAnimator.ofFloat(mButtonTranRotate, "translationX", mButtonTranRotate.getTranslationX()
  2.  
    , 500, mButtonTranRotate.getTranslationX());
  3.  
    ObjectAnimator rotateAnimator = ObjectAnimator.ofFloat(mButtonTranRotate, "rotation", 0 , 360);
  4.  
    ObjectAnimator alphaAnimator = ObjectAnimator.ofFloat(mButtonTranRotate, "alpha", 1f,0f, 1f);
  5.  
    AnimatorSet animatorSet = new AnimatorSet();
  6.  
    animatorSet.play(translateAnimator).with(rotateAnimator).before(alphaAnimator);
  7.  
    animatorSet.setDuration(3000);
  8.  
    animatorSet.start();

给大家贴一段组合动画的代码,是不是很简单。

animatorSet.reverse(); 这个用来恢复动画到先前状态。到此属性动画大致讲完了,欢迎大家来补充。下节重点说一下自定义估值器,TypeEvaluate!


转载自 https://blog.csdn.net/no_loafer/article/details/86156590
原文地址:https://www.cnblogs.com/fafa12138/p/13424624.html