Android动画攻略—帧动画、补间动画、属性动画

489 阅读24分钟

前言

  • 动画时Android开发中使用频率比较高的功能。
  • 对Android提供的补间动画,帧动画以及属性动画做出归纳总结。

目录

目录.png

1. 帧动画

帧动画总体实现比较简单,其实现本身是实现一个图片集的连续播放,从而达到动画的效果。

实现帧动画就必须将大量图片资源加入到APK当中,从而增加APK的大小,但是却可以实现比较复杂的动画效果。

帧动画使用比较简单的,具体操作过程如下

  1. 将图片集导入到对应目录下
  2. 在drawable文件夹下新建文件anim_chat. xml,的代码实现如下
<?xml version="1.0" encoding="UTF-8"?>
<animation-list android:oneshot="false"
	xmlns:android="http://schemas.android.com/apk/res/android">
	//duraction字段可以用来设置该图片播放时长,drawable用来设置要显示的图片
	<item android:duration="230" android:drawable="@drawable/ic_chat_recording1" />
	<item android:duration="230" android:drawable="@drawable/ic_chat_recording2" />
	<item android:duration="230" android:drawable="@drawable/ic_chat_recording3" />
	<item android:duration="230" android:drawable="@drawable/ic_chat_recording4" />
	<item android:duration="230" android:drawable="@drawable/ic_chat_recording5" />
	<item android:duration="230" android:drawable="@drawable/ic_chat_recording6" />
</animation-list>
  1. 在布局文件activity_main.xml当中添加组件
<Button
        android:id="@+id/frame_animation_test"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="帧动画测试" />

    <ImageView
        android:id="@+id/frame_animation_img"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/ic_chat_recording1"
        android:layout_marginTop="20dp"/>
  1. 在activity当中加入java代码实现
public class MainActivity extends AppCompatActivity {

    private Button frameButton;
    private ImageView frameImage;
    private AnimationDrawable frameAnimation;
    boolean isStart = false;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initFrame();
    }

    private void initFrame() {
        //初始化控件
        frameButton = (Button) findViewById(R.id.frame_animation_test);
        frameImage = (ImageView) findViewById(R.id.frame_animation_img);
        //给ImageView设置drawable
        frameImage.setImageResource(R.drawable.anim_chat_recording);
        //给动画资源赋值
        frameAnimation = (AnimationDrawable) frameImage.getDrawable();
        //给按钮添加点击事件用来控制动画
        frameButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //当isStart=false时表示动画没有在播放,点击按钮开始播放
                if (!isStart) {
                    frameAnimation.start();
                    isStart = true;
                } else {//当isStart=true时表示动画正在播放,点击按钮停止播放
                    frameAnimation.stop();
                    isStart = false;
                }
            }
        });
    }
}

运行代码进行测试

帧动画--语音.gif

帧动画也存在使用纯java代码的实现方式,但是在应用当中并不多见,有兴趣可以了解一下,这里不做介绍。

2. 补间动画

与按帧播放的帧动画不同,补间动画只需要定义初始和结束时的状态,中间的动画过程将由系统自动补齐。

  • 特点:

    • 补间动画作用于View,可以实现视觉上的动画效果,但是并没有真正对视图做出改变。
    • 使用简单,可以使用非常简单的方式实现动画效果。
  • 实现方式可以有jave代码实现和XML代码实现两种。

  • 分类:补间动画可分为四类

jave | xml | 效果 ---|--- | -- AlphaAnimation | alph | 渐变透明度动画效果 ScaleAnimation | scale | 渐变尺寸伸缩动画效果 TranslateAnimation1 | translate | 画面转换位置移动动画效果 RotateAnimation | rotate | 画面转移旋转动画效果

后文将对四种补间动画效果做具体说明。

2.1 alph动画

特有属性:

  • android:fromAlpha:动画开始时的透明度。
  • android:toAlpha:动画结束时的透明度。

Java代码实现

直接上代码

  1. 在activity_main.xml当中定义布局资源
<ImageView
        android:id="@+id/alph_animation_img"
        android:layout_width="150dp"
        android:layout_height="200dp"
        android:src="@drawable/animation_test1"/>

后续内容的动画效果基本针对图片涉及到的xml布局都基本类似,将不再进行说明。

  1. 在activity当中进行实现
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initAlphJava();
    }

    private void initAlphJava() {
        alphImage = (ImageView) findViewById(R.id.alph_animation_img);
        alphImage.setVisibility(View.VISIBLE);
        //构造参数中,第一个参数为动画开始时候透明度,第二个参数toAlpha为 动画结束时候透明度
        //对于fromAlpha和toAlpha,1.0代表完全不透明状态,0.0代表完全透明状态
        AlphaAnimation mAlpha = new AlphaAnimation(1.0f,0.0f);
        //设置动画播放时间,2000MS=2S
        mAlpha.setDuration(2000);
        //设置动画循环次数,-1为一直循环
        mAlpha.setRepeatCount(-1);
        //设置动画循环方式Animation.REVERSE为倒叙播放,Animation.RESTART为重复播放
        mAlpha.setRepeatMode(Animation.REVERSE);
        //alphImage开始播放动画
        alphImage.startAnimation(mAlpha);
    }

