Android动画全解

739 阅读19分钟

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

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页面。

效果界面:

View

View

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