android 动画详细总结(含有自定义动画)以及插值器与估值器分析

在android开发中动画已经是随处可见了,所以本片文章笔者将详细总结常见动画的使用!


动画的分类

帧动画:

什么是帧动画?其实就是一帧一帧切换图片所以看起来就有动画效果。看看如何使用:
第一步定义一个xml文件在drawable里:

<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
    android:oneshot="false">       //false表示循环
        <item
            android:duration="100"              //本张图的保留时间
            android:drawable="@drawable/one"    //drawable目录下的图片资源
            ></item>
        <item
            android:duration="100"
            android:drawable="@drawable/two"
            ></item>
        <item
            android:duration="100"
            android:drawable="@drawable/there"
            ></item>
</animation-list>
注意:只能在drawable目录下才能定义animation-list,并且图片资源只能放在这个目录下。
imageView.setBackgroundResource(R.drawable.animal);   //animal为我们定义的xml文件
        AnimationDrawable animationDrawable= (AnimationDrawable)imageView.getBackground();      //如果是src则用getDrawable()
        animationDrawable.start();
注意:区分imageView.setBackgroundResource()与imageView.setImageResource()前者为background,后者为src。

上述代码效果图

补间动画:

什么是补间动画? 笔者的理解是一种行为动画。

 1. alpha(淡入淡出)
 2. translate(位移)
 3. scale(缩放大小)
 4. rotate(旋转)

alpha

方式一:
先生成一个xml文件,在res下新建一个anim的文件夹,新建一个alpha.xml文件

 <?xml version="1.0" encoding="utf-8"?>
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
    android:fromAlpha="0.0"    //从那个值开始
    android:toAlpha="1.0"      //到那个值结束
    android:duration="3000"    //动画开始到结束总时长
    android:interpolator="@android:interpolator/accelerate_decelerate"     
                      //动画运行的速率,一般直接使用android提供的插值器
    >
</alpha>

然后在代码中直接调用:

 Animation alphaAnimation = AnimationUtils.loadAnimation(this,R.anim.alpha);
           imageView.startAnimation(alphaAnimation);

解释:AnimationUtils.loadAnimation() 可根据xml文件生成一个animation实例。

方式二:
代码实现:

 AlphaAnimation alphaAnimation=new AlphaAnimation(0,1);
 alphaAnimation.setDuration(3000);
 alphaAnimation.setInterpolator(new AccelerateDecelerateInterpolator());
 imageView.startAnimation(alphaAnimation);

看看效果图:
这里写图片描述

translate
方式一:
同上生成一个xml文件

<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
    android:fromXDelta="0"      
    android:toXDelta="300"      
    android:duration="3000"
    android:interpolator="@android:interpolator/accelerate_decelerate"
    >
</translate>

注意:在xml文件里只能用像素所以不同分辨率手机视觉效果也就不同,所以我们一般用代码将dp转换px

这里仍是我的文章关于上述转换
代码部分:

Animation alphaAnimation = AnimationUtils.loadAnimation(this,R.anim.translate);
          imageView.startAnimation(alphaAnimation);

方式二

TranslateAnimation translateAnimation=new TranslateAnimation(0,300,0,300);
                   translateAnimation.setDuration(3000);
                   translateAnimation.setInterpolator(new AccelerateDecelerateInterpolator());
                   translateAnimation.setRepeatCount(1);
                   translateAnimation.setFillAfter(true);
                   translateAnimation.setRepeatMode(Animation.REVERSE);
                   imageView.startAnimation(translateAnimation);

注意我这里引入了一个参数: setFillAfter() 表示动画结束是否停留当前位置,setRepeatCount() 表示动画重复的次数。setRepeatMode表示重复的模式(默认重新开始,REVERSE倒退)效果图就不展示了。

scale
方式一:

<scale xmlns:android="http://schemas.android.com/apk/res/android"
    android:fromXScale="0"
    android:toXScale="1"
    android:fromYScale="0"
    android:toYScale="1"
    android:pivotX="540"
    android:pivotY="1920"
    android:duration="3000"
    >
</scale>

代码部分

ScaleAnimation scaleAnimation= (ScaleAnimation) AnimationUtils.loadAnimation(MainActivity.this, R.anim.scale);
                   imageView.startAnimation(scaleAnimation);

解释:scale的值表示倍率, pivotX,pivotY表示从哪个点开始

这里写图片描述

根据Pivot点到原图形的距离可得到不同倍率下的图形,连贯起来形成动画。

方式二:

ScaleAnimation scaleAnimation=new ScaleAnimation(0,1,0,1,0,1920-imageView.getTop());
                   scaleAnimation.setDuration(3000);
                   imageView.startAnimation(scaleAnimation);

scaleAnimation最后两个参数为Pivot增加的X,Y
注意:在Xml文件中Pivot可值即可为int(增加的X,Y),也可以用x%(自身的比例) ,同样可以用x%p(父容器的比例)

rotate
方式一:

xmlns:android="http://schemas.android.com/apk/res/android"
    android:fromDegrees="0"
    android:toDegrees="180"
    android:duration="3000"
    android:pivotX="50%"
    android:pivotY="50%"
    >
</rotate>

效果图
这里写图片描述

动画集合
方式一:

<set xmlns:android="http://schemas.android.com/apk/res/android">
    <rotate
    android:duration="1000"
    android:fromDegrees="0"
    android:toDegrees="180"
    android:pivotX="300"
    android:pivotY="300"
    />
    <translate
        android:duration="1000"
        android:fromXDelta="0"
        android:toXDelta="300"
        android:fromYDelta="0"
        android:toYDelta="300"
        ></translate>

</set>

其实就是将标签用set包裹起来然后视作一个动画

方式二:

 ScaleAnimation scaleAnimation= new ScaleAnimation(0,1,0,1,0,1920-imageView.getTop());
                    TranslateAnimation translateAnimation=new TranslateAnimation(0,300,0,300);
                    AnimationSet set=new AnimationSet(true);
                    set.setDuration(3000);
                    set.addAnimation(scaleAnimation);
                    set.addAnimation(translateAnimation);
                    set.setInterpolator(new AccelerateDecelerateInterpolator());
                    imageView.startAnimation(set);

ok补间动画就告一段了,属性动画才是重点。

属性动画

笔者的理解属性动画就是补间动画的升华, 补间动画存在很大的问题,比如在动画过程View的真实属性并没有变化这样便存在不合理性,所以属性动画为了弥补这个不足才因此诞生并且功能更强。

定义一个属性动画:

 ObjectAnimator objectAnimator= (ObjectAnimator)  ObjectAnimator.ofFloat();

当然执行肯定需要一些参数

1. Object target   //要执行动画的对象
2. String propertyName  //某种动画属性,像alpha,scale....
3. float... values    //动画过程值

上代码:

ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(imageView,"translationX",0,300,-300,0,300,0);
                objectAnimator.setDuration(5000);
                objectAnimator.setInterpolator(new LinearInterpolator());
                objectAnimator.start();

这里写图片描述

注意:这里用了translationX,而上述用的线性插值器但是运动的速率为什么不同? 这里就要解释一下插值器与估值器了。

插值器
(Interpolator)

什么是插值器? 提供给估值器的一个参考比例值(返回0-1数值),然后估值器根据这个比例值逐渐向目标值靠拢。

①LinearInterpolator(线性插值器):匀速动画。
②AccelerateDecelerateInterpolator(加速减速插值器):动画两头慢,中间快。
③DecelerateInterpolator(减速插值器):动画越来越慢。
④BounceInterpolator(弹跳插值器) :动画在结尾的时候会有一个弹跳过程

方便起见看一下LinearInterpolator的源码:

    public LinearInterpolator() {
    }

    public LinearInterpolator(Context context, AttributeSet attrs) {
    }

    public float getInterpolation(float input) {
        return input;
    }

getInterpolation(float input)这里会按照一定的时间分配0-1的值,下来我们演示一下:

 class myInterpolator implements TimeInterpolator{
          int t=0;
          @Override
          public float getInterpolation(float input) {
                if(t==0){
                    time_old= System.currentTimeMillis();
                    t++;
                }
                Log.e("input:"+input,"已执行时间:"+ (System.currentTimeMillis()-time_old));
                return input;
          }
      }

这里我重写了插值器(与LinearInterpolator效果相同),属性动画的插值器实现TimeInterpolator接口即可,并记录第一次执行时间保存在time_old里。

 valueAnimator.setDuration(5000);
                valueAnimator.setInterpolator(new myInterpolator());

然后定义了一个ValueAnimator(提供动画的值),并设置执行时间为5秒,放入我们自定义的插值器,这样插值器就可以正常工作了。看看执行效果:
这里写图片描述

看到最后input的值确实为1,而整个过程保持线性靠拢,time的值确实为我们设定的值。

那如果非LinearInterpolator插值器time还能否保持线性呢?