动画效果为:由原图显示渐变为隐藏状态。

alph渐入渐出.gif

XML实现

  1. 在res目录中新建anim文件夹。
  2. 在anim目录中新建一个alph_anim.xml文件(注意文件名小写)。
  3. 在alph_anim.xml文件当中对动画进行定义。
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <!--透明度控制动画效果 alpha-->
    <!--fromAlpha 属性为动画起始时透明度 0.0表示完全透明 1.0表示完全不透明-->
    <!--toAlpha   属性为动画结束时透明度-->
    <!--duration  属性为动画持续时间-->
    <alpha
        android:duration="2000" //播放时间为2秒
        android:fromAlpha="1.0" //初始透明度为完全显示
        android:toAlpha="0.0"   //结束透明度为完全透明
        android:repeatCount= "-1" />    //重复播放次数为无限循环
</set>

fromAlpha和toAlpha为alph动画的特有属性,1.0代表完全不透明状态,0.0代表完全透明状态

  1. 在activity当中对动画资源信息引用。
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initAlphXml();
    }

    private void initAlphXml() {
        alphImage = (ImageView) findViewById(R.id.alph_animation_img);
        Animation animation = AnimationUtils.loadAnimation(this,R.anim.alph_anim);
        alphImage.startAnimation(animation);
    }

运行程序,动画效果与上述java实现相同。 补间动画中有一些公共属性,其说明如下:

android:duration: 动画执行的时间,以毫秒为单位

android:fillEnabled:true|false 动画结束时还原到开始动画前的状态

android:fillBefore:true|false 动画结束后视图会停留在动画开始的状态,如果fillEnabled的值为true,它的值才有意义,否则没有意义。默认值是true。

android:fillAfter:true|false 动画结束后是否保留这个动画的最后一帧的效果,它的设置不受fillEnabled的影响

android:repeatMode:reverse|restart 重复类型,reverse:表示倒序回放,restart:表示重新放一遍,这个属性必须与repeatCount联合使用,因为它的前提是重复,即重复播放时的播放类型。

android:repeatCount:动画重复的次数(注意是重复的次数),设定具体数值,也是可以是infinite,表示无限循环

android:interpolator:设定的插值器,它主要用来为动画设置一些特殊的效果,比方说:加速运动、减速运动等等。

2.2 scale动画

特有属性:

  • android:fromXScale起始的X方向上相对自身的缩放比例,类型float
  • android:toXScale:结尾的X方向上相对自身的缩放比例,类型float
  • android:fromYScale:起始的Y方向上相对自身的缩放比例,类型float
  • android:toYScale:结尾的Y方向上相对自身的缩放比例,类型float
  • android:pivotX: 缩放起点X轴坐标,可以是数值、百分数、百分数p。
  • android:pivotY: 缩放起点Y轴坐标,同pivotX。

java代码实现

  1. 在activity_main.xml当中定义布局资源
  2. 在java代码当中的代码实现如下:
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initScaleJava();
    }

    private void initScaleJava() {
        mImage = (ImageView) findViewById(R.id.animation_img);
        ScaleAnimation scaleAnimation = new ScaleAnimation(1,2,1,2,Animation.RELATIVE_TO_SELF,
0.5f,Animation.RELATIVE_TO_SELF,0.5f);
        // fromX:动画起始X轴缩放倍数
        // toX:动画结束时X轴缩放倍数
        // fromY:动画起始Y轴缩放倍数
        // toY:动画结束时Y轴缩放倍数
        // pivotXType:X轴缩放原点参数类型
        // pivotXValue:X轴缩放原点参数
        // pivotYType:Y轴缩放原点参数类型
        // pivotYValue:Y轴缩放原点参数
        scaleAnimation.setDuration(3000);
        scaleAnimation.setRepeatMode(Animation.REVERSE);
        //Animation.INFINITE与-1取值相同,为无限重播
        scaleAnimation.setRepeatCount(Animation.INFINITE);
        mImage.startAnimation(scaleAnimation);
    }

动画实现效果:以自身中心点为原点,缩放为原大小的两倍。

scale动画-图片缩放.gif

构造方法

ScaleAnimation(float fromX, float toX, float fromY, float toY,
            int pivotXType, float pivotXValue, int pivotYType, float pivotYValue)

参数取值介绍

对于前四个参数:0.0表示收缩到没有 1.0表示正常无伸缩 值小于1.0表示收缩 值大于1.0表示放大 pivotXType存在三种取值 pivotXType = Animation.ABSOLUTE:缩放轴点的x坐标 = View左上角的原点 在x方向 + pivotXValue数值的点(y方向同理)

pivotXType = Animation.RELATIVE_TO_SELF:缩放轴点的x坐标 = View左上角的原点 在x方向 + 自身宽度乘上pivotXValue数值的值(y方向同理)

pivotXType = Animation.RELATIVE_TO_PARENT:缩放轴点的x坐标 = View左上角的原点 在x方向 + 父控件宽度乘上pivotXValue数值的值 (y方向同理)

例子中所选参数为从原图大小X轴和Y轴缩放到原大小的两倍,缩放参照点为以自身宽高比例的50%处,也就是中心点。

