Android 属性动画

属性动画与Tween和Frame动画一样,也支持使用代码以及xml的方式,但是在实际的项目使用中,更多的是使用代码的方式实现能更加灵活。

1 ViewPropertyAnimator

ViewPropertyAnimator 在使用上非常简单,通过 View.animate() 就可以获取到一个ViewPropertyAnimator,ViewPropertyAnimator的内部是封装了一个ValueAnimator。

// View.animate()会返回一个ViewPropertyAnimator
// 平移500px
View.animate().translationX(500);

具体的操作如下图所示:

在这里插入图片描述

从图中可以看到,View的每个方法都对应了ViewPropertyAnimator的两个方法,其中带有 -By() 后缀的是增量版本的方法,例如 translationX(100) 表示用动画把View的 translationX 值渐变为100,而 translationBy(100) 则表示用动画把View的 translationX 值渐变地增加到100。

虽然VIewPropertyAnimator使用非常方便也很简单,但是也有局限性,就是只能使用上述图中的一些属性,不能定制化。如果我们要定制动画,就要使用到 ObjectAnimator

2 ObjectAnimator

ObjectAnimator 继承自ValueAnimator。

举例:改变一个对象的translationY属性,让其沿着Y轴平移一段距离。

ObjectAnimaotr.ofFloat(myObject, "translationY", -myObject.getHeight()).start();
  • AnimatorSet

举例:5秒内对View的旋转、平移、缩放和透明度进行改变。

AnimatorSet set = new AnimatorSet();
set.playTogether(
    ObjectAnimator.ofFloat(myView, "rotationX", 0, 360),
    ObjectAnimator.ofFloat(myView, "rotationY", 0, 180),
    ObjectAnimator.ofFloat(myView, "rotation", 0, -90),
    ObjectAnimator.ofFloat(myView, "translationX", 0, 90),
    ObjectAnimator.ofFloat(myView, "translationY", 0, 90),
    ObjectAnimator.ofFloat(myView, "scaleX", 1, 1.5f),
    ObjectAnimator.ofFloat(myView, "scaleY", 1, 0.5f),
    ObjectAnimator.ofFloat(myView, "alpha", 1, 0.25f, 1)
);
set.setDuration(5000).start();

3 ValueAnimator

额外简单的说一下 ValueAnimator。很多时候,用不到它,只是在你使用一些第三方库的控件,而你想要做动画的属性却没有 setter/getter 方法的时候,会需要用到它。

除了ViewPropertyAnimator和ObjectAnimator,还有第三个选择是ValueAimator。ValueAnimator并不常用,因为它功能太基础了。

ValueAnimator是ObjectAnimator的父类,实际上,ValueAnimator就是一个不能指定目标对象版本的ObjectAnimator。

ObjectAnimator是自动调用目标对象的 setter 方法来更新目标属性的值,以及很多时候还会以此来改变目标对象的UI。

而ValueAnimator只是通过渐变的方式来改变一个独立的数据,这个数据不是属于某个对象的,至于在数据更新后要做什么事,全都由你来定,你可以依然是去调用某个对象的 setter 方法,也可以做其他的事,不管要做神呢,都是要你自己来写的。

ValueAnimator功能最少、最不方便,但有时也是束缚最少、最灵活。比如有时候给一个第三方控件做动画,你需要更新的那个属性没有 setter 方法,只能直接修改,这样的话ObjectAnimator就不灵了,这个时候可以用ValueAnimator,在它的 onUpdate() 里面更新这个属性的值,并且手动调用 invalidate()

4 使用xml定义属性动画

res/animator 目录下定义属性动画。

<set  android:ordering=["together" | "sequentially"]>
    
    <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=["repeat" | "reverse"]
        android:valueType=["intType" | "floatType"]/>

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

    <set>
        .....
    </set>
</set>

属性说明:

<set> 代表 AnimatorSet<objectAnimator> 代表 ObjectAnimator<animator> 代表ValueAnimator

  • android:orderingtogether 表示动画集合中的子动画同时播放,sequentially 表示动画集合中的子动画按照前后顺序依次播放。默认together

  • android:propertyName:表示属性动画的作用对象的属性的名称

  • android:duration:表示动画时长。默认时长300ms

  • android:valueFrom:表示属性的起始值

  • android:valueTo:表示属性的结束值

  • android:startOffset:表示动画的延迟时间,当动画开始后,需要延迟多少毫秒才会真正播放此动画

  • android:repeatCount:表示动画的重复次数。默认值为0,-1表示无限循环

  • android:repeatMode:表示动画的重复模式。repeat 表示连续重复,即动画每次都重新开始播放;reverse 表示逆向重复,即如第一次播放完,第二次倒播,第三次又重头开始播放,第四次又倒播,如此反复

  • android:valueType:表示 android:propertyName 所指定的属性的类型,有 intTypefloatType 两个可选项,分别表示属性的类型为整型和浮点型

另外,如果 android:propertyName 所指定的属性表示的是颜色,那么不需要指定android:valueType,系统会自动对颜色类型的属性做处理

3 属性的动画进阶用法

3.1 PropertyValuesHolder

3.1.1 使用PropertyValuesHolder多属性修改

很多时候,你在同一个动画中会需要改变多个属性,例如在改变透明度的同时改变尺寸。如果使用ViewPropertyAnimator,你可以直接用连写的方式来在一个动画中同时改变多个属性:

view.animate()
	.scaleX(1)
	.scaleY(1)
	.alpha(1);

而对于ObjectAnimator,是不能这么用的。不过你可以使用 PropertyValuesHolder 来同时在一个动画中改变多个属性:

PropertyValuesHolder holder1 = PropertyValuesHolder.ofFloat("alpha", 0.0f, 1.0f);
PropertyValuesHolder holder2 = PropertyValuesHolder.ofFloat("scaleX", 0.0f, 1.0f);
PropertyValuesHolder holder3 = PropertyValuesHolder.ofFloat("scaleY", 0.0f, 1.0f);

ObjectAnimator.ofPropertyValuesHolder(view, holder1, holder2, holder3).setDuration(300).start();

PropertyValuesHolder 的意思从名字可以看出来,它是一个属性值的批量存放地。所以你如果有多个属性需要修改,可以把它们放在不同的PropertyValuesHolder中,然后使用 ObjectAnimator.ofPropertyValuesHolder() 统一放进Animator,这样就不用为每个属性单独创建一个Animator分别执行了。

3.1.2 ofKeyframe()把同一个属性拆分

除了合并多个属性和调配多个动画,你还可以在PropertyValuesHolder的基础上更进一步,通过设置 Keyframe(关键帧),把同一个动画属性拆分成多个阶段。例如,你可以让一个进度增加到100%后再反弹回来:

// 在0%处开始
Keyframe keyframe1 = Keyframe.ofFloat(0, 0);
// 时间经过50%的时候,动画完成度100%
Keyframe keyframe2 = Keyframe.ofFloat(0.5f, 100);
// 时间经过100%的时候,动画完成度倒退到80%,即反弹20%
Keyframe keyframe3 = Keyframe.ofFloat(1, 80);

PropertyValuesHolder holder = PropertyValuesHolder.ofKeyframe("progress", keyframe1 , keyframe2 , keyframe3 );
ObjectAnimator.ofPropertyValuesHolder(view, holder).setDuration(3000).start();

3.2 AnimatorSet

有的时候,你不止需要在一个动画中改变多个属性,还会需要多个动画配合工作,比如,在内容的大小从0放大到100%大小后开始移动。这种情况使用PropertyValuesHolder是不行的,因为这些属性如果放在同一个动画中,需要共享动画的开始时间、结束时间、Interpolator等等一系列的设定,这样就不能有先后次序地执行动画了。

这就需要用到 AnimatorSet了:

ObjectAnimator animator1 = ObjectAnimator.ofFloat(...);
animator1.setInterpolator(new LinearInterpolator());
ObjectAnimator animator2 = ObjectAnimator.ofInt(...);
animator2.setInterpolator(new DecelerateInterpolator());

AnimatorSet animatorSet = new AinmatorSet();
// 两个动画依次执行
animatorSet.playSequentially(animator1, animator2).start();

// 两个动画同时执行
animatorSet.playTogether(animator1, animator2).start();

// 使用play().with/before/after精确配置各个Animator之间的关系
animatorSet.play(animator1).with(animator2);
animatorSet.play(animator1).before(animator2);
animatorSet.play(animator1).after(animator2);
animatorSet.start();

3.3 属性动画进阶使用小结