return (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f;

当然就不会了。

总结Interpolator
就是在指定时间内产生连续的(0-1)之间的数据提供给估值器,不同的插值器提供的数据是否一致? 不一致。

估值器
(Evaluator)

android提供三中估值器:

①IntEvaluator:针对整型属性
②FloatEvaluator:针对浮点型属性
③ArgbEvaluator:针对Color属性:

以Float为例:

public Float evaluate(float fraction, Number startValue, Number endValue) {
        float startFloat = startValue.floatValue();
        return startFloat + fraction * (endValue.floatValue() - startFloat);
    }

它的源码只有这个方法,可以看到它的返回值为Float而它本身就是Float类型估值器,当然IntEvaluator返回Int,ArgbEvaluator返回颜色值。而在其内部处理机制也是比较简单的,先说下内部参数:

 1. float fraction       //任务完成比例
 2. Number startValue   //封装了起始值
 3. Number endValue    //封装了结束值

根据完成比例*差值+初始值=当前值, 然后返回了当前值。

问题? 估值器里的fraction是否等于插值器里的返回的input?

不一定相等! 这里仍然做了个小实验:

估值器里的:

valueAnimator.setEvaluator(new FloatEvaluator(){
                     @Override
                     public Float evaluate(float fraction, Number startValue, Number endValue) {
                         L.g("完成比例"+fraction);
                         float startFloat = startValue.floatValue();
                         return startFloat + fraction * (endValue.floatValue() - startFloat);


                    }
                });

插值器里的:

  public float getInterpolation(float input) {
             return input;
          }

代码:

 ValueAnimator valueAnimator=ValueAnimator.ofFloat(0,300);

这里只提供了一组起始值,结束值。

这里写图片描述

这里确实相等,但是永远会相等吗?

ValueAnimator valueAnimator=ValueAnimator.ofFloat(new float[]{0,300,-300});

这里我填写了3组数据 ,看看结果

这里写图片描述

可以看到fraction是input的两倍,为什么? 其实就是我们传入了三组数据, 导致产生了两组起始值,两组结束值;
我在估值器里加上了Log:

public Float evaluate(float fraction, Number startValue, Number endValue) {
                         L.g("完成比例"+fraction);
                         float startFloat = startValue.floatValue();
                         L.g("start:"+startValue);
                         L.g("end:"+endValue);
                         return startFloat + fraction * (endValue.floatValue() - startFloat);
                    }

运行结果:
这里写图片描述

看到首先是(0,300)当完成比例(fraction)==1时,切换成第二组(300,-300),完成比例又会从0开始, 所以这也是导致fraction是input的两倍。所以上述问题迎刃而解,由于执行时间相同,而位移不同导致速率不同。

当然估值器也是可以重写的:

 class  myEvaluator implements TypeEvaluator{
        @Override
        public Object evaluate(float fraction, Object startValue, Object endValue) {
            return null;
        }
    }

其实估值器的重写目的就是自定义返回什么, 当你返回一个对象时便可以通过监听事件接收到这个对象然后做相关处理:

objectAnimator.setEvaluator(new myEvaluator());
                objectAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                    @Override
                    public void onAnimationUpdate(ValueAnimator animation) {
                          Object obj= animation.getAnimatedValue();
                    }
                });

设定了我们自定义的估值器后便可以从监听事件里在设定时间内连续的拿到一个Object对象,这个对象就是估值器返回的对象,当然这样源码肯定就无法读懂我们要做什么,所以我们就要自定义动画(下文)。

总结: 对于LinearInterpolator存在fraction于input倍数关系, 对于非LinearInterpolator,固定倍数关系就不存在了,而它们的共同点是:fraction要执行(数据组数-1)次(0-1),并且每组数据从起始值到结束值分配的时间是相同的,所以每组的时间就是 :总时间/(数据组数-1)。如果只有一组起始点,终止点,那么fraction与input是相等的,不论什么插值器。

回到正文
String propertyName 还有那些种类:

 1.translationY :Y方向的位置用法与translationX相同
 2.alpha :    透明度(仍然是从0-1)。
 3.scale :     缩放
 4.rotate:     旋转

alpha,与scale与补间动画相似不多做解释,下面演示一下ratote,
rotate有三种方式,这里我用一个组合动画。

ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(imageView, "rotation", new float[] { 0,360});
                objectAnimator.setDuration(2000);
                ObjectAnimator objectAnimator1=ObjectAnimator.ofFloat(imageView,"rotationX",  new float[] { 0,360});
                objectAnimator1.setDuration(2000);
                ObjectAnimator objectAnimator2=ObjectAnimator.ofFloat(imageView,"rotationY",new float[] { 0,360});
                objectAnimator2.setDuration(2000);
                AnimatorSet set=new AnimatorSet();
                set.play(objectAnimator).after(objectAnimator1).before(objectAnimator2);
                set.start();

