View 动画

参考:

https://www.jianshu.com/p/87373134481b

https://mp.weixin.qq.com/s/KiZY2HJDoV2HNKrR_2Fp1A

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

Frame Animation(帧动画)

Frame Animation其实就是将一系列的图片一张一张的展示出来,有两种实现方式。

1.1 xml 实现方式

它的 xml 语法如下:

<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android" 
android:oneshot="["true" | "false"]>
    <item 
        android:drawable="@[package:]drawable/drawable_resourece_name" 
        android:duration="integer"/>
</animation-list>

属性比较简单,animation-list 是动画的根元素,在根元素中的oneshot属性表示动画执行次数(默认是false),如果设置为true表示只播放一次,如果false则表示会一直循环执行。在根元素下有item元素,该元素就是我们要添加的图片,每一个item表示一帧,item下的drawable就是我们的图片资源,duration就是该帧动画执行的时间。例如:

<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
 android:oneshot="false">
    <item android:drawable="@mipmap/run1" android:duration="200"/>
    <item android:drawable="@mipmap/run2" android:duration="200"/>
    <item android:drawable="@mipmap/run3" android:duration="200"/>
    <item android:drawable="@mipmap/run4" android:duration="200"/>
    <item android:drawable="@mipmap/run5" android:duration="200"/>
    <item android:drawable="@mipmap/run6" android:duration="200"/>
    <item android:drawable="@mipmap/run7" android:duration="200"/>
    <item android:drawable="@mipmap/run8" android:duration="200"/>
</animation-list>

使用方法如下:

//可以在 xml 属性中设置    
mFragmeAnimationIcon.setBackgroundResource(R.drawable.frame_run_animation);
mAnimationDrawable = (AnimationDrawable) mFragmeAnimationIcon.getBackground();
mAnimationDrawable.start();

1.2 代码实现方式

private int[] drawableRes = new int[]{R.mipmap.run1,R.mipmap.run2,R.mipmap.run3,R.mipmap.run4,
        R.mipmap.run5,R.mipmap.run6,R.mipmap.run7,R.mipmap.run8};

private void startFrameAnimation() {
    mAnimationDrawable = new AnimationDrawable();
    for (int resId : drawableRes) {
        mAnimationDrawable.addFrame(getResources().getDrawable(resId),200);
    }
    //设置不是执行一次,即会永久循环播放动画
    mAnimationDrawable.setOneShot(false);
    mFragmeAnimationIcon.setImageDrawable(mAnimationDrawable);
    mAnimationDrawable.start();

}

以上两种方式的实现都需要注意 OOM 问题,其次还有一些其他的方法,比如动画是否进行中,是否要停止动画,例如:

if (mAnimationDrawable != null && mAnimationDrawable.isRunning()) {
        mAnimationDrawable.stop();
    }

效果如下:

Tween Animation(补间动画)

主要分为四种,分别是位移、缩放、旋转、透明度。它们的语法如下图所示:

<?xml version="1.0" encoding="utf-8"?>    
<set xmIns:android="http://schemas. android.com/apk/res/android"
    //插值器
    android: interpolator="@[package:]anim/interpolator resource'
    //动画结束后View是否停留在结束的位置
    android: fillAfter=["true" | "false"]
    //重复的模式,默认为restart,即重头开始重新运行,reverse即从结束开始向前重新运行
    android: repeatMode="restart/reverse"
    //子元素是否共用此插值器
    android: shareInterpolator=["true" | "false"] >

    <alpha
        //开始透明度0.0 (全透明)到1.0 (完全不透明)
        android: fromAlpha="float"
        android: toAlpha="float"
        //动画执行时间
        android: duration="integer" />

    <scale
        //1的时候表示不缩放,小于1缩小,大于1放大
        android: fromXScale="float"        
        android: toxScale="float"    
        android: fromYScale="float"    
        android: toYScale="float"
        //缩放中心,也可以穿fraction值    
        android: pivotX="float"    
        android: pivotY="float"
        //动画执行时间
        android: duration="integer" />

    <transLate
        //表示X的起始值
        android: fromXDelta="float/fraction"
        android: toXDelta="float"    
        android: fromYDelta="float"
        android: toYDelta="float"
        android:duration=" integer" />

    <rotate    
        //起始的旋转角度
        android: fromDegrees="float"
        android: toDegrees="float"
        android: pivotX="fLoat"
        android: pivoty="fLoat"
        android: duration=" integer" >
    ...
</set>

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" />