View 属性动画

参考:

https://hencoder.com/ui-1-6/

https://mp.weixin.qq.com/s/62jNAKdxpS_dOfzfYxZOww

练习github地址:https://github.com/fandazeng/ViewDemo

简介

属性动画是 API11 新加入的特性,和 View 动画不同,它可以对任何对象做动画,甚至还可以没有对象,动画默认时间间隔 300ms,默认帧率 10ms/ 帧。其可以达到的效果是:在一个时间间隔内完成对对象从一个属性值到另一个属性值得改变。常用属性动画类 ValueAnimator 、 ObjectAnimator 和 AnimationSet ,其中ObjectAnimator 继承于ValueAnimator。

1.1 xml 实现方式

它的 xml 语法如下:

<set xmlns:android="http://schemas.android.com/apk/res/android"
 android:ordering=["sequentially"|"together"]>
<objectAnimator
    android:propertyName="string"
    android:duration="int"
    android:valueFrom="float|int|color"
    android:valueTo="float|int|color"
    android:startOffset="int"
    android:repeatCount="int"
    android:repeatMode=["restart"|"reverse"]
    android:valueType=["colorType"|"intType"]>

</objectAnimator>
<animator
    android:duration="int"
    android:valueFrom="float|int|color"
    android:valueTo="float|int|color"
    android:startOffset="int"
    android:repeatCount="int"
    android:repeatMode=["restart"|"reverse"]
    android:valueType=["colorType"|"intType"]>

</animator>
...
</set>

下面介绍一下各属性名称的含义。属性动画的核心类有3个类,AnimatorSet,ObjectAnimator 以及ValueAnimator 。

  1. <set>标签对应 AnimatorSet ,<set> 标签的 ordering 属性有两个候选值”sequentially”|”together”,分别表示 <set> 标签内的动画是按照前后顺序播放和同时播放。默认是 “together” 。

  2. <animatior> 对应 ValueAnimator ,属性如下:

  • android:duration:表示动画的时长
  • android:valueFrom:表示属性的起始值
  • android:valueTo:表示属性的结束值
  • android:startOffset:表示动画的延迟时间,动画开始后,需要延迟多少毫秒后才会真正播放该动画
  • android:repeatCount:表示动画的重复次数,默认值是0,为-1时,表示无限循环。
  • android:repeatMode:表示动画的重复播放模式,restart表示动画每次都是重新开始播放,reverse表示动画第1次播放完毕后,第2次会逆向播放,第3次又从头开始播放,以此类推。
  1. <objectAnimator> 对应 ObjectAnimator 。因为 ObjectAnimator 继承于 ValueAnimator ,所以拥有 ValueAnimator 所有属性。除外还有自己的属性,如下:
  • android:propertyName:表示属性动画作用对象的属性名称
  • android:valueType:表示android:propertyName的值的类型,分为intType,和floatType,分别代表整型数值和浮点型数值,若android:propertyName指定的属性表示的是颜色,那么无需指定android:valueType,系统会自动适配

具体 xml 示例:

xml 实现的属性动画需要放在 res/animator 目录下,没有则新建一个。

<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="500"
    android:propertyName="alpha"
    android:repeatCount="-1"
    android:repeatMode="reverse"
    android:startOffset="200"
    android:valueFrom="0.0"
    android:valueTo="1.0"
    android:valueType="floatType" />

代码调用如下:

//点击播放动画事件
mObjectAnimator = (ObjectAnimator) AnimatorInflater.loadAnimator(getContext(), R.animator.test_property_animator);
mObjectAnimator.setTarget(mMusicIcon);
mObjectAnimator.start();

1.2 代码实现方式

mObjectAnimator = ObjectAnimator.ofFloat(mMusicIcon, "alpha", 0, 1);
mObjectAnimator.setStartDelay(200);
mObjectAnimator.setDuration(500);
mObjectAnimator.setRepeatMode(ValueAnimator.REVERSE);
mObjectAnimator.setRepeatCount(ValueAnimator.INFINITE);

在实际开发中建议采用代码来实现属性动画,代码实现较简单,而有时候属性的起始值是无法提前确定的,需要在代码运行中动态变化来确定。

效果如下:

ValueAnimator

整个属性动画机制当中最核心的一个类。

mValueAnimator = ValueAnimator.ofFloat(0, 1).setDuration(500);
       mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
           @Override
           public void onAnimationUpdate(ValueAnimator animation) {
               Log.d("ValueAnimator", animation.getAnimatedValue() + "");
           }
       });