创建了三种动画分别为rotation,rotationX,rotationY,并且旋转180度,这里我用了AnimatorSet集合(补间动画用的是AnimationSet),将三种动画都加了进去,set在放置动画的时候用了构建者模式(就是将自己返回),方便书写, set.play()表示放置一个动画, after表示将现有动画放在这个动画后执行, before表示将现有动画放在这个动画之前。 所以after的动画先执行, 然后是play里的,最后为before。 看看效果:

这里写图片描述

当然也可以用Xml定义属性动画,

<?xml version="1.0" encoding="utf-8"?>
<set>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="3000"
    android:propertyName="translationX"
    android:valueFrom="0dp"
    android:valueTo="300dp"
    >
</objectAnimator>
    <objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
        android:duration="3000"
        android:propertyName="translationY"
        android:valueFrom="0dp"
        android:valueTo="300dp"
        >
    </objectAnimator>
</set>

在res下新建一个animator文件夹然后建立这个文件,用了set标签所以是组合动画。接着看代码:

Animator animator=AnimatorInflater.loadAnimator(MainActivity.this,R.animator.translation);
                            animator.setTarget(imageView);
                            animator.start();

通过静态方法从xml中实例化一个Animator对象然后操作与上述就相同了。

到了收尾了。笔者做个自定义动画,上抛运动吧,

package listen.ssnjdan;

import android.animation.ObjectAnimator;
import android.animation.TypeEvaluator;
import android.animation.ValueAnimator;
import android.graphics.drawable.AnimationDrawable;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.view.animation.LinearInterpolator;
import android.widget.ImageView;

public class MainActivity extends AppCompatActivity {
    ImageView imageView;
    float v=500;       // 物体初始速度v
    float g=10//重力加速度
    private Points point=new Points();   自定义的类内部存放着不断更新的Y坐标。

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        imageView = (ImageView) findViewById(R.id.iv);
        imageView.setBackgroundResource(R.drawable.animal);
        AnimationDrawable animationDrawable = (AnimationDrawable) imageView.getBackground();
        animationDrawable.start();
        ;



        //属性动画从这开始
        imageView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                ObjectAnimator objectAnimator = ObjectAnimator.ofObject(imageView, "shuai",new myEvaluator(),0,0 );
                //  自定义动画后propertyName值就无效了,随便什么都可以,后面的也就没有用了。
                objectAnimator.setDuration(5000); 
                //整个动画的持续时间,也就是插值器连续提供值,估值器不断处理结果的时间。
                objectAnimator.setInterpolator(new LinearInterpolator());
                //线性插值器
                objectAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                        @Override
                        public void onAnimationUpdate(ValueAnimator animation) {
                           Points points=(Points)animation.getAnimatedValue();
                                imageView.setTranslationY(-points.Y);
                               L.g(points.Y+"");

                        }
                    });
    //定义一个数值监听           
 objectAnimator.start();
            }
        });
    }
    class myEvaluator implements TypeEvaluator {
        public  myEvaluator(){
        }
        @Override
        public Object evaluate(float fraction, Object startValue, Object endValue) {
            float myfraction=fraction*5;    由于插值器提供的值范围是(0-1)所以将其扩大。
            point.Y=v*myfraction-(float)1/2*g*myfraction*myfraction; 
             //物理公式不做解释
            v-=myfraction*g;
            return point;
        }
    }
    class Points{
        public  float Y=0;
    }
    //我们定义的类
}

ok 不做解释了 代码里写的很清楚,看看效果:

这里写图片描述

当然用ValueAnimator也是一样的道理, ObjectAnimator本身继承ValueAnimator,而ValueAnimator更像是一个数值发生器,在设定的时间内不断提供给插值器线性连续的数值(0-1),插值器决定是否更改,然后再将插值器更改的值做相关处理后传递到估值器里,而估值器的返回的Object就可以用监听事件来接收了,内部的相关调用全由ValueAnimator执行。

属性动画与补间动画
通俗讲属性动画就是不断地修改一个View的属性,而补间动画只是做相关渲染,属性并没变化。而两者各有优缺点,还是要具体分清场合使用,毕竟多一些操作就会多增加cpu负担,

如有不正,直接扣在评论里,谢谢!

猜你喜欢

转载自blog.csdn.net/qq_36043263/article/details/78905509