XML实现

  1. 在res目录中新建anim文件夹。
  2. 在anim目录中新建一个scale_anim.xml文件(注意文件名小写)。
  3. 在scale_anim.xml文件当中对动画进行定义。
<set xmlns:android="http://schemas.android.com/apk/res/android" >
    <scale
        android:duration="3000"  //设置播放时长为3秒
        android:fillAfter="false"   //设置不保存播放完毕之后的画面
        android:fromXScale="1.0"    //起始画面X轴缩放倍数
        android:fromYScale="1.0"    //起始画面Y轴缩放倍数
        android:interpolator="@android:anim/accelerate_decelerate_interpolator"  
        //使用accelerate_decelerate_interpolator加速--减速差值器
        android:pivotX="50%"    //X轴缩放原点为自身宽度的50%
        android:pivotY="50%"    //Y轴缩放原点为自身宽度的50%
        android:toXScale="2.0"  //结束画面X轴缩放倍数
        android:toYScale="2.0"  //结束画面Y轴缩放倍数
        android:repeatCount= "-1"   //动画循环次数为无限循环
        android:repeatMode="reverse"/>  //循环模式为倒播

</set>

pivotX与pivotY相同,有三种取值方式:

  • 取值为数值:当为数值时,表示在当前View的左上角,加上参数值即原点处,做为旋转点X坐标,单位为px。
  • 取值为百分数:如果是50%表示在当前控件的左上角加上自己宽度的50%做为旋转点X坐标。
  • 取值为百分数p:如果是50%p,那么就是表示在当前的左上角加上父控件宽度的50%做为旋转点X坐标。
  1. 在activity当中对动画资源信息引用。
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initScaleXML();
    }

    private void initAlphXml() {
        private void initScaleXML() {
        mImage = (ImageView) findViewById(R.id.animation_img);
        Animation animation = AnimationUtils.loadAnimation(this,R.anim.scale_anim);
        mImage.startAnimation(animation);
    }
    }

运行程序,动画效果与上述java实现相同。

2.3 translate动画

特有属性:

  • android:fromXDelta:起始点X轴坐标。
  • android:fromYDelta:起始点Y坐标。
  • android:toXDelta:结束点X坐标
  • android:toYDelta:结束点Y坐标

java代码实现

  1. 在activity_main.xml当中定义布局资源
  2. 在java代码当中的代码实现如下:
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initTranslateJava();
    }
    
    private void initTranslateJava() {
        mImage = (ImageView) findViewById(R.id.animation_img);
        TranslateAnimation translateAnimation = new TranslateAnimation(0,300,0,300);
        //fromXDelta:起始点X轴的坐标
        //toXDelta:终止点X轴的坐标
        //fromYDelta:起始点Y轴的坐标
        //toYDelta:终止点Y轴的坐标
        translateAnimation.setDuration(2000);
        translateAnimation.setRepeatMode(Animation.REVERSE);
        //Animation.INFINITE与-1取值相同,为无限重播
        translateAnimation.setRepeatCount(Animation.INFINITE);
        mImage.startAnimation(translateAnimation);
    }

上述代码实现效果:View向右下角45°移动,最终坐标为原左上角坐标的X轴正方向300,Y轴正方向300. 动画效果如下

translate动画-图片移动.gif

对于构造方法

public TranslateAnimation(int fromXType, float fromXValue, int toXType, float toXValue,
            int fromYType, float fromYValue, int toYType, float toYValue) {

参数类型可大体分为两种: value和type

Type参数取值介绍

  • fromXType = Animation.ABSOLUTE:缩放轴点的x坐标 = View左上角的原点 在x方向 加上 pivotXValue数值的点(y方向同理) 默认为这种方式。

  • fromXType = Animation.RELATIVE_TO_SELF:缩放轴点的x坐标 = View左上角的原点 在x方向 加上 自身宽度乘上pivotXValue数值的值(y方向同理)

  • fromXType = Animation.RELATIVE_TO_PARENT:缩放轴点的x坐标 = View左上角的原点 在x方向 加上 父控件宽度乘上pivotXValue数值的值 (y方向同理)

Value取值介绍:

有三种取值方式:

  • 取值为数值:当为数值时,表示在当前View的左上角,即原点处加上参数值,做为旋转点X坐标,单位为px。
  • 取值为百分数:如果是50%表示在当前控件的左上角加上自己宽度的50%做为原点X坐标。
  • 取值为百分数p:如果是50%p,那么就是表示在当前的左上角加上父控件宽度的50%做为原点X坐标。

XML实现

  1. 在res目录中新建anim文件夹。
  2. 在anim目录中新建一个translate_anim.xml文件(注意文件名小写)。
  3. 在translate_anim.xml文件当中对动画进行定义。
<set xmlns:android="http://schemas.android.com/apk/res/android">

    <translate
        android:duration="2000"
        android:fromXDelta="0"
        android:fromYDelta="0"
        android:toXDelta="300"
        android:toYDelta="300" />

</set>

参数值类型介绍

整型值:

fromXDelta 属性为动画起始时 X坐标上的位置

toXDelta 属性为动画结束时 X坐标上的位置

fromYDelta 属性为动画起始时 Y坐标上的位置

toYDelta 属性为动画结束时 Y坐标上的位置

注意:

没有指定fromXType toXType fromYType toYType 时候, 默认是以自己为相对参照物

长整型值:

duration 属性为动画持续时间

说明: 时间以毫秒为单位

  1. 在activity当中对动画资源信息引用。
  @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initTranslateXML();
    }

    private void initTranslateXML() {
        mImage = (ImageView) findViewById(R.id.animation_img);
        Animation animation = AnimationUtils.loadAnimation(this,R.anim.translate_anim);
        mImage.startAnimation(animation);
    }