关于复杂的属性关系来做动画,就这么三种:

  • 使用 PropertyValuesHolder 来对多个属性同时做动画

  • 使用 AnimatorSet 来同时管理调配多个动画,将动画做得复杂

  • 使用 PropertyValuesHolder.ofKeyframe() 来把一个属性拆分成多段,执行更加精细的属性动画

4 Interpolator和TypeEvaluator

插值器和估值器是实现非匀速动画的重要手段。

4.1 插值器

TimeInterpolator,时间插值器。它的作用是根据时间流逝的百分比来计算出当前属性值改变的百分比。系统预置的Interpolator如下(这里只列举用得比较多的三个,还有很多):

  • LinearInterpolator(线性插值器:匀速动画)

  • AccelerateDecelerateInterpolator(加速减速插值器:动画两头慢中间快)

  • DecelerateInterpolator(减速插值器:动画越来越慢)

4.2 估值器

TypeEvaluator,类型估值器,也称为估值器。它的作用是根据当前属性改变的百分比来计算改变后的属性值。系统预置的TypeEvaluator如下:

  • IntEvaluator(针对整型属性)

  • FloatEvaluator(针对浮点型属性)

  • ArgbEvaluator(针对Color属性)

下面只挑选 ArgbEvaluator 举例估值器的使用。

4.2.1 ArgbEvaluator

TypeEvaluator最经典的用法是使用 ArgbEvaluator 来做颜色渐变的动画。

// view需要有setColor()
ObjectAnimator animator = ObjectAnimator.ofInt(view, "color", 0xffff0000, 0xff00ff00);
animator.setEvaluator(new ArgbEvaluator()).start();

// 在Android 5.0(API21)加入了新的方法ofArgb()
ObjectAnimator animator = ObjectAnimator.ofArgb(view, "color", 0xffff0000, 0xff00ff00);
animator.start();

4.2.2 自定义Evaluator

如果你对ArgbEvaluator的效果不满意,可以自定义一个TypeEvaluator:

private class HsvEvaluator implements TypeEvaluator<Integer> {
	float[] startHsv = new float[3];
	float[] endHsv = new float[3];
	float[] outHsv = new float[3];

	@Overrid
	public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
		// 把ARGB转换成HSV
		Color.colorToHSV(startValue, startHsv);
		Color.colorToHSV(endValue, endHsv);

		// 计算当前动画完成度(fraction)所对应的颜色值
		if (endHsv[0] - startHsv[0] > 1800) {
			endHsv[0] -= 360;
		} else if (endHsv[0] - startHsv[0] < -180) {
			endHsv[0] += 360;
		}
		outHsv[0] = startHsv[0] + (endHsv[0] - startHsv[0]) * fraction;
		if (outHsv[0] > 360) {
			outHsv[0] -= 360;
		} else if (outHsv[0] < 0) {
			outHsv[0] += 360;
		}
		outHsv[1] = startHsv[1] + (endHsv[1] - startHsv[1]) * fraction;
		outHsv[2] = startHsv[2] + (endHsv[2] - startHsv[2]) * fraction;

		// 计算当前动画完成度(fraction)所对应的透明度
		int alpha = startValue >> 34 + (int)((endValue >> 34 - startValue >> 34) * fraction);

		// 把HSV转换回ARGB返回
		return Color.HSVToColor(alpha, outHsv);
	}
}

ObjectAnimator animator = ObjectAnimator.ofInt(view, "color", 0xffff0000, 0xff00ff00);
animator.setEvaluator(new HsvEvaluator());
animator.setDuration(2000);
animator.start();

4.2.3 ofObject()

借助于TypeEvaluator,属性动画就可以通过 ofObject() 来对不限定类型的属性做动画了。方式很简单:

  • 为目标属性写一个自定义的TypeEvaluator

  • 使用 ofObject() 来创建Animator,并把自定义的TypeEvaluator作为参数填入

// 在API21中,已经自带了PointFEvaluatorl这个类
private class PointFEvaluator implements TypeEvaluator<PointF> {
	PointF newPoint = new PointF();

	@Override
	pbulic PointF evaluate(float fraction, PointF startValue, PointF endValue) {
		float x = startValue.x + (fraction * (endValue.x - startValue.x));
		float y = startValue.y + (fraction * (endValue.y - startValue.y));

		newPoint.set(x, y);

		return newPoint;
	}
}