2.1 transLate 动画

xml 文件如下:

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="2000"
    android:interpolator="@android:anim/linear_interpolator"
    android:repeatMode="reverse"
    android:shareInterpolator="true">

    <translate
        android:fromXDelta="50%p"
        android:fromYDelta="50%p"
        android:repeatCount="-1"
        android:toXDelta="300"
        android:toYDelta="300" />

</set>

代码实现如下:

// 如果是Animation.RELATIVE_TO_PARENT 或 Animation.RELATIVE_TO_SELF ,
   则值要用百分比 0% -100%,默认是Animation.ABSOLUTE

   mAnimation = new TranslateAnimation(Animation.RELATIVE_TO_PARENT,0.5f,Animation.RELATIVE_TO_PARENT,
           0.2f,Animation.RELATIVE_TO_PARENT, 0.5f,Animation.RELATIVE_TO_PARENT, 0.2f);
   mAnimation.setRepeatCount(Animation.INFINITE);
   mAnimation.setDuration(2000);
   mAnimation.setRepeatMode(Animation.REVERSE);
   mAnimation.setInterpolator(new LinearInterpolator());

效果如下:

2.2 alpha 动画

xml 文件如下:

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1000"
    android:interpolator="@android:anim/linear_interpolator"
    android:repeatMode="reverse"
    android:shareInterpolator="true">

    <alpha
        android:fromAlpha="1.0"
        android:toAlpha="0.0"
        android:repeatCount="-1"/>

</set>

代码实现如下:

mAnimation = new AlphaAnimation(1, 0);
...

效果如下:

2.3 scale 动画

xml 文件如下:

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="2000"
    android:interpolator="@android:anim/linear_interpolator"
    android:repeatMode="reverse"
    android:shareInterpolator="true">

    <scale
        android:pivotX="50%"
        android:pivotY="50%"
        android:fromYScale="0.5"
        android:fromXScale="0.5"
        android:toXScale="1.5"
        android:repeatCount="-1"
        android:toYScale="1.5" />

</set>

代码实现如下:

mAnimation = new ScaleAnimation(0.5f, 1.5f, 0.5f, 1.5f,
            Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
...

效果如下:

2.4 rotate 动画

xml 文件如下:

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="2000"
    android:interpolator="@android:anim/linear_interpolator"
    android:repeatMode="reverse"
    android:shareInterpolator="true">

    <rotate
        android:fromDegrees="90"
        android:pivotX="50%"
        android:pivotY="50%"
        android:repeatCount="-1"
        android:toDegrees="360" />

</set>

代码实现如下:

mAnimation = new RotateAnimation(90f, 360f, Animation.RELATIVE_TO_SELF,
     0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
...

效果如下:

2.5 AnimationSet 动画集合

AnimationSet继承自Animation,是上面四种的组合容器管理类,没有自己特有的属性,他的属性继承自Animation,所以特别注意,当我们对set标签使用Animation的属性时会对该标签下的所有子控件都产生影响。

xml 文件如下:

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:interpolator="@android:anim/linear_interpolator"
    android:repeatMode="reverse"
    android:duration="1000"
    android:shareInterpolator="true">

    <translate
        android:fromXDelta="50%"
        android:fromYDelta="50%"
        android:repeatCount="-1"
        android:toXDelta="300"
        android:toYDelta="300" />

    <alpha
        android:fromAlpha="1.0"
        android:toAlpha="0.0"
        android:repeatCount="-1"/>

    <scale
        android:pivotX="50%"
        android:pivotY="50%"
        android:fromYScale="0.5"
        android:fromXScale="0.5"
        android:toXScale="1.5"
        android:repeatCount="-1"
        android:toYScale="1.5" />

    <rotate
        android:fromDegrees="90"
        android:pivotX="50%"
        android:pivotY="50%"
        android:repeatCount="-1"
        android:toDegrees="360" />

</set>

代码实现如下:

mTranslateAnimation = new TranslateAnimation(Animation.RELATIVE_TO_SELF,0.5f,Animation.RELATIVE_TO_PARENT,
        0.2f,Animation.RELATIVE_TO_SELF, 0.5f,Animation.RELATIVE_TO_PARENT, 0.2f);
mTranslateAnimation.setRepeatCount(Animation.INFINITE);

mAlphaAnimation = new AlphaAnimation(1, 0);
mAlphaAnimation.setRepeatCount(Animation.INFINITE);

mScaleAnimation = new ScaleAnimation(0.5f, 1.5f, 0.5f, 1.5f,
        Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
mScaleAnimation.setRepeatCount(Animation.INFINITE);

mRotateAnimation = new RotateAnimation(90f, 360f,
        Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
mRotateAnimation.setRepeatCount(Animation.INFINITE);

mAnimationSet = new AnimationSet(true);

mAnimationSet.addAnimation(mTranslateAnimation);
mAnimationSet.addAnimation(mRotateAnimation);
mAnimationSet.addAnimation(mScaleAnimation);
mAnimationSet.addAnimation(mAlphaAnimation);

mAnimationSet.setDuration(1000);
mAnimationSet.setRepeatMode(Animation.REVERSE);
mAnimationSet.setInterpolator(new LinearInterpolator());

效果如下:

LayoutAnimation

LayoutAnimation用于ViewGroup,为ViewGroup指定一个动画,这样它的所有子View在出场时都带有指定的动画效果。

xml 方式如下:

<?xml version="1.0" encoding="utf-8"?>
<layoutAnimation  xmlns:android="http://schemas.android.com/apk/res/android"
    android:animationOrder="normal"
    android:animation="@anim/layout_anim"
    android:delay="0.5">

</layoutAnimation>

layout_anim 如下:

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="2000"
    android:interpolator="@android:anim/linear_interpolator"
    android:repeatMode="reverse"
    android:shareInterpolator="true">

    <translate
        android:fromXDelta="0"
        android:fromYDelta="0"
        android:repeatCount="-1"
        android:toXDelta="100"
        android:toYDelta="0" />

    <rotate
        android:fromDegrees="0"
        android:pivotX="50%"
        android:pivotY="50%"
        android:repeatCount="-1"
        android:toDegrees="360" />

</set>

android:delay
表示子元素开始动画的延迟,比如子元素的入场时间周期为500ms,那么0.5 表示每个子元素都要延迟500*0.5=250ms后才会开始播放。总体来说就是第一个子元素延迟250ms播放,第二个子元素延迟500ms播放,后面的子元素以此类推。

android:animationOrder
表示子元素动画的顺序,有三种选项:normal,reverse,random,其中normal表示顺序显示,即排在前面的子元素先开始播放入场动画;reverse表示逆向显示,即排在后面的子元素先开始播放入场动画;random则表示随机播放入场动画。

使用方式有两种:

xml 方式使用,直接在ViewGroup内指定android:layoutAnimation属性。

<zeng.fanda.com.pratice9.view.LayoutAnimationLayout
      android:layout_width="match_parent"
      android:layoutAnimation="@anim/layout_anim_layout"
      android:layout_height="match_parent">

代码方式实现:

mAnimation = AnimationUtils.loadAnimation(getContext(), R.anim.layout_anim);
LayoutAnimationController controller = new LayoutAnimationController(mAnimation);
controller.setOrder(LayoutAnimationController.ORDER_NORMAL);
controller.setDelay(0.5f);
setLayoutAnimation(controller);
startLayoutAnimation();

效果如下:

View动画属性配置中 %以及%p的含义

  1. android:fromXDelta=”X”,X>0 表示View动画的开始位置是以当前View的原点向右偏移X个位置,同理,X<0时View动画的开始位置是以当前View的原点向左偏移X个位置。

  2. android:fromXDelta=”X%”,X>0 表示View动画的开始位置是以当前View的原点向右偏移View宽度的X%(View.widthX%)个位置,同理,X<0时View动画的开始位置是以当前View的原点向左偏移向右偏移View宽度的X%(View.widthX%)个位置

  3. android:fromXDelta=”X%p”,X>0 表示View动画的开始位置是以当前View的父View的原点向右偏移父View宽度的X%(View.Parent.widthX%)个位置,同理,X<0时View动画的开始位置是以当前View的父View的原点向左偏移父View宽度的X%(View.Parent.widthX%)个位置

View动画事件监听

mAnimation.setAnimationListener(new Animation.AnimationListener() {
            @Override
            public void onAnimationStart(Animation animation) {
                // 动画开始时回调
                Toast.makeText(getContext(),"onAnimationStart",Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onAnimationEnd(Animation animation) {
                // 动画结束时回调
                Toast.makeText(getContext(),"onAnimationEnd",Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onAnimationRepeat(Animation animation) {
                // 动画循环时,重新下一个循环时回调
                Toast.makeText(getContext(),"onAnimationRepeat",Toast.LENGTH_SHORT).show();
            }
        });

Activity之间的切换动画

有两种实现方式,在Activity中提供了overridePendingTransition(int enterAnim, int exitAnim) 方法,该方法接收两个参数,第一个参数是Activity进入时的动画,第二个参数是Activity退出时的动画。该方法一般写在startActivity()后和finish()后,如果我们想打开或者退出不显示动画,可将参数设置为0。

假设有A、B两界面,当前界面处于A,要打开B界面。
如果是写在startActivity()后,enterAnim 是B界面的进入动画,exitAnim 是A(当前)界面的退出动画。
如果是写在finish()后,enterAnim 是A界面的进入动画,exitAnim 是B(当前)界面的退出动画。

演示的动画没有实际的意义,只是为了显示出界面切换时动画的执行情况。

由上述分析可知,我们需要4个不同的动画文件,以下便是4个动画的 xml 文件:

B界面的进入动画(slide_in_right.xml):

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:interpolator="@android:anim/decelerate_interpolator">

    <translate
        android:duration="1300"
        android:fromXDelta="100%p"
        android:toXDelta="0%p" />

</set>

B界面的退出动画(slide_out_right.xml):

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1300"
    android:interpolator="@android:anim/decelerate_interpolator">

    <translate
        android:fromXDelta="0%p"
        android:toXDelta="100%p" />

</set>

A界面的进入动画(slide_in_left.xml):

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1300"
    android:interpolator="@android:anim/decelerate_interpolator">

    <translate
        android:fromXDelta="-40%p"
        android:toXDelta="0%p" />

    <alpha
        android:fromAlpha="0.5"
        android:toAlpha="1" />

</set>

A界面的退出动画(slide_out_left.xml):

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1300"
    android:interpolator="@android:anim/decelerate_interpolator">

    <translate

        android:fromXDelta="0%p"
        android:toXDelta="-100%p" />

    <alpha
        android:fromAlpha="1.0"
        android:toAlpha="0.5" />

</set>

代码实现 :

//打开界面
startActivity(new Intent(MainActivity.this, Pratice9Activity.class));
overridePendingTransition(R.anim.slide_in_right,R.anim.slide_out_left);

//关闭界面
finish();
overridePendingTransition(R.anim.slide_in_left,R.anim.slide_out_right);

不要动画,可以这样写
overridePendingTransition(0,0);

主题实现 :

我们可以在主题上统一我们的界面的切换动画,当你的界面应用上该主题时,动画即生效,无需写任何代码。

xml 文件如下:

<style name="MyCustomActivityTheme" parent="AppTheme">
    <item name="android:windowAnimationStyle">@style/AnimationStyle</item>
</style>
<style name="AnimationStyle">
    <item name="android:activityOpenEnterAnimation">@anim/slide_in_right</item>
    <item name="android:activityOpenExitAnimation">@anim/slide_out_left</item>
    <item name="android:activityCloseEnterAnimation">@anim/slide_in_left</item>
    <item name="android:activityCloseExitAnimation">@anim/slide_out_right</item>
</style>

通过 windowAnimationStyle 来引用需要的动画,结合上面的分析即可知道 theme 中 activityOpenEnterAnimation 等属性对应的动画意思。

在你要使用切换动画的界面中引用该主题即可生效:

<activity android:name=".MainActivity"  
android:theme="@style/MyCustomActivityTheme">

<activity android:name=".ui.Pratice9Activity" 
    android:theme="@style/MyCustomActivityTheme"/>

效果如下:

注意

View动画的主体是View,更准确的说是View的副本(影子),View动画更改的只是显示,其x,y坐标仍然没有改变,响应事件的位置没有改变,也就是说view本身并没有改变。
也因此,不要使用View动画做交互性操作,例如点击。现在View动画已经很少人使用了,不过View动画简单易用,可以用来做一些简单的不需要交互的动画。

xml动画文件里的数值不能加 “f” ,比如:

<alpha
     android:duration="1300"
     android:fromAlpha="1.0f"
     android:toAlpha="0.85f" />

不能使用 1.0f 和0.85f ,正常的文件属性如下:

<alpha
     android:duration="1300"
     android:fromAlpha="1.0"
     android:toAlpha="0.85" />