运行程序,动画效果与上述java实现相同。

2.4 Rotate动画

特有属性:

  • android:fromDegrees:动画开始时旋转的角度位置,float类型,正值代表顺时针方向度数,负值代码逆时针方向度数
  • android:toDegrees: 动画结束时旋转到的角度位置,float类型,正值代表顺时针方向度数,负值代码逆时针方向度数
  • android:pivotX:旋转点X轴坐标,float类型,可以是数值、百分数、百分数p三种样式。
  • android:pivotY:旋转点Y轴坐标,取值及意义跟android:pivotX一样。

Java实现

  1. 在activity_main.xml当中定义布局资源
  2. 在java代码当中的代码实现如下:
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initRotateJava();
    }
    private void initRotateJava() {
        mImage = (ImageView) findViewById(R.id.animation_img);
        RotateAnimation rotateAnimation = new RotateAnimation(0,90,Animation.RELATIVE_TO_SELF,
0.5f,Animation.RELATIVE_TO_SELF,0.5f);
//        参数说明:
//        fromDegrees :动画开始时 视图的旋转角度(正数 = 顺时针,负数 = 逆时针)
//        toDegrees :动画结束时 视图的旋转角度(正数 = 顺时针,负数 = 逆时针)
//        pivotXType:旋转轴点的x坐标的模式
//        pivotXValue:旋转轴点x坐标的相对值
//        pivotYType:旋转轴点的y坐标的模式
//        pivotYValue:旋转轴点y坐标的相对值
        rotateAnimation.setRepeatMode(Animation.REVERSE);
//        视频循环模式
        rotateAnimation.setDuration(2000);
//        视频播放时长
        rotateAnimation.setRepeatCount(Animation.INFINITE);
//        视频循环次数
        mImage.startAnimation(rotateAnimation);

上述代码实现效果:以自身中心点为坐标,顺时针旋转90度。

rotate动画-图片旋转.gif

参数取值介绍

fromDegrees :动画开始时 视图的旋转角度(正数 = 顺时针,负数 = 逆时针)

toDegrees :动画结束时 视图的旋转角度(正数 = 顺时针,负数 = 逆时针)

XML实现

  1. 在res目录中新建anim文件夹。
  2. 在anim目录中新建一个set_anim.xml文件(注意文件名小写)。
  3. 在set_anim.xml文件当中对动画进行定义。
<set xmlns:android="http://schemas.android.com/apk/res/android">
 <rotate
        android:duration="3000"
        android:fromDegrees="0"
        android:interpolator="@android:anim/accelerate_decelerate_interpolator"
        android:pivotX="50%"
        android:pivotY="50%"
        android:toDegrees="90" />    
</set>

<!--
     rotate 旋转动画效果
       属性:interpolator 指定一个动画的插入器
             在我试验过程中,使用android.res.anim中的资源时候发现
             有三种动画插入器:
                accelerate_decelerate_interpolator   加速-减速 动画插入器
                accelerate_interpolator               加速-动画插入器
                decelerate_interpolator               减速- 动画插入器
             其他的属于特定的动画效果
                           
       浮点数型值:
            fromDegrees 属性为动画起始时物件的角度    
            toDegrees   属性为动画结束时物件旋转的角度 可以大于360度   

        
            说明:
                     当角度为负数——表示逆时针旋转
                     当角度为正数——表示顺时针旋转              
                     (负数from——to正数:顺时针旋转)   
                     (负数from——to负数:逆时针旋转) 
                     (正数from——to正数:顺时针旋转) 
                     (正数from——to负数:逆时针旋转)       

            pivotX     属性为动画相对于物件的X坐标的开始位置
            pivotY     属性为动画相对于物件的Y坐标的开始位置
                
            说明:        以上两个属性值 从0%-100%中取值
                         50%为物件的X或Y方向坐标上的中点位置

        长整型值:
            duration  属性为动画持续时间
            说明:       时间以毫秒为单位

    -->
  1. 在activity当中对动画资源信息引用。
   @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initRotateXML();
    }

   private void initRotateXML() {
        mImage = (ImageView) findViewById(R.id.animation_img);
        Animation animation = AnimationUtils.loadAnimation(this, R.anim.rotate_anim);
        mImage.startAnimation(animation);
    }

运行程序,动画效果与上述java实现相同。

2.5 组合动画(set标签)

使用set标签可以做到多种动画类型的组合效果具体步骤如下

  1. 在res目录中新建anim文件夹。
  2. 在anim目录中新建一个set_anim.xml文件(注意文件名小写)。
  3. 在set_anim.xml文件当中对动画进行定义。