5 属性动画监听

设置监听器的方法,ViewPropertyAnimatorObjectAnimator 略微不一样:

  • ViewPropertyAnimator 用的是 setListener()setUpdateListener(),可以设置一个监听器,要移除监听器通过 set[Update]Listener(null) 移除

  • ObjectAnimator 用的是 addListener()addUpdateListener() 添加一个或多个监听器,移除监听器则通过 remove[Update]Listener() 指定移除对象

另外,ObjectAnimator 支持使用 pause() 方法暂停,所以它还多了一个 addPauseListener()/removePauseListener() 的支持;而 ViewPropertyAnimator 则独有 withStartAction()withEndAction() 方法,可以设置一次性的动画开始或结束的监听。

5.1 ViewPropertyAnimator.setListener()/ObjectAnimator.addListener()

属性动画监听动画播放过程提供了两个接口:AnimatorUpdateListenerAnimatorListener

系统还提供了 AnimatorListenerAdapter 类,它是 AnimatorListener 的适配器类,可以选择性的实现上面四个方法。

public static interface AnimatorListener {
    void onAnimationStart(Animation animation);
    // 动画被取消,onAnimationEnd()也会被调用
    void onAnimationEnd(Animation animation);
    // 动画被取消,会先调用onAnimationCancel()然后再调用onAnimationEnd()
    void onAnimationCancel(Animation animation);
    // ViewPropertyAnimator不支持重复,所以这个方法对ViewPropertyAnimator无效
    void onAnimationRepeat(Animation animation);
}

ViewPropertyAnimator.setListener(new AnimatorListener()/AnimatorListenerAdapter());
ObjectAnimator.addListener(new AnimatorListener()/AnimatorListenerAdapter());

5.2 ViewPropertyAnimator.setUpdateListener()/ObjectAnimator.addUpdateListener()

AnimatorUpdateListener 会监听整个动画过程,动画是由许多帧组成的,每播放一帧,onAnimationUpdate()就会被调用一次。

public static interface AnimatorUpdateListener {
    void onAnimationUpdate(ValueAnimator animator);
}

// ValueAnimator是ViewPropertyAnimator的内部实现
// AnimatorUpdateListener的参数ValueAnimator就是ViewPropertyAnimator的内部ValueAnimator
// ObjectAnimator就是本身
ViewPropertyAnimator.setUpdateListener(new AnimatorUpdateListener());
ObjectAnimator.addUpdateListener(new AnimatorUpdateListener());

5.3 ViewPropertyAnimator.withStartAction()/EndAction()

这两个方法是ViewPropertyAnimator独有的方法。它们和 set/addListener() 中回调的 onAnimationStart()/onAnimationEnd() 相比起来的不同主要有两点:

  • withStartAction()/withEndAction() 是一次性的,在动画执行结束后就自动弃掉了,就算之后再重用ViewPropertyAnimator来做别的动画,用它们设置的回调也不会再被调用。而 set/addListener() 所设置的 AnimatorListener 是持续有效的,当动画重复执行时,回调总会被调用

  • withEndAction() 设置的回调只有在动画正常结束时才会被调用,而在动画被取消时不会被执行。这点和 AnimatorListener.onAnimationEnd() 的行为是不一致的

6 对任意属性做动画

举例:给一个Button用动画5秒内让宽度增加到500px。

平移(translate)、缩放(scale)、透明度(alpha)、旋转(rotate)无法做到让Botton宽度增加,只能对对象进行操作,不能对对象内部进行操作。

可以使用属性动画三种解决方案来解决这个问题:

  • 给对象加上get和set方法,如果你有权限的话(一般涉及到修改源码,可能性不大,该方案不考虑);

  • 用一个类来包装原始对象,间接为其提供get和set方法;

private void performAnimate() {
    ViewWrapper wrapper = new ViewWrapper(mButton);
    ObjectAnimator.ofInt(wrapper, "width", 500).setDuration(5000).start();    
}

@Override 
public void onClick(View view) {
    if (view == mButton)
        performAnimate();
}

private static class ViewWrapper {
    private View mTarget;

    public ViewWrapper(View target) {
        mTarget = target;
    }

    public void setWidth(int width) {
        mTarget.getLayoutParams().width = width;
        mTarget.requestLayout();
    }

