欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android动画全解

程序员文章站 2022-07-03 10:39:34
...

Android View加入动画之后使其对用户更加友好,用户体验也得到极大的增强,特别是Android 3.0之后,加入的动画新成员——属性动画,使其更加具备交互的特性,而且通过动画可以做出各种比较炫比较酷的效果。如果没有动画,那么View的表现比较生硬,给用户的体验很不友好。Android动画使其可以做到可以和iphone一样的友好体验,在Android5.0之后,加入了Android Material Design,其用户体验得到了更加友好的发挥,甚至超过了iphone,所以Android动画是学习Android的重点之一。

Android动画可以分为三种:View动画、帧动画和属性动画,上面已经提到了属性动画是Android API11之后新增的,如果想要兼容API11以下的版本,可以使用NineOldAndroids动画库。

View动画

View动画是通过View的平移、旋转、缩放和改变透明度来实现动画,这也是一种渐进式动画。

View动画原理:每次绘制视图时,View的所在ViewGroup中的drawChild函数获取该View的Animation的Transformation值,然后调用canvas.concat(TransformToApply .getMatrix()),然后通过矩阵运算完成动画帧,如果动画没有完成,就继续调用invalidate()函数,启动下次绘制来驱动动画,从而完成整个动画的绘制。

View动画提供了TranslateAnimation、RotateAnimation、ScaleAnimation和AlphaAnimation四种动画。通过这四种动画基本上能够完成所有的View的特效效果,不过View动画明显的缺点就是不具备交互性。而Android3.0新增的属性动画就具有交互性。

TranslateAnimation

TranslateAnimation也就是平移动画,显然该动画能够将View在水平或者垂直方向上进行移动。

如在X轴上移动200px


    /**
     * translateAnimation
     * @return
     */
    private Animation getTranslate(){

        /*TranslateAnimation(int fromXType, float fromXValue,
                                int toXType, float toXValue,
                                int fromYType,float fromYValue,
                                int toYType, float toYValue)
                               */
        TranslateAnimation translateAnimation = new TranslateAnimation(
                Animation.RELATIVE_TO_SELF, 0.5f,
                Animation.RELATIVE_TO_SELF, 200,
                Animation.RELATIVE_TO_SELF, 0,
                Animation.RELATIVE_TO_SELF, 0);
        translateAnimation.setDuration(300);//动画时间
        translateAnimation.setFillAfter(false);//动画结束后View是否保持动画结束时的状态
        return translateAnimation;
    }
复制代码

启动该动画

TranslateAnimation anim = (TranslateAnimation) getTranslate(); mTargetView.startAnimation(anim);

也可以在xml中写:在项目res下新建一个anim目录,在目录下新建一个.xml文件。

<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
           android:duration="300"
           android:fromXDelta="0%p"
           android:interpolator="@android:anim/accelerate_decelerate_interpolator"
           android:toXDelta="100%p"/>
复制代码

android:fromXDelta:起始点X轴坐标,可以是数值、百分数、百分数p 三种样式,比如 30、30%、30%p。 android:fromYDelta:起始点Y轴从标,可以是数值、百分数、百分数p 三种样式; 当为数值时,表示在当前View的左上角,即原点处加上50px; 如果是50%,表示在当前控件的左上角加上自己宽度的50%; 如果是50%p,表示在当前的左上角加上父控件宽度的50%。 android:toXDelta:结束点X轴坐标 android:toYDelta:结束点Y轴坐标

使用:

TranslateAnimation anim = (TranslateAnimation) AnimationUtils.loadAnimation(this, R.anim.my_animation); mTargetView.startAnimation(anim);

RotateAnimation

RotateAnimation旋转动画,能够将View进行旋转,达到动画的效果。

    /**
     * 围绕自身中点
     * 旋转270度
     *
     * @return
     */
    private Animation getRotate() {
        RotateAnimation rotateAnimation = new RotateAnimation(
                0, 270,//开始角度和旋转的到的度数
                Animation.RELATIVE_TO_SELF, 0.5f,//x
                Animation.RELATIVE_TO_SELF, 0.5f);//y
        rotateAnimation.setDuration(1000);
        rotateAnimation.setFillAfter(true);
        return rotateAnimation;
    }
复制代码

在xml中表示:

<?xml version="1.0" encoding="utf-8"?>
<rotate
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:fromDegrees="0"
    android:toDegrees="270"
    android:pivotX="50%"
    android:pivotY="50%"
    android:duration="300"
    android:fillAfter="true"/>
复制代码

ScaleAnimation