<set xmlns:android="http://schemas.android.com/apk/res/android">
 <alpha  
        android:fromAlpha= "0.0"  
        android:toAlpha= "1.0"  
        android:duration= "3000" />
        //alph动画 实现效果:从透明状态渐变到显示状态
     
    <scale  
        android:fromXScale= "0.0"  
        android:toXScale= "1.0"  
        android:fromYScale= "0.0"  
        android:toYScale= "1.0"  
        android:pivotX= "50%"  
        android:pivotY= "50%"  
        android:duration= "3000" />  
        //以自身原点为坐标从0缩放原图大小
     
    <rotate  
        android:fromDegrees= "0"  
        android:toDegrees= "720"  
        android:pivotX= "50%"  
        android:pivotY= "50%"  
        android:duration= "3000"/>  
        //旋转720°
     
     <translate  
        android:startOffset= "3000"  
        android:fromXDelta= "0"  
        android:fromYDelta= "0"  
        android:toXDelta= "85"  
        android:toYDelta= "0"  
        android:duration= "1000" />  
        //3000毫秒以后,水平方向移动85距离
      
    <alpha  
        android:startOffset= "4000"  
        android:fromAlpha= "1.0"  
        android:toAlpha= "0.0"  
        android:duration= "1000" />  
        //4000毫秒以后图片渐变消失
</set>
  1. 在==activity==当中对动画资源信息引用。
   @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
       initSet();
    }

  private void initSet() {
        mImage = (ImageView) findViewById(R.id.animation_img);
        Animation animation = AnimationUtils.loadAnimation(this, R.anim.set_anim);
        animation.setRepeatCount(-1);
        mImage.startAnimation(animation);
    }

动画效果如下:

set集合动画.gif
补间动画使用方便,但是其针对view进行操作的特性,以及只能实现视图上的动画而不能改变实际的view位置大小等缺点使其功能受到限制。下面将介绍功能更加强大的属性动画。

3.属性动画

  • 优势:
    • 在Android3.0推出了属性动画。相比于属性动画,View动画一个非常大的缺陷就是其不具备交互性。当某个元素发生View动画之后,其相应事件的位置依然在进行前的地方,所以View动画只能做到普通的动画效果,尽量避免交互操作。View动画的优势也十分明显:效率比较高,使用也方便。
    • View动画只能实现比较单一的View的移动缩放等效果,效果比较单一,属性动画则是通过属性值变化来重新布局View,理论上可以实现任何动画效果。
  • 问题:
    • 在使用上不如View动画方便。

3.1 原理解析

属性动画有两个非常重要的类:ValueAnimator 类 和 ObjectAnimator 类

  • ValueAnimator:通过不断控制值的变化,再不断手动赋给对象的属性,从而实现动画效果。
  • ObjectAnimator:直接对对象的属性值进行改变操作,通过不断控制值的变化,再不断自动赋给对象的属性,从而实现动画效果。其实现继承了ValueAnimator。

3.2 差值器和估值器

  • 插值器(Interpolator)前面我们在介绍补间动画时就有提到过,它主要用来为动画设置一些特殊的效果,比方说:加速运动、减速运动、动画结束的时候弹等等。主要有
    • accelerate_decelerate_interpolator 加速-减速 动画插入器
    • accelerate_interpolator 加速-动画插入器
    • decelerate_interpolator 减速- 动画插入器
  • 估值器(TypeEvaluator)决定值的具体变化数值

我们先来看一下系统提供的估值器:

public class FloatEvaluator implements TypeEvaluator<Number> {
    /**
     * @param fraction   The fraction from the starting to the ending values
     * @param startValue The start value; should be of type <code>float</code> or
     *                   <code>Float</code>
     * @param endValue   The end value; should be of type <code>float</code> or <code>Float</code>
     * @return A linear interpolation between the start and end values, given the
     *         <code>fraction</code> parameter.
     */
    public Float evaluate(float fraction, Number startValue, Number endValue) {
    <!--fraction 动画的完成度-->
    <!--startValue 动画的初始值-->
    <!--endValue 动画的结束值-->
        float startFloat = startValue.floatValue();
        <!--计算返回结果的公式为result = x0 + t * (v1 - v0)-->
        <!--x0=startValue-->
        <!--x1 = endValue-->
        <!--t = fraction-->
        return startFloat + fraction * (endValue.floatValue() - startFloat);
    }
}

这是由系统提供的操作float类型数据的估值器,可以根据我们提供的fraction、startValue以及endValue不断地产float数值对象返回给我们,我们可以根据返回的数据对我们的View进行更改。

但是很多情况下我们需要操作的不只是float或者int类型等基本数据类型,所以我们就需要自定义==ObjectEvaluator==。

模仿==FloatEvaluator==,自定义==ObjectEvaluator==如下:

// 实现TypeEvaluator接口
public class ObjectEvaluator implements TypeEvaluator<Object>{  

// 复写evaluate()
// 在evaluate()里写入对象动画过渡的逻辑
    @Override  
    public Object evaluate(float fraction, Object startValue, Object endValue) {  
      
        ... // 写入对象动画过渡的逻辑
        // 返回对象动画过渡的逻辑计算后的值
        return value;  
    }

后面会进行实例说明。再回过头来看我们的主角:ValueAnimator和ObjectAnimator类

3.3 ObjectAnimator类

本质原理: 通过不断控制值的变化,再不断自动赋给对象的属性,从而实现动画效果。 先来看一个简单的例子:

 private void initObjectAnimationJava() {
        mImage = (ImageView) findViewById(R.id.animation_img);
        ObjectAnimator alpha = ObjectAnimator.ofFloat(mImage, "alpha", 0f, 1f);
        alpha.setDuration(2000);//设置动画时间
        alpha.setInterpolator(new DecelerateInterpolator());//设置动画插入器,减速
        alpha.setRepeatCount(-1);//设置动画重复次数,这里-1代表无限
        alpha.setRepeatMode(ValueAnimator.REVERSE);//设置动画循环模式,注意这里是ValueAnimator.REVERSE而不是Animator.REVERSE
        alpha.start();//启动动画。
    }

实现效果:图片由隐藏渐渐显示。

动画对象定义完后的操作与View动画基本一致,最主要的区别在于构造方法,这里我使用了ObjectAnimator.ofFloat,ofFloat()方法主要有两个作用:设置参数以及返回ObjectAnimator对象,其方法如下:

public static ObjectAnimator ofFloat(Object target, String propertyName, float... values) {
        ObjectAnimator anim = new ObjectAnimator(target, propertyName);
        anim.setFloatValues(values);
        return anim;
    }
    //1. 需要操作的对象,注意这里与补间动画不同,不止限于View对象,而可以是任意操作对象。
    //2. propertyName需要操作的属性,这个属性必须要有get和set方法才可以。
    //3. 这是一个可变参数,为后续的变化趋势。如设置两个参数v1,v2,则属性由v1变化到v2,如果设置的是v1,v2,v3,则由v1变化到v2再变化到v3以此类推。
  • 透明度:
ObjectAnimator alpha = ObjectAnimator.ofFloat(text, "alpha", 0f, 1f);

透明度由0.0变为1.0。

  • 伸缩X轴:
ObjectAnimator scaleX = ObjectAnimator.ofFloat(text, "scaleX", 0f, 1f);
ObjectAnimator scaleY = ObjectAnimator.ofFloat(text, "scaleY", 0f, 1f);

X轴(Y轴)方向由0缩放到原图大小。

  • 移动:
ObjectAnimator translationUp = ObjectAnimator.ofFloat(text, "Y",0f,300f, 0f);

Y轴方向:0f-> -300f,向左;-300f-> 0f,向右。

  • 旋转
ObjectAnimator animator = ObjectAnimator.ofFloat(text, "rotation", 0f, 360f, 0f);

旋转动画:0f -> 360f ,顺时针; 360f -> 0f,逆时针。 可以操作的动画属性propertyName的取值可以为:

属性 作用 数值类型
Alpha 控制View的透明度 float
TranslationX 控制X方向的位移 float
TranslationY 控制Y方向的位移 float
ScaleX 控制X方向的缩放倍数 float
ScaleY 控制Y方向的缩放倍数 float
Rotation 控制以屏幕方向为轴的旋转度数 float
RotationX 控制以X轴为轴的旋转度数 float
RotationY 控制以Y轴为轴的旋转度数 float

ObjectAnimation组合动画

Java代码

使用AnimatorSet完成组合动画播放。

  • anim1.with(anim2):anim1与anim2同时播放。
  • anim1.befor(anim2):在anim2之前播放anim1。
  • anim1.after(anim2):在anim2之后播放anim1。
  • anim1.after(long delay):delay时间之后播放anim1。

具体使用代码如下:

private void initObjectAnimationSetJava() {
        mImage = (ImageView) findViewById(R.id.animation_img);
        AnimatorSet set = new AnimatorSet() ;
        //X轴旋转180°
        ObjectAnimator rotationX = ObjectAnimator .ofFloat(mImage, "rotationX", 0f, 180f);
        rotationX.setDuration(2000);
        //Y轴旋转180°
        ObjectAnimator rotationY = ObjectAnimator .ofFloat(mImage, "rotationY", 0f, 180f);
        rotationY.setDuration(2000);
        //X轴方向缩放到原图大小
        ObjectAnimator scaleX = ObjectAnimator.ofFloat(mImage, "scaleX", 0f, 1f);
        scaleX.setDuration(2000);
        //Y轴方向缩放到原图大小
        ObjectAnimator scaleY = ObjectAnimator.ofFloat(mImage, "scaleY", 1f, 0f);
        scaleY.setDuration(2000);

        set.play(rotationX).with(scaleX); //X轴旋转与X轴缩放动画同时进行
        set.play(rotationX).before(rotationY); //X轴旋转动画完成以后开始Y轴旋转
        set.play(rotationY).with(scaleY);//Y轴旋转动画与Y轴缩放同时开始
        set.start(); //开始播放动画
    }

动画效果:X轴旋转与X轴缩放动画同时进行,结束之后Y轴旋转动画与Y轴缩放同时开始。实际效果如下:

ObjectAnimation组合动画.gif

XML实现

set中的属==性android:ordering==:规定了这个set中的动画的执行顺序,包括:

  • together(默认):set中的动画同时执行
  • sequentially:set中的动画按顺序执行
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:ordering="sequentially">
        <set android:ordering="together">
            <objectAnimator
                android:duration="2000"
                android:propertyName="rotationX"
                android:repeatMode="reverse"
                android:valueFrom="0"
                android:valueTo="180" />
            <objectAnimator
                android:duration="2000"
                android:propertyName="scaleX"
                android:repeatMode="reverse"
                android:valueFrom="0.0"
                android:valueTo="1.0" />
        </set>

        <set android:ordering="together">
            <objectAnimator
                android:duration="2000"
                android:propertyName="rotationY"
                android:repeatMode="reverse"
                android:valueFrom="0"
                android:valueTo="180" />
            <objectAnimator
                android:duration="2000"
                android:propertyName="scaleY"
                android:repeatMode="reverse"
                android:valueFrom="1.0"
                android:valueTo="0.0" />
        </set>
</set>

java代码:

private void initObjectAnimationSetXML() {
        mImage = (ImageView) findViewById(R.id.animation_img);
        Animator animator = AnimatorInflater.loadAnimator(this,R.animator.object_set_anim);
        animator.setTarget(mImage);
        animator.start();
    }

实现效果与java代码一致。

3.4 ValueAnimator类

ValueAnimator类本质上是一种值得操作机制,想要实现动画,是需要开发者手动将这些值 赋给对象的属性值。 ValueAnimator动画同样存在java和xml两种使用方式。建议使用java代码的方式,这里只介绍java方式,xml有兴趣的可以自己了解一下。

ValueAnimator类常用的方法与ObjectAnimation类似,主要方法有

  • public static ValueAnimator ofInt(int... values)
  • public static ValueAnimator ofArgb(int... values)
  • public static ValueAnimator ofFloat(float... values)
  • public static ValueAnimator ofObject(TypeEvaluator evaluator, Object... values)

前三种方法使用类似,后面的参数都表示变化趋势,即:设置两个参数v1,v2,则属性由v1变化到v2,如果设置的是v1,v2,v3,则由v1变化到v2再变化到v3以此类推。

以ofFloat为例,使用方法如下

private void initValueAnimationJava() {
        mImage = (ImageView) findViewById(R.id.animation_img);
        //步骤1. 定义ValueAnimator方法
        ValueAnimator valueAnimator = ValueAnimator.ofFloat(0.0f,1.0f);
        //获取原控件宽高
        final int width = mImage.getLayoutParams().width;
        final int height = mImage.getLayoutParams().height;
        //设置动画属性
        valueAnimator.setDuration(2000);
        valueAnimator.setRepeatMode(ValueAnimator.REVERSE);
        valueAnimator.setRepeatCount(ValueAnimator.INFINITE);
        //步骤2. 添加AnimatorUpdateListener方法永联监听值得变化趋势
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animator) {

                float currentValue = (Float) animator.getAnimatedValue();
                // 获得每次变化后的属性值

                // 步骤3:每次值变化时,将值手动赋值给对象的属性
                // 即将每次变化后的值 赋 给按钮的宽度,这样就实现了按钮宽度属性的动态变化

                mImage.getLayoutParams().width = (int) (width*currentValue);
                mImage.getLayoutParams().height = (int) (height*currentValue);
                

                // 步骤4:刷新视图,即重新绘制,从而实现动画效果
                mImage.requestLayout();

            }
        });
        valueAnimator.start();
    }

实际动画效果如下:

valueAnimation-图片缩放.gif

如果我们想实现一些比较负载的动画效果,而系统提供的估值器满足不来我们时怎么办,这时候我们之前提到的估值器TypeEvaluator就该登场了,通过方法public static ValueAnimator ofObject(TypeEvaluator evaluator, Object... values),设置估值器以后将对数据按照估值器进行计算并返回值,通过点赞动画例子了解实战应用。

4. 属性动画实战--点赞动画

不多说,直接上代码

  1. 首先是点赞动画的自定义==PraiseLayout==
public class PraiseLayout extends RelativeLayout {
    //底部点赞按钮宽高
    private int dHeight;
    private int dWidth;
    //整个View空间宽高
    private int mHeight;
    private int mWidth;
    private LayoutParams layoutParams;
    private Random random = new Random();
    //图片资源数组
    private Drawable[] drawables;

    public PraiseLayout(Context context) {
        super(context);
        init();
    }

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

    public PraiseLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        //初始化图片资源
        drawables = new Drawable[4];
        Drawable red = ContextCompat.getDrawable(getContext(), R.drawable.nemo_heart_red);
        Drawable blue = ContextCompat.getDrawable(getContext(), R.drawable.nemo_heart_blue);
        Drawable orange = ContextCompat.getDrawable(getContext(), R.drawable.nemo_heart_orange);
        Drawable pink = ContextCompat.getDrawable(getContext(), R.drawable.nemo_heart_pink);

        drawables[0] = red;
        drawables[1] = blue;
        drawables[2] = orange;
        drawables[3] = pink;