    public int getWidth() {
        return mTarget.getLayoutParams().width;
    }
}
  • 采用ValueAnimator,监听动画过程,自己实现属性的改变。
private void performAnimate(final View target, final int start, final int end) {
    ValueAnimator valueAnimator = ValueAnimator.ofInt(1, 100);
    valueAnimator.addUpdateListener(new AnimatorUpdateListener() {
        //持有一个IntEvaluator对象,方便下面估值的时候使用
        private IntEvaluator mEvaluator = new IntEvaluator();

        @Override
        public void onAnimatorUpdate(ValueAnimator animator) {
            //获得当前动画的进度,整型,1-100之间
            int currentValue = (Integer) animator.getAnimatedValue();
            //获得当前进度占整个动画过程的比例,浮点型,0-1之间
            float fraction = animator.getAnimatedFraction();
            //直接调用整形估值器,通过比例计算出宽度,然后再设给Button
            target.getLayoutParams().width = mEvaluator.evaluate(fraction, start, end);
            target.requestLayout();
        }
    });
}

@Override
public void onClick(View view) {
    if (view == mButton)
        performAnimate(mButton, mButton.getWidth(), 500);
}

7 属性动画原理简单说明

属性动画要求动画作用的对象提供该属性的 setter/getter 方法,属性动画根据外界传递的该属性的初始值和最终值,以动画的效果多次去调用 setter 方法,每次传递给 setter 方法的值都不一样,确切来说是随着时间的推移,所传递的值越来越接近最终值。

属性动画原理总结:

Object的属性abc做动画,如果想让动画生效,要同时满足两个条件:

  • Object必须提供 setAbc() ,如果动画的时候没有传递初始值,那么还要提供 getAbc() ,因为系统要去取abc属性的初始值

  • Object的 setAbc() 对属性abc所做的改变必须能够通过某种方法反映出来,比如会带来UI的改变之类

更详细的属性动画原理,参考另一篇文章:属性动画原理解析

8 属性动画使用demo

8.1 ViewPropertyAnimator demo

// 在1秒时间水平方向平移200dp
view.animate()
	.translateX(Utils.dp2px(200))
	.setDuration(1000)
	.start();

8.2 ObjectAnimator demo

public class CircleView extends View {
	private Paint paint = new Paint(ANTI_ALIAS);
	private float radius;

	public CircleView(Context context, AttributeSet attr) {
		super(context, attr);
	}

	// 定制属性动画的修改属性
	// 要提供修改属性的getter和setter方法
	// 因为属性动画的原理最终会调用修改属性的getter和setter方法(如果没有提供初始值,会先调用getter然后调用setter)
	public float getRadius() {
		return radius;
	}

	public void setRadius(int radius) {
		this.radius = radius;
		invalidate();
	}

	@Override
	protected void onDraw(Canvas canvas) {
		canva.drawCircle(getWidth() / 2f, getHeight() / 2f, radius, paint);
	}
}

// 属性动画修改属性是通过方法名而不是属性名称,即CircleView只要提供的getter和setter是getRadius或setRadius即可
// 实际属性名称是其他也没关系
ObjectAnimator.ofFloat(view, "radius", Utils.dp2px(200)).setDuration(2000).start();

8.3 PropertyValuesHolder keyframe demo

// 对View平移动画做拆分,移动length=300dp内,前10%的平移长度达到40% length,前90%的平移长度达到60% length,最后到达
// 效果是先前行一段距离,然后类似受阻一样平移速度变慢,然后又恢复速度运行到终点
float length = Utils.dp2px(300);
Keyframe keyframe1 = Keyframe.ofFloat(0, 0);
Keyframe keyframe2 = Keyframe.ofFloat(0.1f, 0.4f * length); // 如果是1.5f * length,会飞出去然后又回弹回来走完
Keyframe keyframe3 = Keyframe.ofFloat(0.9f, 0.6f * length);
Keyframe keyframe4 = Keyframe.ofFloat(1f, 1f * length);
PropertyValuesHolder holder = PropertyValuesHolder.ofKeyframe(keyframe1, keyframe2, keyframe3, keyframe4);
ObjectAnimator.ofPropertyValuesHolder(view, holder).setDuration(2000).start();

8.4 other

xml加载加载动画(创建在 res/animator 目录):

  • value_animator.xml