ScaleAnimation对View进行缩放的动画。

    /**
     * 缩放
     *
     * @return
     */
    private Animation getScaleAnim() {
       /* (float fromX, float toX,
        float fromY, float toY,
        int pivotXType, float pivotXValue,
        int pivotYType, float pivotYValue)*/
        ScaleAnimation scaleAnimation = new ScaleAnimation(
                1.0f, 0.0f, 1.0f, 0.0f,//xy轴上的缩放起始值
                Animation.RELATIVE_TO_SELF, 0.5f,//x轴的缩放中心
                Animation.RELATIVE_TO_SELF, 0.5f);//y轴的缩放中心
        scaleAnimation.setDuration(300);
        scaleAnimation.setFillAfter(false);
        return scaleAnimation;
    }
复制代码

在xml中表示:

<?xml version="1.0" encoding="utf-8"?>
<scale
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:pivotX="50%"
    android:pivotY="50%"
    android:fromXScale="1.0"
    android:fromYScale="1.0"
    android:toXScale="0.0"
    android:toYScale="0.0"
    android:duration="300"
    android:fillAfter="true"/>
复制代码

android:fromXScale:起始的X方向上相对自身的缩放比例; android:toXScale:结尾的X方向上相对自身的缩放比例,浮点值; android:fromYScale:起始的Y方向上相对自身的缩放比例,浮点值, android:toYScale:结尾的Y方向上相对自身的缩放比例,浮点值; android:pivotX:缩放起点X轴坐标,可以是数值、百分数、百分数p 三种样式; android:pivotY:缩放起点Y轴坐标,取值及意义跟android:pivotX一样。

ScaleAnimation

AlphaAnimation是可以对View进行透明度变化的动画。

    /**
     * 改变透明度动画
     * @return
     */
    private Animation getAlpha(){
        AlphaAnimation alphaAnimation = new AlphaAnimation(1.0f, 0.0f);
        alphaAnimation.setDuration(300);
        alphaAnimation.setFillAfter(true);
        return alphaAnimation;
    }
复制代码

在xml中表示:

<?xml version="1.0" encoding="utf-8"?>
<alpha
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:fromAlpha="1.0"
    android:toAlpha="0.0"
    android:duration="300"
    android:fillAfter="true"/>
复制代码

以上便是View动画提供了TranslateAnimation、RotateAnimation、ScaleAnimation和AlphaAnimation四种动画,上文中的动画已经经过验证的了。但是上文中的动画都是单一形式的,那么有没有提供一种方法,能够混合一起播放以上View动画提供的四种动画的呢?答案是:当然有,那就是使用AnimationSet动画集合。

网上也有相当多的文章是关于AnimationSet的使用和介绍的,那么什么是AnimationSet呢?其实就是一个动画集合,能够将View动画的提供的几种动画可以一起播放,从而产生更炫的动画效果。AnimationSet也可以在xml文件中设置,其对应的标签就是标签。

AnimationSet set = new AnimationSet(false);
ScaleAnimation anim01 = (ScaleAnimation) getScaleAnim();
RotateAnimation anim02 = (RotateAnimation) getRotate();
AlphaAnimation anim03 = (AlphaAnimation) getAlpha();
set.addAnimation(anim01);
set.addAnimation(anim02);
set.addAnimation(anim03);
set.setDuration(300);
mTargetView.startAnimation(set);
复制代码

在xml中表示:

<?xml version="1.0" encoding="utf-8"?>
<set
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="300"
    android:fillAfter="true">
    <alpha
        android:toAlpha="0.0"
        android:fromAlpha="1.0"/>
    <rotate
        android:fromDegrees="0"
        android:toDegrees="270"
        android:pivotX="50%"
        android:pivotY="50%"/>
    <scale
        android:pivotY="50%"
        android:pivotX="50%"
        android:fromXScale="1.0"
        android:fromYScale="1.0"
        android:toXScale="0.0"
        android:toYScale="0.0"/>
</set>
复制代码
set的属性有:

android:duration:动画持续时间,以毫秒为单位 ;

android:fillAfter:如果设置为true,控件动画结束时,将保持动画最后时的状态;

android:fillBefore:如果设置为true,控件动画结束时,还原到开始动画前的状态;

android:fillEnabled:与android:fillBefore效果相同,都是在动画结束时,将控件还原到初始化状态;

android:repeatCount 重复次数;

android:repeatMode:重复类型,有reverse和restart两个值,reverse表示倒序回放,restart表示重新放一遍,必须与repeatCount一起使用才能看到效果。因为这里的意义是重复的类型,即回放时的动作;

android:interpolator:设定插值器。

使用:

AnimationSet set = (AnimationSet) AnimationUtils.loadAnimation(this, R.anim.my_animation); mTargetView.startAnimation(set);

上文中就是View动画的种类和集合的基本用法,除了以上的知识还有一个重要的知识点就是动画的监听。动画的监听可实现很多需求,比如在动画开始或者结束后要做的事情,或者在动画不断回调的方法onAnimationRepeat中实现一些动画不能实现的效果。

添加动画监听很简单,如下所示:

set.setAnimationListener(new Animation.AnimationListener() {
      @Override
      public void onAnimationStart(Animation animation) {

      }

      @Override
      public void onAnimationEnd(Animation animation) {

      }

      @Override
      public void onAnimationRepeat(Animation animation) {

       }
 });
复制代码

自定义属性动画

在View动画中除了上文中的四种动画之外,其实自定义动画也算是View动画的一种,自定义需要继承Animation类,并重写覆盖initialize和applyTransformation方法。在initialize方法中主要是做一些初始化的工作,applyTransformation通过矩阵变换来实现动画效果,一般采用Camera来简化矩阵变换过程。

applyTransformation(float interpolatedTime, Transformation t)方法有两个参数 interpolatedTime:动画当前完成的百分比和当前时间所对应的插值所计算得来的,取值0到1.0,也就是插值器的时间因子。

Transformation :矩阵的封装类,可以使用这个类获得当前的矩阵对象

Matrix matrix = t.getMatrix();

然后通过矩阵变化实现动画。

public class MyAnimation extends Animation {

    private float mWidth;
    private float mHeight;
    private Camera mCamera;
    private float mRorateX = 270.0f;

    @Override
    public void initialize(int width, int height, int parentWidth, int parentHeight) {
        super.initialize(width, height, parentWidth, parentHeight);
        setDuration(300);
        setFillAfter(true);
        mWidth = width/2;
        mHeight = height/2;
        mCamera = new Camera();
    }

    @Override
    protected void applyTransformation(float interpolatedTime, Transformation t) {
        super.applyTransformation(interpolatedTime, t);
        Matrix matrix = t.getMatrix();
        mCamera.save();
        mCamera.rotateX(mRorateX*interpolatedTime);
        mCamera.getMatrix(matrix);
        mCamera.restore();
        //通过pre方法设置矩阵作用前的偏移量来改变旋转中心
        matrix.preTranslate(mWidth,mHeight);
        matrix.postTranslate(-mWidth,-mHeight);
    }
}
复制代码

以上代码就是沿着X轴方向旋转。

帧动画

帧动画是顺序播放一组预先定义好的图片,类似电影,逐帧播放图片。

<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
    android:oneshot="false">
    <item android:drawable="@drawable/loadingmore01" android:duration="30"/>
    <item android:drawable="@drawable/loadingmore02" android:duration="30"/>
    <item android:drawable="@drawable/loadingmore03" android:duration="30"/>
    <item android:drawable="@drawable/loadingmore04" android:duration="30"/>
    <item android:drawable="@drawable/loadingmore05" android:duration="30"/>
    <item android:drawable="@drawable/loadingmore06" android:duration="30"/>
    <item android:drawable="@drawable/loadingmore07" android:duration="30"/>
    <item android:drawable="@drawable/loadingmore08" android:duration="30"/>
    <item android:drawable="@drawable/loadingmore09" android:duration="30"/>
    <item android:drawable="@drawable/loadingmore10" android:duration="30"/>
    <item android:drawable="@drawable/loadingmore11" android:duration="30"/>
    <item android:drawable="@drawable/loadingmore12" android:duration="30"/>
    <item android:drawable="@drawable/loadingmore13" android:duration="30"/>
    <item android:drawable="@drawable/loadingmore14" android:duration="30"/>
    <item android:drawable="@drawable/loadingmore15" android:duration="30"/>
    <item android:drawable="@drawable/loadingmore16" android:duration="30"/>
    <item android:drawable="@drawable/loadingmore17" android:duration="30"/>
    <item android:drawable="@drawable/loadingmore18" android:duration="30"/>
    <item android:drawable="@drawable/loadingmore19" android:duration="30"/>
    <item android:drawable="@drawable/loadingmore20" android:duration="30"/>
    <item android:drawable="@drawable/loadingmore21" android:duration="30"/>
    <item android:drawable="@drawable/loadingmore22" android:duration="30"/>
    <item android:drawable="@drawable/loadingmore23" android:duration="30"/>
    <item android:drawable="@drawable/loadingmore24" android:duration="30"/>
    <item android:drawable="@drawable/loadingmore25" android:duration="30"/>
    <item android:drawable="@drawable/loadingmore26" android:duration="30"/>
    <item android:drawable="@drawable/loadingmore27" android:duration="30"/>

</animation-list>
复制代码

使用

animView = new ImageView(context); animView.setImageResource(R.drawable.anim_loading_more); animationDrawable = (AnimationDrawable) animView.getDrawable(); animationDrawable.start();

帧动画的使用比较简单,以上就是定义一个帧动画并使用。

属性动画

属性动画是Android API11新加入的动画,属性动画能够对任何对象做动画,只要对象有这个属性,并且提供get和set方法即可,因为是API11才加入的动画,所以如果想向下兼容,可以使用nineoldandroids动画库来兼容。

属性动画提供了ValueAnimator、ObjectAnimator和AnimatorSet,通过他们可以使对象实现炫丽的动画效果。属性动画有一个最大的特点就是其可以实现与用户交互,这也是其他动画所不具备的特点。