        dHeight = red.getIntrinsicHeight();
        dWidth = red.getIntrinsicWidth();

        layoutParams = new LayoutParams(dWidth, dHeight);

        //点赞动画开始于布局底部
        layoutParams.addRule(CENTER_HORIZONTAL, TRUE);
        layoutParams.addRule(ALIGN_PARENT_BOTTOM, TRUE);
    }

    //获取组件大小
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        mHeight = getMeasuredHeight();
        mWidth = getMeasuredWidth();
    }
    //点击动画事件
    public void addFavorWithoutBiz() {
        ImageView imageView = new ImageView(getContext());
        imageView.setImageDrawable(drawables[random.nextInt(4)]);
        imageView.setLayoutParams(layoutParams);

        addView(imageView);
        if (mWidth > 0 && dHeight > 0) {
            Animator set = getBezierValueAnimator(imageView);
            set.addListener(new AnimEndListener(imageView));
            set.start();
        }

    }
    //添加点赞动画
    private ValueAnimator getBezierValueAnimator(View target) {
        //点赞动画的估值器,参数为贝塞尔曲线两个中间点
        BezierEvaluator evaluator = new BezierEvaluator(getPointF(2), getPointF(1));

        //参数1:估值器对象  参数2、3:三重贝塞尔曲线的起点和终止点。
        ValueAnimator animator = ValueAnimator.ofObject(evaluator, new PointF((mWidth - dWidth) / 2, mHeight - dHeight - 20), new PointF(random.nextInt(getWidth()), 0));
        animator.addUpdateListener(new BezierListenr(target));
        animator.setTarget(target);
        animator.setDuration(3000);
        return animator;
    }
    private PointF getPointF(int scale) {

        PointF pointF = new PointF();

        pointF.x = random.nextInt((mWidth - 50));//减去50 是为了控制 x轴活动范围,看效果 随意~~
        //再Y轴上 为了确保第二个点 在第一个点之上,我把Y分成了上下两半 这样动画效果好一些  也可以用其他方法
        pointF.y = random.nextInt((mHeight - 150)) / scale;
        return pointF;
    }


    //终止动画监听
    class AnimEndListener extends AnimatorListenerAdapter {
        private View target;
        public AnimEndListener(View target) {
            this.target = target;
        }

        @Override
        public void onAnimationEnd(Animator animation) {
            super.onAnimationEnd(animation);
            removeView((target));
        }
    }

    //属性动画的监听类,当数值更新时触发
    class BezierListenr implements ValueAnimator.AnimatorUpdateListener {
        private View target;

        public BezierListenr(View target) {
            this.target = target;
        }

        @Override

        public void onAnimationUpdate(ValueAnimator animation) {
            //这里获取到贝塞尔曲线计算出来的的x y值 赋值给view 这样就能让爱心随着曲线走啦
            PointF pointF = (PointF) animation.getAnimatedValue();
            target.setX(pointF.x);
            target.setY(pointF.y);
            // alpha动画
            target.setAlpha(1 - animation.getAnimatedFraction());
        }
    }

}
  1. 估值器==BezierEvaluator==的实现,这里使用三重贝塞尔曲线的公式计算。
public class BezierEvaluator implements TypeEvaluator<PointF> {

    private PointF pointF1;
    private PointF pointF2;

    public BezierEvaluator(PointF pointF1, PointF pointF2) {
        this.pointF1 = pointF1;
        this.pointF2 = pointF2;
    }

    @Override
    public PointF evaluate(float time, PointF startValue, PointF endValue) {
        float timeLeft = 1.0f - time;
        PointF point = new PointF();//结果

        point.x = timeLeft * timeLeft * timeLeft * (startValue.x)
                + 3 * timeLeft * timeLeft * time * (pointF1.x)
                + 3 * timeLeft * time * time * (pointF2.x)
                + time * time * time * (endValue.x);

        point.y = timeLeft * timeLeft * timeLeft * (startValue.y)
                + 3 * timeLeft * timeLeft * time * (pointF1.y)
                + 3 * timeLeft * time * time * (pointF2.y)
                + time * time * time * (endValue.y);

        return point;
    }
}
  1. 在xml布局文件中引入自定义布局
 <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <com.example.liubohua.myapplication.PraiseLayout
            android:id="@+id/animation_praise"
            android:layout_width="match_parent"
            android:layout_height="match_parent">
        </com.example.liubohua.myapplication.PraiseLayout>

        <ImageView
            android:id="@+id/animation_praise_img"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/nemo_heart_blue"
            android:layout_gravity="center|bottom"/>

    </FrameLayout>

  1. 在java代码实现
 private void initPraise() {
        animation_praise_img = (ImageView) findViewById(R.id.animation_praise_img);
        animation_praise = (PraiseLayout) findViewById(R.id.animation_praise);
        animation_praise_img.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                animation_praise.addFavorWithoutBiz();
            }
        });
    }

实现效果图:

点赞动画.gif

尾声

差不多Android常见的动画操作也就这些,通过简单的学习再加上庞大的脑洞就可以实现非常炫酷的效果,赶紧尝试一下吧。