<!-- 设置ValueAnimator -->
<?xml version="1.0" encoding="utf-8"?>
<animator xmlns:android="http://schemas.android.com/apk/res/android"
    android:valueFrom="0.0f"
    android:valueTo="1.0f"
    android:valueType="floatType"
    android:duration="3000"/>
  • alpha_animator.xml
<!-- 从完全透明到完全不透明 ->
<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
    android:valueFrom="0.0f"
    android:valueTo="1.0f"
    android:valueType="floatType"
    android:propertyName="alpha"
    android:duration="3000"/>
  • rotation_animator.xml
<!-- 旋转360->
<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
        android:valueFrom="0f"
        android:valueTo="360f"
        android:propertyName="rotation"
        android:duration="3000"/>
  • scale_animator.xml
<!-- 缩放到2倍大小后,再缩放会原本大小 ->
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:ordering="together">

    <objectAnimator
            android:duration="3000"
            android:propertyName="scaleX"
            android:valueFrom="1.0f"
            android:valueTo="2.0f"
            android:valueType="floatType" />

        <objectAnimator
            android:duration="3000"
            android:propertyName="scaleY"
            android:valueFrom="1.0f"
            android:valueTo="2.0f"
            android:valueType="floatType" />

        <objectAnimator
            android:duration="3000"
            android:propertyName="scaleX"
            android:startOffset="3000"
            android:valueFrom="2.0f"
            android:valueTo="1.0f"
            android:valueType="floatType" />

        <objectAnimator
            android:duration="3000"
            android:propertyName="scaleY"
            android:startOffset="3000"
            android:valueFrom="2.0f"
            android:valueTo="1.0f"
            android:valueType="floatType" />

</set>
  • translation_animator.xml
<!-- 先水平移动100px,再竖直移动100px ->
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:ordering="together">

    <objectAnimator
        android:valueFrom="0"
        android:valueTo="100"
        android:valueType="floatType"
        android:propertyName="translationX"
        android:duration="3000"/>

    <objectAnimator
        android:valueFrom="0"
        android:valueTo="100"
        android:valueType="floatType"
        android:propertyName="translationY"
        android:duration="3000"
        android:startOffset="3000"/>

</set>
// xml加载动画
findViewById(R.id.btn_xml_animator).setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        Animator animator =AnimatorInflater.loadAnimator(ObjectAnimActivity.this, R.animator.scale_animator);
        animator.setTarget(mIvAnim);
        animator.start();
    }
});


代码加载动画:

// ValueAnimator和ObjectAnimator,ofFloat和ofInt较为常用

findViewById(R.id.btn_valueAnimator).setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        ValueAnimator valueAnimator = ValueAnimator.ofFloat(0.0f, 1.0f);
        valueAnimator.setDuration(2000);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            /**
             *  监听动画状态,可以获取动画执行过程中值的变化
             */
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                // 这里的值转换需要和ofInt()和ofFloat()的int类型和float类型对应
                float value = (float) valueAnimator.getAnimatedValue();
                Log.i(TAG, "value is = " + value);
            }
        });
        valueAnimator.addListener(new AnimatorListenerAdapter() {
            /**
             *  监听动画执行状态,使用AnimatorListenerAdapter()可以根据需要实现对应方法
             */
            @Override
            public void onAnimationEnd(Animator animation) {
                if (mIvAnim.getVisibility() == View.VISIBLE) {
                    mIvAnim.setVisibility(View.GONE);
                } else {
                    mIvAnim.setVisibility(View.VISIBLE);
                }
            }
        });
        valueAnimator.start();
    }
});

// 从完全透明到完全不透明
// propertyName: alpha
// 0(0.0)完全透明,1(1.0)完全不透明
findViewById(R.id.btn_alpha).setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        ValueAnimator animator = ObjectAnimator.ofFloat(mIvAnim, "alpha", 0.0f, 1.0f);
        animator.setDuration(3000);
        animator.start();
    }
});