ObjectAnimator

ObjectAnimator anim = ObjectAnimator.ofFloat(mTargetView,"translationY",0,2000,0); anim.start();

这是ObjectAnimator的简单用法,但是已经显示了动画效果。

ofFloat方法的参数分析:ofFloat(Object target, String propertyName, float... values) 第一个参数:动画对象; 第二个参数:对象的属性,要执行动画的属性,这里我改变的是Y轴上的移动距离; 第三个参数:可变参数,可以有多个值,即对象属性要执行动画的值。上例中表示的是属性动画在Y轴上先移动到2000px的位置,然后再执行动画移动到初始位置。

注意:这里的ObjectAnimator动画并没有指定执行时间,是因为属性动画的默认时间间隔300ms,默认帧率为10ms/帧。

ObjectAnimator anim = ObjectAnimator.ofFloat(mTargetView,"translationY",0,2000,-200,0); anim.setDuration(500); anim.start();

上例执行的动画会有弹动的效果,动画执行translationY,先移动到2000px的位置,然后再移动-200px的位置,最后回到初始的位置。而且设定了动画执行的时间。

旋转动画:

ObjectAnimator anim = ObjectAnimator.ofFloat(mTargetView,"rotation",0,360,-200,0);
anim.setDuration(2000);
anim.start();
复制代码

改变透明度:

ObjectAnimator anim = ObjectAnimator.ofFloat(mTargetView,"alpha",1.0f,0.0f,0.8f);
anim.setDuration(2000);
anim.start();
复制代码

缩放:

ObjectAnimator anim = ObjectAnimator.ofFloat(mTargetView, "scaleX",1,0,1);
anim.setDuration(2000);
anim.start();

ObjectAnimator anim1 = ObjectAnimator.ofFloat(mTargetView, "scaleY",1,0,1);
anim1.setDuration(2000);
anim1.start();
复制代码

第三参数表示缩放的倍数。

改变背景颜色

ObjectAnimator anim = ObjectAnimator.ofInt(mStart, "backgroundColor",0xff0000,0xffff8080,0xff000000);
anim.setDuration(2000);
anim.start();
复制代码

ObjectAnimator 同时执行多个属性动画:

PropertyValuesHolder px = PropertyValuesHolder.ofFloat("scaleX",1,0,1);
PropertyValuesHolder py = PropertyValuesHolder.ofFloat("scaleY",1,0,1);
ObjectAnimator anim = ObjectAnimator.ofPropertyValuesHolder(mTargetView, px,py);
anim.setDuration(2000);
anim.start();
复制代码

ValueAnimator

ValueAnimator只针对值,只是对值做动画运算,而不是针对控件,没有跟任何的控件相关联,需要监听ValueAnimator的动画过程来自己对控件做操作。

ValueAnimator anim = ValueAnimator.ofFloat(0,2000,-200,0);
anim.setDuration(2000);
anim.start();
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
       @Override
       public void onAnimationUpdate(ValueAnimator valueAnimator) {
            int curValueFloat = (int)valueAnimator.getAnimatedValue();
             //layout(int l, int t, int r, int b)
              Log.i("tag","value"+curValueFloat);
       }
 });
复制代码

读者可以参考自定义控件三部曲之动画篇(四)——ValueAnimator基本使用和 Android 属性动画(Property Animation) 完全解析 (上)这两篇文章,就已足够了解ValueAnimator了。

AnimatorSet

AnimatorSet是属性动画的集合,可以给View设置一组的属性动画,也可以指定播放顺序,是否一起播放或者是否延迟播放。

PropertyValuesHolder px = PropertyValuesHolder.ofFloat("scaleX",1.0f,0.0f,1.0f);
PropertyValuesHolder py = PropertyValuesHolder.ofFloat("scaleY",1.0f,0.0f,1.0f);
ObjectAnimator anim = ObjectAnimator.ofPropertyValuesHolder(mTargetView, px,py);

ObjectAnimator scale = ObjectAnimator.ofFloat(mTargetView,"rotation",0.0f,360.0f,-360.0f);

AnimatorSet set = new AnimatorSet();
set.setDuration(2000);
set.playTogether(anim,scale);//一起播放
set.start();
复制代码

使用playTogether方法,可以设置一起播放设置进去的动画。也可以使用以下语句设置一起播放动画

set.play(anim).with(scale);

按顺序播放动画:

set.playSequentially(anim,scale);

除了使用playSequentially,还可以使用如下语句来设置按顺序播放:

set.play(scale).after(anim);

动画监听AnimatorListener

ObjectAnimator translate = ObjectAnimator.ofFloat(goDescri, "translationX", -50);
translate.setInterpolator(new AccelerateInterpolator());
translate.setDuration(1500);

ObjectAnimator alpha1 = ObjectAnimator.ofFloat(goDescri, "alpha", 0.0f,1.0f);
alpha1.setInterpolator(new AccelerateInterpolator());
alpha1.setDuration(1000);

ObjectAnimator alpha2 = ObjectAnimator.ofFloat(goDescri, "alpha", 1.0f,0.0f);
alpha2.setInterpolator(new AccelerateInterpolator());
alpha2.setDuration(500);

AnimatorSet mAnimatorSet = new AnimatorSet();
mAnimatorSet.setInterpolator(new AccelerateInterpolator());
mAnimatorSet.play(translate).with(alpha1);
mAnimatorSet.play(alpha2).after(alpha1);
mAnimatorSet.addListener(new Animator.AnimatorListener()
{
	@Override
	public void onAnimationStart(Animator animation)
	{

	}

	@Override
	public void onAnimationEnd(Animator animation)
	{
		mAnimatorSet.start();
	}

	@Override
	public void onAnimationCancel(Animator animation)
	{

	}

	@Override
	public void onAnimationRepeat(Animator animation)
	{

	}
});
复制代码

上述代码通过监听AnimatorListener,在动画结束时重新启动动画,而从达到动画顺序循环播放的效果。

循环播放还可以如下设置:

PropertyValuesHolder px = PropertyValuesHolder.ofFloat("scaleX",1.0f,0.0f,1.0f);
PropertyValuesHolder py = PropertyValuesHolder.ofFloat("scaleY",1.0f,0.0f,1.0f);
ObjectAnimator anim = ObjectAnimator.ofPropertyValuesHolder(mTargetView, px,py);
anim.setRepeatCount(ObjectAnimator.INFINITE);//播放次数
anim.setRepeatMode(ObjectAnimator.REVERSE);//播放顺序,顺序和倒叙

ObjectAnimator scale = ObjectAnimator.ofFloat(mTargetView,"rotation",0.0f,360.0f,-360.0f);
scale.setRepeatCount(ObjectAnimator.INFINITE);
scale.setRepeatMode(ObjectAnimator.REVERSE);

AnimatorSet set = new AnimatorSet();
set.setDuration(1000);
set.playTogether(scale,anim);
set.start();
复制代码

通过设置播放次数anim.setRepeatCount(ObjectAnimator.INFINITE); 并且设置播放顺序anim.setRepeatMode(ObjectAnimator.REVERSE);来达到循环播放动画,这两个函数在AnimatorSet时没有。

插值器和估值器

插值器(Interpolator)可以分为时间插值器(TimeInterpolator)、线性插值器(LinearInterpolator)、加速减速插值器(AccelerateDecerateInterpolator)、减速插值器(DecerateInterpolator)。

时间插值器:根据时间流逝的百分比计算出当前属性值改变的百分比; 线性插值器:使动画匀速; 加速减速插值器:使动画两头慢,中间快; 减速插值器:使动画越来越慢。

时间插值器

根据时间流逝的百分比计算出当前属性值改变的百分比

AnimatorSet set = new AnimatorSet();
set.setDuration(2000);
set.playTogether(scale,anim);
set.setInterpolator(new TimeInterpolator() {
    @Override
    public float getInterpolation(float v) {
          Log.i("tag","v----------->"+v);
          return v;
  }
});
复制代码

加速减速估值器

AnimatorSet set = new AnimatorSet();
set.setDuration(2000);
set.playTogether(scale,anim);
set.setInterpolator(new AccelerateDecelerateInterpolator());
set.start();
复制代码

自定义估值器

public class DecelerateAccelerateInterpolator implements TimeInterpolator {

        @Override
        public float getInterpolation(float input) {
            float result;
            if (input <= 0.5) {
                result = (float) (Math.sin(Math.PI * input)) / 2;
            } else {
                result = (float) (2 - Math.sin(Math.PI * input)) / 2;
            }
            return result;
        }
    }
复制代码

以上自定义插值器实现了先减速后加速。

使用

set.setInterpolator(new DecelerateAccelerateInterpolator());

读者可以阅读这篇文章,此文章详细的介绍了插值器和估值器[Android 动画:你真的会使用插值器与估值器吗?(含详细实例教学)(http://www.jianshu.com/p/2f19fe1e3ca1)

估值器

估值器(TypeEvaluator):根据当前属性的改变的百分比来计算改变后的属性值,系统预置的估值器有:IntEvaluator(针对整形属性)、FloatEvaluator(针对浮点型属性)和ArgbEvaluator(针对Color属性)。

对任意对象属性做动画

既然属性动画能够对任何对象做动画,只要对象有这个属性,并且提供get和set方法即可。 如果这个对象没有get和set方法,怎么办呢? 1)给对象加上get和set方法; 2)用个类来包装原始对象,提供get和set方法;

private static class ViewWrapper{
        private View target;

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