// 水平方向和垂直方向从对象原始大小到2倍大小再到原始大小
// propertyName: scaleX, scaleY:控制View基于pivotX和pivotY的缩放
// pivotX pivotY:旋转的轴点和缩放的轴点,默认是View的中心点
// x y:View在父容器的最终位置,是左上角坐标和偏移量(translationX,translationY)的和
findViewById(R.id.btn_scale).setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        AnimatorSet animatorSet = new AnimatorSet();
        animatorSet.playTogether(
                ObjectAnimator.ofFloat(mIvAnim, "scaleX", 1.0f, 2.0f, 1.0f),
                ObjectAnimator.ofFloat(mIvAnim, "scaleY", 1.0f, 2.0f, 1.0f)
        );
        animatorSet.setDuration(3000);
        animatorSet.start();
    }
});

// 水平方向平移View宽度后,再垂直方向平移View高度
// propertyName:translationX, translationY:控制View的位置,相对于View的左上角(坐标原点)
// pivotX pivotY:旋转的轴点和缩放的轴点,默认是View的中心点
// x y:View在父容器的最终位置,是左上角坐标和偏移量(translationX,translationY)的和
findViewById(R.id.btn_translate).setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        ValueAnimator animatorX = ObjectAnimator.ofFloat(mIvAnim, "translationX", 0, mIvAnim.getWidth());
        animatorX.setDuration(3000);
        animatorX.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                ValueAnimator animatorY = ObjectAnimator.ofFloat(mIvAnim, "translationY", 0, mIvAnim.getHeight());
                animatorY.setDuration(3000);
                animatorY.start();
            }
        });
        animatorX.start();
    }
});

// 360度旋转
// propertyName: rotation, rotationX, rotationY:与补间动画不同的是可以立体(rotationX, rotationY)旋转,默认是围绕View的中心点旋转
findViewById(R.id.btn_rotate).setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        ValueAnimator animator = ObjectAnimator.ofFloat(mIvAnim, "rotation", 0.0f, 360f);
        animator.setDuration(3000);
        animator.start();
    }
});

// 倒序播放透明动画:从完全透明到完全不透明,再倒序从完全透明到完全不透明
findViewById(R.id.btn_repeat_reverse).setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        ValueAnimator animator = ObjectAnimator.ofFloat(mIvAnim, "alpha", 0.0f, 1.0f);
        animator.setDuration(3000);
        animator.setRepeatCount(1);
        animator.setRepeatMode(ValueAnimator.REVERSE);
        animator.start();
    }
});

// 重复播放透明动画:从完全透明到完全不透明重复2次
findViewById(R.id.btn_repeat_restart).setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        ValueAnimator animator = ObjectAnimator.ofFloat(mIvAnim, "alpha", 0.0f, 1.0f);
        animator.setDuration(3000);
        animator.setRepeatCount(1);
        animator.setRepeatMode(ValueAnimator.RESTART);
        animator.start();
    }
});

// xml加载动画
findViewById(R.id.btn_xml_animator).setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        Animator animator = AnimatorInflater.loadAnimator(ObjectAnimActivity.this, R.animator.scale_animator);
        animator.setTarget(mIvAnim);
        animator.start();
    }
});

// Keyframe帧动画
findViewById(R.id.btn_key_frame).setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        Keyframe kf0 = Keyframe.ofFloat(0f, 0f); // 第一帧动画,平移的位置为0
        Keyframe kf1= Keyframe.ofFloat(.5f, 100.0f); // 第二帧动画,平移的位置移动到100px
        Keyframe kf2= Keyframe.ofFloat(1f, 0f); // 第三帧动画,平移的位置回到0

        PropertyValuesHolder holder = PropertyValuesHolder.ofKeyframe("translationX", kf0, kf1, kf2);
        ObjectAnimator.ofPropertyValuesHolder(mIvAnim, holder).setDuration(3000).start();
    }
});

// 组合动画,以上动画同时执行以及设置分别执行
findViewById(R.id.btn_set).setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        AnimatorSet animatorSet = new AnimatorSet();
        ValueAnimator alphaAnimator = ObjectAnimator.ofFloat(mIvAnim, "alpha", 0.0f, 1.0f);
        ValueAnimator rotationAnimator = ObjectAnimator.ofFloat(mIvAnim, "rotation", 0.0f, 360f);
        ValueAnimator scaleXAnimator = ObjectAnimator.ofFloat(mIvAnim, "scaleX", 0.0f, 1.0f);
        ValueAnimator scaleYAnimator = ObjectAnimator.ofFloat(mIvAnim, "scaleY", 0.0f, 1.0f);
        ValueAnimator translationXAnimator = ObjectAnimator.ofFloat(mIvAnim, "translationX", 0, 100.0f);
        ValueAnimator translationYAnimator = ObjectAnimator.ofFloat(mIvAnim, "translationY", 0, 100.0f);