        public int getWidth(){
            return target.getLayoutParams().width;
        }
        public void setWidth(int width){
            target.getLayoutParams().width = width;
            target.requestLayout();
        }


    }
复制代码

使用

ViewWrapper wrapper = new ViewWrapper(mTargetView); ObjectAnimator.ofInt(wrapper,"width",2000).start();

除了以上的方法外还可以使用ValueAnimator来监听值的改变,然后做改变,达到动画的效果。

关于这一点知识,读者可以参考《Android开发艺术探索》的第七章《Android动画深入分析》。

ViewAnimationUtils

Android5.0新特性增加了一种动画框架,这就是ViewAnimationUtils,我们可以利用ViewAnimationUtils来做一些动画效果,比如水波纹,揭露等动画效果。

public final class ViewAnimationUtils {
    private ViewAnimationUtils() {}
    public static Animator createCircularReveal(View view,
            int centerX,  int centerY, float startRadius, float endRadius) {
        return new RevealAnimator(view, centerX, centerY, startRadius, endRadius);
    }
}
复制代码

上面代码就是ViewAnimationUtils类全部代码,该动画工具只有createCircularReveal方法来创建动画效果。从方法中,可以知道最后交给了RevealAnimator类来完成动画效果。

createCircularReveal方法说明:

view:动画作用的View;

centerX:扩散的中心点的X轴坐标;

centerY:扩散的中心点的Y轴坐标;

startRadius:开始扩散初始半径;

endRadius:扩散结束半径;

使用:

Animator animator = ViewAnimationUtils.createCircularReveal(mStart, mStart.getWidth()/2, mStart.getHeight()/2, 0, mStart.getHeight());
animator.setDuration(1000);
animator.setInterpolator(new AccelerateInterpolator());
animator.start();
复制代码

上面代码就是mStart做水波纹扩散的效果。

mStart的揭露动画:

Animator animator = ViewAnimationUtils.createCircularReveal(btn, 0, 0, 0, (float)Math.hypot(btn.getWidth(), btn.getHeight()));
animator.setDuration(1000);
animator.setInterpolator(new AccelerateInterpolator());
animator.start();
复制代码

转场动画

Android的转场动画分为两种:普通转场和共享元素转场。

普通转场

普通转场通过overridePendingTransition设置Activity的关闭和显示动画,如下所示:

startActivity(new Intent(this, Animator2Activity.class));
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
复制代码

通过以上代码,跳转Activity时:当前的Activity动画淡出,新的Activity淡入,完成淡出淡入的Activity转场动画。

Activity退出时同样可以设置转场动画

    @Override
    public void onBackPressed() {
        super.onBackPressed();
        overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
    }
复制代码

这是API21系统自带的转场动画,只要在API21或者以上才有。 fade_in:

<alpha xmlns:android="http://schemas.android.com/apk/res/android"
        android:interpolator="@interpolator/decelerate_quad"
        android:fromAlpha="0.0" android:toAlpha="1.0"
        android:duration="@android:integer/config_longAnimTime" />
复制代码

fade_out:

<alpha xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@interpolator/accelerate_quad" 
    android:fromAlpha="1.0"
    android:toAlpha="0.0"
    android:duration="@android:integer/config_mediumAnimTime" 
/>
复制代码

滑入滑出转场

startActivity(new Intent(this, Animator2Activity.class));
overridePendingTransition(android.R.anim.slide_in_left,android.R.anim.slide_out_right);
复制代码

除了以上系统自带的转场动画,也可以在res的anim目录下定义动画xml来实现自定义的转场动画。

scale_in.xml

<?xml version="1.0" encoding="utf-8"?>
<scale
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="@android:integer/config_longAnimTime"
    android:fromXScale="0%"
    android:fromYScale="0%"
    android:toYScale="100%"
    android:pivotX="50%"
    android:pivotY="50%"
    android:toXScale="100%"/>
复制代码

scale_out.xml

<?xml version="1.0" encoding="utf-8"?>
<scale
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="@android:integer/config_longAnimTime"
    android:fromXScale="100%"
    android:fromYScale="100%"
    android:pivotX="50%"
    android:pivotY="50%"
    android:toXScale="0%"
    android:toYScale="0%"/>
复制代码

使用的时候直接在overridePendingTransition指定动画即可

startActivity(new Intent(this, Animator2Activity.class));
overridePendingTransition(R.anim.scale_in, R.anim.scale_out);
复制代码

底部滑入滑出 slide_in_botton.xml

<?xml version="1.0" encoding="utf-8"?>
<translate
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="@android:integer/config_longAnimTime"
    android:fromYDelta="100%"
    android:toYDelta="0%"
    />
复制代码

slide_out_botton.xml

<?xml version="1.0" encoding="utf-8"?>
<translate
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="@android:integer/config_longAnimTime"
    android:fromYDelta="0%"
    android:toYDelta="100%"
    />
复制代码

共享元素转场

共享元素转场是指:可以把两个Activity当中的相同的元素关联起来做连贯的变换动画。 共享元素转场是Android5.0或以上才显示的,而使用共享元素转场需要条件: A、必须给两个Activity设置Window.FEATURE_CONTENT_TRANSITIONS,让Activity允许使用转场动画。 而设置Window.FEATURE_CONTENT_TRANSITIONS有两个方法: 1)通过在setContentView方法之前设 getWindow().requestFeature(Window.FEATURE_CONTENT_TRANSITIONS); 2)在主题修改 true

B、给两个Activity当中的共享元素view都设置同一个名字(android:transitionName)

共享元素分为单共享元素和多个共享元素。

单共享元素

public class AnimationActivity extends AppCompatActivity implements View.OnClickListener {

    private Button mStart;
    private ImageView mTargetView;
    private Button btn;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        getWindow().requestFeature(Window.FEATURE_CONTENT_TRANSITIONS);
        setContentView(R.layout.activity_animation);
        initView();
    }

    private void initView() {
        mStart = (Button) this.findViewById(R.id.animation_btn);
        mStart.setOnClickListener(this);
        btn = (Button) this.findViewById(R.id.animation_btn01);
        btn.setOnClickListener(this);
        mTargetView = (ImageView) this.findViewById(R.id.animation_target);
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    @Override
    public void onClick(View view) {
        if (view == mStart) {
            ActivityOptionsCompat optionsCompat = ActivityOptionsCompat.makeSceneTransitionAnimation(
                    this, mTargetView, "transition_iv");
            Intent intent = new Intent(this, Animator2Activity.class);
            startActivity(intent, optionsCompat.toBundle());
        } else if (view == btn) {

        }
    }
}
复制代码

设置getWindow().requestFeature(Window.FEATURE_CONTENT_TRANSITIONS); 然后使用ActivityOptionsCompat.makeSceneTransitionAnimation来创建ActivityOptionsCompat对象。

makeSceneTransitionAnimation方法已经做好版本判断了,我们看下该方法的参数。

(Activity activity,View sharedElement, String sharedElementName) activity:当前activity的对象 sharedElement:共享元素的View sharedElementName:也就是在sharedElement中设置android:transitionName名字。

activity_animation.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="20dp"
    tools:context="com.main.animation.AnimationActivity">

    <Button
        android:id="@+id/animation_btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="10dp"
        android:gravity="center"
        android:text="startAnimation"/>

    <Button
        android:id="@+id/animation_btn01"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@+id/animation_btn"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="10dp"
        android:gravity="center"
        android:text="Text"/>

    <ImageView
        android:id="@+id/animation_target"
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:layout_alignParentBottom="true"
        android:transitionName="transition_iv"
        android:layout_centerHorizontal="true"
        android:scaleType="centerCrop"
        android:src="@drawable/animationtarget"/>

</RelativeLayout>

复制代码

在xml中需要注意的就是贡献元素的View需要设置 android:transitionName。

跳转的Activity

public class Animator2Activity extends AppCompatActivity {

    private ImageView target;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //设置允许使用转场动画
        getWindow().requestFeature(Window.FEATURE_CONTENT_TRANSITIONS);
        setContentView(R.layout.activity_animator2);
        target = (ImageView)this.findViewById(R.id.animation2_target);
    }


    @Override
    public void onBackPressed() {
        super.onBackPressed();
    }
}
复制代码

同样需要设Window.FEATURE_CONTENT_TRANSITIONS。

activity_animator2.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.main.animation.Animator2Activity">

    <ImageView
        android:id="@+id/animation2_target"
        android:layout_width="match_parent"
        android:layout_height="500dp"
        android:scaleType="centerCrop"
        android:src="@drawable/animationtarget"
        android:transitionName="transition_iv"
        tools:layout_editor_absoluteX="8dp"
        tools:layout_editor_absoluteY="0dp"/>

</RelativeLayout>

复制代码

同样需要在共享的View设置android:transitionName,而且名字需要相同。

说明: 按返回键的时候自动实现了返回的共享元素转场动画,具体原因看源码:

public void onBackPressed() {
        finishAfterTransition();
    }
    public void finishAfterTransition() {
        if (!mActivityTransitionState.startExitBackTransition(this)) {
            finish();
        }
    }
复制代码

当然你可以自己调用finishAfterTransition()来结束activity,也是有动画。

多元素共享转场

基本上跟单元素转场一样,唯一的不同点就是使用Pair.create来设置多个元素转场。

ActivityOptionsCompat optionsCompat = ActivityOptionsCompat
                    .makeSceneTransitionAnimation(this, Pair.create((View)mTargetView, "transition_iv"),
Pair.create((View)text, "transition_text"));
Intent intent = new Intent(this, Animator2Activity.class);
startActivity(intent, optionsCompat.toBundle());
复制代码