//                animatorSet.playTogether(
//                        alphaAnimator,
//                        rotationAnimator,
//                        scaleXAnimator,
//                        scaleYAnimator,
//                        translationXAnimator,
//                        translationYAnimator
//                );
        animatorSet.playTogether(
                alphaAnimator,
                scaleXAnimator,
                scaleYAnimator,
                rotationAnimator
        );
        // after(Animator animator)或after(long delay): 设置在某个动画执行完成后或多少时长后执行当前设置动画
        // before(Animator animator): 设置在某个动画执行前先执行当前动画
        // with(Animator animator): 设置和某个动画或一组动画一起执行
        animatorSet.setDuration(3000);
        animatorSet.setDuration(1000).play(translationXAnimator).after(3000);
        animatorSet.setDuration(1000).play(translationYAnimator).after(3000);
        animatorSet.start();
    }
});

// PropertyValuesHolder组合动画
// 不同于AnimatorSet能多个或单个进行动画组合,PropertyValuesHolder只能多个动画一起执行
findViewById(R.id.btn_property_values_holder).setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        PropertyValuesHolder pvhAlpha = PropertyValuesHolder.ofFloat("alpha", 0.0f, 1.0f);
        PropertyValuesHolder pvhScaleX = PropertyValuesHolder.ofFloat("scaleX", 0.0f, 1.0f);
        PropertyValuesHolder pvhScaleY = PropertyValuesHolder.ofFloat("scaleY", 0.0f, 1.0f);
        PropertyValuesHolder pvhTranslationX = PropertyValuesHolder.ofFloat("translationX", 0f, 100.0f);
        PropertyValuesHolder pvhTranslationY = PropertyValuesHolder.ofFloat("translationY", 0f, 100.0f);
        PropertyValuesHolder pvhRotation = PropertyValuesHolder.ofFloat("rotation", 0, 360f);
        ObjectAnimator pvhAnimator = ObjectAnimator.ofPropertyValuesHolder(mIvAnim, pvhAlpha, pvhScaleX, pvhScaleY, pvhTranslationX, pvhTranslationY,
                pvhRotation);
        pvhAnimator.setDuration(3000);
        pvhAnimator.start();
    }
});

// ViewPropertyAnimator多属性动画(动画一起执行,是在View原有基础上进行动画)
// ViewPropertyAnimator只能作用于View,并且API >= 12,不调用start()也会默认开启动画
findViewById(R.id.btn_view_property_animator).setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        // 是在View本身属性基础上进行动画
        // 如果是向在View本身属性基础上就执行动画,就不要使用xxxBy()方法,执行使用对应的动画方法
        ViewPropertyAnimator animator = mIvAnim.animate();
        animator.alpha(0.0f).alphaBy(1.0f) // 透明度变化,因为alphaBy(1.0f),所以没有显示出透明变化效果
                .scaleX(0.0f).scaleXBy(1.0f)
                .scaleY(0.0f).scaleYBy(1.0f) // 缩放变化,因为scaleXBy(1.0f)和scaleYBy(1.0f),在View原本大小上变化,就是View变为2倍大小
                .translationX(0.0f).translationYBy(100.0f)
                .translationY(0.0f).translationXBy(100.0f) // 平移变化,因为translationXBy(100.0f)和translationYBy(100.0f),在View原本位置上向右和下方平移100px
                .rotation(0.0f).rotationBy(360.0f) // 旋转变化
                .setDuration(3000);
    }
});

9 属性动画使用的选择

属性动画的使用上,从简单到复杂如下顺序:

ViewPropertyAnimator > ObjectAnimator > ValueAnimator

定制化扩展性也根据顺序提高,其中 ViewPropertyAnimator 的定制化最差,ValueAnimator 的定制化最好。

但是它们的性能是一样的,因为 ViewPropertyAnimatorObjectAnimator 的内部实现其实都是 ValueAnimatorObjectAnimator 更是本来就是 ValueAnimator 的子类。

所以在实际使用时候的选择,只要遵循一个原则就行:尽量用简单的。能用 View.animate() 实现就不用 ObjectAnimator,能用 ObjectAnimator 就不用 ValueAnimator

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值