以上就是多元素共享转场的核心代码。

使用RecyclerView实现共享元素转场动画。

TranslationActivity:

public class TranslationActivity extends AppCompatActivity {
    private RecyclerView mList;
    private List<String> mDatas = new ArrayList<>();
    private MyAdapter mMyAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_translation);
        mList = (RecyclerView)this.findViewById(R.id.translation_list);
        mList.setLayoutManager(new LinearLayoutManager(this));
        for (int i = 0; i < 30; i++) {
            String tx = "呵呵呵"+i;
            mDatas.add(tx);
        }
        mMyAdapter = new MyAdapter(this,mDatas);
        mList.setAdapter(mMyAdapter);
        mMyAdapter.setOnItemOnclickListener(new MyAdapter.OnItemOnclickListener() {
            @TargetApi(Build.VERSION_CODES.LOLLIPOP)
            @Override
            public void onClickItem(ImageView iv, TextView tv, String text) {
                ActivityOptionsCompat optionsCompat = ActivityOptionsCompat
                        .makeSceneTransitionAnimation(TranslationActivity.this, Pair.create((View)iv, "transition_iv"),
                                Pair.create((View)tv, "transition_text"));
                Intent intent = new Intent(TranslationActivity.this, Animator2Activity.class);
                intent.putExtra("tag",text);
                startActivity(intent, optionsCompat.toBundle());
            }
        });

    }


    private static class MyAdapter extends RecyclerView.Adapter{

        private List<String>datas;
        private Context mContext;


        private OnItemOnclickListener mOnItemOnclickListener;

        public void setOnItemOnclickListener(OnItemOnclickListener onItemOnclickListener) {
            mOnItemOnclickListener = onItemOnclickListener;
        }

        public MyAdapter(Context context,List<String> datas) {
            this.datas = datas;
            mContext = context;
        }

        @Override
        public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            View view = LayoutInflater.from(mContext).inflate(R.layout.transition_item,null);
            return new MyHolder(view);
        }

        @Override
        public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
            MyHolder myHolder = (MyHolder)holder;
            myHolder.text.setText(datas.get(position));
            myHolder.tag = datas.get(position);
        }

        @Override
        public int getItemCount() {
            if (datas != null && datas.size()>0){
                return datas.size();
            }
            return 0;
        }

        private class MyHolder extends RecyclerView.ViewHolder{
            private TextView text;
            private String tag;
            private ImageView iv;
            public MyHolder(View itemView) {
                super(itemView);
                text = (TextView)itemView.findViewById(R.id.transition_tx);
                iv = (ImageView) itemView.findViewById(R.id.transition_iv);
                itemView.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View view) {
                        if (mOnItemOnclickListener != null){
                            mOnItemOnclickListener.onClickItem(iv,text,tag);
                        }
                    }
                });
            }
        }

        private View.OnClickListener mOnClickListener = new View.OnClickListener() {
            @Override
            public void onClick(View view) {

            }
        };

        public interface OnItemOnclickListener{
            void onClickItem(ImageView iv,TextView tv,String text);
        }

    }


}
复制代码

主要是设置RecyclerView,实现RecyclerView.Adapter,然后设置Item的点击,RecyclerView没有为Item提供点击,所以要实现点击回调。 在回调设置共享元素转场动画

ActivityOptionsCompat optionsCompat = ActivityOptionsCompat
                        .makeSceneTransitionAnimation(TranslationActivity.this, Pair.create((View)iv, "transition_iv"),Pair.create((View)tv, "transition_text"));
Intent intent = new Intent(TranslationActivity.this, Animator2Activity.class);
intent.putExtra("tag",text);
startActivity(intent, optionsCompat.toBundle());
复制代码

注意: 回调的时候需要把Item的共享元素作为参数传递过来,否则设置不了。

translation_list.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="20dp"
    android:orientation="vertical"
    tools:context="com.main.animation.TranslationActivity">
    <android.support.v7.widget.RecyclerView
        android:id="@+id/translation_list"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</LinearLayout>

复制代码

Item条目的布局transition_item.xml:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="120dp">

    <ImageView
        android:id="@+id/transition_iv"
        android:layout_width="match_parent"
        android:layout_height="120dp"
        android:layout_marginTop="10dp"
        android:scaleType="centerCrop"
        android:src="@drawable/animationtarget"
        android:transitionName="transition_iv"/>

    <TextView
        android:id="@+id/transition_tx"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:text="tv"
        android:textColor="#ffffff"
        android:textSize="30dp"
        android:transitionName="transition_text"/>

</RelativeLayout>

复制代码

同样共享元素需要设置android:transitionName。

这里的跳转的页面直接跳转到上面的Animator2Activity页面。

效果界面:

由于没有录制视频,所以读者很难看出效果,不过读者可以根据上面代码,自行实战一把,同时我的也希望读者可以自己动手实战,巩固和理解该知识点。