Android动画(二):属性动画

本系列将介绍以下内容:
在这里插入图片描述

Android动画

1 属性动画Property Animation

属性动画是在API Level 11(Android 3.0)时引入的,包含ValueAnimator和ObjectAnimator,均位于包android.animation中,且ObjectAnimator继承自ValueAnimator,其UML图(Class Diagram类图)如图所示:
Animator类图

Animator类图

Animator是抽象类,内含三个接口和一个具体类AnimatorConstantState。
AnimationHandler内含两个接口和一个具体类MyFrameCallbackProvider。
ValueAnimator内含三个接口,且实现了AnimationHandler.AnimationFrameCallback接口,详情见源码。
ObjectAnimator不含任何内部类和接口。

TypeEvaluator是一个泛型接口,Keyframes是普通接口,PropertyValuesHolder是普通类,它们三个将在Evaluator中涉及到,本文不展开。

属性动画的执行流程:
属性动画执行流程

属性动画执行流程

2 ValueAnimator

ValueAnimator是针对值的,不会对控件执行任何操作,功能要点:
1、ValueAnimator只负责对指定值区间进行动画运算
2、需要对运算过程进行监听,然后自己对控件执行动画操作
注意:动画过程中产生的数值与构造时传入的值的类型是一样的。

package java.lang;

/**
 * Cloneable是个空接口,什么都没有
 */
public interface Cloneable {
}
package android.animation;

public abstract class Animator implements Cloneable {
	...
	
	public void start() {
    }
    
    public void cancel() {
    }
    
    public abstract void setStartDelay(long startDelay);
    
	public abstract Animator setDuration(long duration);
	
	public abstract void setInterpolator(TimeInterpolator value);
	
	public void addListener(AnimatorListener listener) {
        if (mListeners == null) {
            mListeners = new ArrayList<AnimatorListener>();
        }
        mListeners.add(listener);
    }
    
    public void removeListener(AnimatorListener listener) {
        if (mListeners == null) {
            return;
        }
        mListeners.remove(listener);
        if (mListeners.size() == 0) {
            mListeners = null;
        }
    }
    
    public void addPauseListener(AnimatorPauseListener listener) {
        if (mPauseListeners == null) {
            mPauseListeners = new ArrayList<AnimatorPauseListener>();
        }
        mPauseListeners.add(listener);
    }
    
    public void removePauseListener(AnimatorPauseListener listener) {
        if (mPauseListeners == null) {
            return;
        }
        mPauseListeners.remove(listener);
        if (mPauseListeners.size() == 0) {
            mPauseListeners = null;
        }
    }
    
    public void removeAllListeners() {
        if (mListeners != null) {
            mListeners.clear();
            mListeners = null;
        }
        if (mPauseListeners != null) {
            mPauseListeners.clear();
            mPauseListeners = null;
        }
    }

	/**
     * <p>An animation listener receives notifications from an animation.
     * Notifications indicate animation related events, such as the end or the
     * repetition of the animation.</p>
     * 监听动画变化时的4个状态
     */
	public static interface AnimatorListener {
        default void onAnimationStart(@NonNull Animator animation, boolean isReverse) {
            onAnimationStart(animation);
        }
        default void onAnimationEnd(@NonNull Animator animation, boolean isReverse) {
            onAnimationEnd(animation);
        }
        void onAnimationStart(@NonNull Animator animation);
        void onAnimationEnd(@NonNull Animator animation);
        void onAnimationCancel(@NonNull Animator animation);
        void onAnimationRepeat(@NonNull Animator animation);
    }

    public static interface AnimatorPauseListener {
        void onAnimationPause(@NonNull Animator animation);
        void onAnimationResume(@NonNull Animator animation);
    }

	private static class AnimatorConstantState extends ConstantState<Animator> {
		...
    }
    
    interface AnimatorCaller<T, A> {
    	...
    }
}
package android.animation;

public class AnimationHandler {
	...
	private class MyFrameCallbackProvider implements AnimationFrameCallbackProvider {
		...
	}
	
	public interface AnimationFrameCallback {
        boolean doAnimationFrame(long frameTime);
        void commitAnimationFrame(long frameTime);
    }

    public interface AnimationFrameCallbackProvider {
        void postFrameCallback(Choreographer.FrameCallback callback);
        void postCommitCallback(Runnable runnable);
        long getFrameTime();
        long getFrameDelay();
        void setFrameDelay(long delay);
    }
}
package android.animation;

@SuppressWarnings("unchecked")
public class ValueAnimator extends Animator implements AnimationHandler.AnimationFrameCallback {
	...
	
	private int mRepeatCount = 0;
	
	private int mRepeatMode = RESTART;
	
	private TimeInterpolator mInterpolator = sDefaultInterpolator;
	
	PropertyValuesHolder[] mValues;
	
	@IntDef({RESTART, REVERSE})
    @Retention(RetentionPolicy.SOURCE)
    public @interface RepeatMode {}
    
    public static final int RESTART = 1;
   
    public static final int REVERSE = 2;
   
    public static final int INFINITE = -1;
    
    public static ValueAnimator ofInt(int... values) {
        ValueAnimator anim = new ValueAnimator();
        anim.setIntValues(values);
        return anim;
    }
    
    public static ValueAnimator ofArgb(int... values) {
        ValueAnimator anim = new ValueAnimator();
        anim.setIntValues(values);
        anim.setEvaluator(ArgbEvaluator.getInstance());
        return anim;
    }
    
    public static ValueAnimator ofFloat(float... values) {
        ValueAnimator anim = new ValueAnimator();
        anim.setFloatValues(values);
        return anim;
    }
    
    public static ValueAnimator ofPropertyValuesHolder(PropertyValuesHolder... values) {
        ValueAnimator anim = new ValueAnimator();
        anim.setValues(values);
        return anim;
    }
    
    public static ValueAnimator ofObject(TypeEvaluator evaluator, Object... values) {
        ValueAnimator anim = new ValueAnimator();
        anim.setObjectValues(values);
        anim.setEvaluator(evaluator);
        return anim;
    }
    
    public void setIntValues(int... values) {
		...
	}
	
	public void setFloatValues(float... values) {
		...
	}
    
    public void setObjectValues(Object... values) {
    	...
    }
    
    public void setValues(PropertyValuesHolder... values) {
    	...
    }
    
    @Override
    public ValueAnimator setDuration(long duration) {
        if (duration < 0) {
            throw new IllegalArgumentException("Animators cannot have negative duration: " +
                    duration);
        }
        mDuration = duration;
        return this;
    }
	
	public Object getAnimatedValue() {
        if (mValues != null && mValues.length > 0) {
            return mValues[0].getAnimatedValue();
        }
        // Shouldn't get here; should always have values unless ValueAnimator was set up wrong
        return null;
    }
    
    public Object getAnimatedValue(String propertyName) {
        PropertyValuesHolder valuesHolder = mValuesMap.get(propertyName);
        if (valuesHolder != null) {
            return valuesHolder.getAnimatedValue();
        } else {
            // At least avoid crashing if called with bogus propertyName
            return null;
        }
    }
    
    public void setRepeatCount(int value) {
        mRepeatCount = value;
    }
    
    public void setRepeatMode(@RepeatMode int value) {
        mRepeatMode = value;
    }
    
    public void addUpdateListener(AnimatorUpdateListener listener) {
        if (mUpdateListeners == null) {
            mUpdateListeners = new ArrayList<AnimatorUpdateListener>();
        }
        mUpdateListeners.add(listener);
    }
    
    public void removeAllUpdateListeners() {
        if (mUpdateListeners == null) {
            return;
        }
        mUpdateListeners.clear();
        mUpdateListeners = null;
    }
    
    public void removeUpdateListener(AnimatorUpdateListener listener) {
        if (mUpdateListeners == null) {
            return;
        }
        mUpdateListeners.remove(listener);
        if (mUpdateListeners.size() == 0) {
            mUpdateListeners = null;
        }
    }
    
    /**
     * ValueAnimator默认使用LinearInterpolator
     */
    @Override
    public void setInterpolator(TimeInterpolator value) {
        if (value != null) {
            mInterpolator = value;
        } else {
            mInterpolator = new LinearInterpolator();
        }
    }
    
    public void setEvaluator(TypeEvaluator value) {
        if (value != null && mValues != null && mValues.length > 0) {
            mValues[0].setEvaluator(value);
        }
    }
    
    @Override
    private void start(boolean playBackwards) {
        ...
    }
    
    @Override
    public void start() {
        start(false);
    }
    
    @Override
    public void cancel() {
    	...
    }
    
    /**
     * Implementors of this interface can add themselves as update listeners
     * to an <code>ValueAnimator</code> instance to receive callbacks on every animation
     * frame, after the current frame's values have been calculated for that
     * <code>ValueAnimator</code>.
     * 监听动画过程中值的实时变化
     */
    public static interface AnimatorUpdateListener {
    	/**
         * <p>Notifies the occurrence of another frame of the animation.</p>
         *
         * @param animation The animation which was repeated.当前动画的实例
         */
        void onAnimationUpdate(@NonNull ValueAnimator animation);
    }
    
    public interface DurationScaleChangeListener {
        void onChanged(@FloatRange(from = 0) float scale);
    }
}

2.1 ValueAnimator的用法

这里的普通用法指的是ValueAnimator.ofInt(xxx)/ofArgb(xxx)/ofFloat(xxx)等只能传入既定类型参数的用法。
使用ValueAniamtor时直接创建即可,但ValueAnimator可以添加两种监听,一种是父类Animator中的AnimatorListener,一种是自身的AnimatorUpdateListener,详见Demo。

布局文件

<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="#ffff00"
        android:padding="10dp"
        android:singleLine="false"
        android:text="ValueAnimator能更改属性,修复补间动画的点击位置问题" />

    <Button
        android:id="@+id/btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:gravity="center"
        android:padding="10dp"
        android:text="Start Anim"
        android:textAllCaps="false"
        android:textSize="18sp" />

</androidx.coordinatorlayout.widget.CoordinatorLayout>

示例代码:

package com.example.myapplication;

import android.animation.Animator;
import android.animation.ValueAnimator;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.animation.LinearInterpolator;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;


public class ValueAnimDemoActivity extends Activity {
    private TextView tv;
    private Button btn;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.translate_anim_activity);

        tv = findViewById(R.id.tv);
        btn = findViewById(R.id.btn);

        btn.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                doValueAnimator(tv);
            }
        });

        tv.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                Toast.makeText(ValueAnimDemoActivity.this, "clicked me", Toast.LENGTH_SHORT).show();
            }
        });
    }

    /**
     * ValueAnimator的使用
     */
    private void doValueAnimator(final TextView tv) {
        // ofXxx(X.. values) 此处values是可变参数,动画过程中产生的数值与构造时传入的值类型一样
        ValueAnimator valueAnimator = ValueAnimator.ofInt(0, 400);

        // 自带的监听,监听动画过程中值的实时变化
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            public void onAnimationUpdate(ValueAnimator animation) {
                // 参数animation为当前状态动画的实例
                int curValue = (Integer) animation.getAnimatedValue();
                // 永久性改变控件位置,控件在新位置能够响应点击事件(补间动画不行)
                tv.layout(curValue, curValue, curValue + tv.getWidth(), curValue + tv.getHeight());
            }
        });

        // 父类Animator的监听,监听动画变化时的4个状态
        valueAnimator.addListener(new Animator.AnimatorListener() {
            public void onAnimationStart(Animator animation) {
                Log.d("tag", "onAnimationStart");
            }

            public void onAnimationEnd(Animator animation) {

            }

            public void onAnimationCancel(Animator animation) {

            }

            public void onAnimationRepeat(Animator animation) {

            }
        });

        valueAnimator.setRepeatMode(ValueAnimator.RESTART);
        valueAnimator.setRepeatCount(ValueAnimator.REVERSE);
        valueAnimator.setInterpolator(new LinearInterpolator());
        valueAnimator.setDuration(1000);
        valueAnimator.start();
    }

}

效果图:
在这里插入图片描述

ValueAnimator示例

需要注意的是,如果要移除监听,如调用removeAllListeners()后,并没有取消动画,因此动画会继续执行,但不再打印日志。

2.2 进阶用法ofObject

进阶用法指的是ValueAnimator.ofObject(xxx),因为它可以传入任何类型的参数,详见Demo。
布局文件:

<?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="match_parent">

    <Button
        android:id="@+id/start_anim"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="开始动画" />

    <TextView
        android:id="@+id/tv"
        android:layout_width="50dp"
        android:layout_height="20dp"
        android:layout_marginLeft="30dp"
        android:layout_toRightOf="@id/start_anim"
        android:background="#ffff00"
        android:gravity="center" />

</RelativeLayout>

自定义Evaluator:

package com.harvic.InterploatorEvaluator;

import android.animation.TypeEvaluator;

/**
* 字符转换
*/
public class CharEvaluator implements TypeEvaluator<Character> {

	@Override
    public Character evaluate(float fraction, Character startValue, Character endValue) {
        int startInt = (int) startValue;
        int endInt = (int) endValue;
        int curInt = (int) (startInt + fraction * (endInt - startInt));
        char result = (char) curInt;
        return result;
    }

}

示例代码:

package com.harvic.InterploatorEvaluator;

import android.animation.ValueAnimator;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.animation.AccelerateInterpolator;
import android.widget.TextView;


public class CharacterEvaluatorActivity extends Activity {
    private TextView tv;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.evaluator_activity);
        tv = (TextView) findViewById(R.id.tv);

        findViewById(R.id.start_anim).setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
            	// 从A到Z的字母变化动画
                ValueAnimator animator = ValueAnimator.ofObject(new CharEvaluator(), new Character('A'), new Character('Z'));

                animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                    public void onAnimationUpdate(ValueAnimator animation) {
                        char text = (Character) animation.getAnimatedValue();
                        tv.setText(String.valueOf(text));
                    }
                });

				// 由慢到快的加速变化
                animator.setInterpolator(new AccelerateInterpolator());
                animator.setDuration(10000);
                animator.start();
            }
        });
    }

}

用法也很简单,只要传入ofObject(xxx)对应参数即可,效果图:
ofObject进阶用法

ValueAnimator.ofObject(xxx)进阶用法

更高阶用法:
ValueAnimator和ObjectAnimator都还有一个ofPropertyValuesHolder(xxx)方法获取动画实例,两者用法类似,本文仅在ObjectAnimator章节介绍这部分内容,具体请参见下文。

3 ObjectAnimator

ValueAnimator只能对动画中的数值进行计算,如果想对控件执行操作则需要监听动画过程,会比较繁琐。
为了能让动画直接与对应控件关联,ObjectAnimator出场了。

package android.animation;

public final class ObjectAnimator extends ValueAnimator {
	...

	public void setPropertyName(@NonNull String propertyName) {
		...
	}

	public void setProperty(@NonNull Property property) {
		...
	}

	/**
     * 其余重载方法未列出
     */
	public static ObjectAnimator ofInt(Object target, String propertyName, int... values) {
        ObjectAnimator anim = new ObjectAnimator(target, propertyName);
        anim.setIntValues(values);
        return anim;
    }

	/**
     * 其余重载方法未列出
     */
	public static ObjectAnimator ofMultiInt(Object target, String propertyName, int[][] values) {
        PropertyValuesHolder pvh = PropertyValuesHolder.ofMultiInt(propertyName, values);
        return ofPropertyValuesHolder(target, pvh);
    }

	/**
     * 其余重载方法未列出
     */
	public static ObjectAnimator ofArgb(Object target, String propertyName, int... values) {
        ObjectAnimator animator = ofInt(target, propertyName, values);
        animator.setEvaluator(ArgbEvaluator.getInstance());
        return animator;
    }

	/**
     * 其余重载方法未列出
     */
	public static ObjectAnimator ofFloat(Object target, String propertyName, float... values) {
        ObjectAnimator anim = new ObjectAnimator(target, propertyName);
        anim.setFloatValues(values);
        return anim;
    }

	/**
     * 其余重载方法未列出
     */
     public static ObjectAnimator ofMultiFloat(Object target, String propertyName,
            float[][] values) {
        PropertyValuesHolder pvh = PropertyValuesHolder.ofMultiFloat(propertyName, values);
        return ofPropertyValuesHolder(target, pvh);
    }  

	/**
     * 其余重载方法未列出
     */
	public static ObjectAnimator ofObject(Object target, String propertyName,
            TypeEvaluator evaluator, Object... values) {
        ObjectAnimator anim = new ObjectAnimator(target, propertyName);
        anim.setObjectValues(values);
        anim.setEvaluator(evaluator);
        return anim;
    }

	@NonNull
    public static ObjectAnimator ofPropertyValuesHolder(Object target,
            PropertyValuesHolder... values) {
        ObjectAnimator anim = new ObjectAnimator();
        anim.setTarget(target);
        anim.setValues(values);
        return anim;
    }

	...
}

3.1 ObjectAnimator的用法

基本使用较简单,与ValueAnimator相似:

TextView tv = findViewById(R.id.tv);

findViewById(R.id.start_alpha).setOnClickListener(new View.OnClickListener() {
    public void onClick(View v) {
        // 实现alpha值变化
        ObjectAnimator animator = ObjectAnimator.ofFloat(tv, "alpha", 1, 0, 1);
        animator.setInterpolator(new LinearInterpolator());
        animator.setEvaluator(new IntEvaluator());
        animator.setDuration(2000);
        animator.start();
    }
});

ObjectAnimator做动画,不是根据控件xml中的属性值来改变的,而是通过指定属性所对应的set方法来改变的,如对TextView的透明度做动画,就会调用View类中的setAlpha(float alpha)方法,归纳起来就是:
1、要使用ObejctAnimator来构造动画,在要操作的控件中必须存在对应属性的set方法,且参数类型必须与构造所使用的ofInt(xxx)、ofArgb(xxx)、ofFloat(xxx)、ofObject(xxx)等方法一致。
2、set方法的命名必须采用驼峰命名法,且以set开头,即setXxxx(xxx)。如setPropertyName(xxx)所对应的属性是propertyName。

3.2 ObjectAnimator动画原理

从上文属性动画执行流程图中可以看出,ValueAnimator动画与ObjectAnimator动画的流程相似,区别是最后一个步骤,ValueAnimator需要通过添加监听器监听当前的数值,而ObjectAnimator则需要根据属性值拼接对应的setXxx(xxx)方法,并通过反射找到对应控件的同名setXxx(xxx)方法,再将当前的数值作为setXxxx(xxx)方法的参数传入。

总结动画执行流程,有几个注意事项:
1、拼接对应的setXxxx(xxx)方法。
2、确定函数类型。与ValueAnimator一样,ObjectAnimator动画中产生的数值类型与构造时传入的值类型也是一样的。如果方法名一样,参数类型不一样,系统会报错。
3、调用setXxx(xxx)方法之后的操作由谁做?
ObjectAnimator只负责把动画过程中的数值传到对应属性的setXxx(xxx)方法中,该setXxxx(xxx)方法相当于ValueAnimator中添加的监听器,而setXxx(xxx)方法中对控件的操作需要开发者手动编码。
4、由于动画在进行时每隔十几毫秒会刷新一次,因此setXxx(xxx)方法也会同频率调用一次。

3.3 自定义ObjectAnimator属性

再捋一遍ObjectAnimator动画的设置流程:
ObjectAnimator需要指定要操作的控件对象,在开始动画后,先到控件类中区寻找要设置的属性所对应的set方法,然后把动画中间值作为参数传给这个set方法并执行它。
因此,控件类中必须存在所要设置的属性所对应的set方法。

用法举例:
1、自定义圆形的shape文件circle.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="oval">
    <solid android:color="#ff0000" />
</shape>

2、自定义ImageView,命名为FallingBallImageView.java

package com.example.objectanim;

import android.content.Context;
import android.graphics.Point;
import android.util.AttributeSet;
import android.widget.ImageView;

public class FallingBallImageView extends ImageView {

    public FallingBallImageView(Context context) {
        super(context);
    }

    public FallingBallImageView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public FallingBallImageView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

	/**
     * 在ObjectAnimator中设置fallingPos属性时对应该方法:
     * 1、该方法所对应的属性只能是FallingPos或者fallingPos
     * 2、方法参数是Point对象,则ObjectAnimator的构造方法必须使用ofObject()
     */
    public void setFallingPos(Point pos) {
        layout(pos.x, pos.y, pos.x + getWidth(), pos.y + getHeight());
    }

	/**
     * ObjectAnimator调用ofXxx(xxx)方法时,若参数中只设置一个过渡值,则会使用方法参数类型的默认值,如果没有默认值就会报错。
     * ofObject(xxx)没有参数类型默认值,只传入一个参数过渡值时,会调用该方法获取默认值。
     */
    public Point getFallingPos() {
        Point point = new Point(0, 0);
        return point;
    }

}

3、布局文件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="horizontal">

    <com.example.objectanim.FallingBallImageView
        android:id="@+id/ball_img"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:src="@drawable/circle" />

    <Button
        android:id="@+id/start_anim"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="开始动画" />

</LinearLayout>

4、自定义转换器FallingBallEvaluator

package com.example.objectanim;

import android.animation.TypeEvaluator;
import android.graphics.Point;

public class FallingBallEvaluator implements TypeEvaluator<Point> {
    private Point point = new Point();

    @Override
    public Point evaluate(float fraction, Point startValue, Point endValue) {
        point.x = (int) (startValue.x + fraction * (endValue.x - startValue.x));

        if (fraction * 2 <= 1) {
            point.y = (int) (startValue.y + fraction * 2 * (endValue.y - startValue.y));
        } else {
            point.y = endValue.y;
        }

        return point;
    }

}

5、使用动画

package com.example.objectanim;

import android.animation.ObjectAnimator;
import android.app.Activity;
import android.graphics.Point;
import android.os.Bundle;
import android.view.View;

public class FallingBallActivity extends Activity {
    private FallingBallImageView ball_img;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.falling_ball_activity);

        ball_img = findViewById(R.id.ball_img);

        findViewById(R.id.start_anim).setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
            	/*
                只设置一个参数过渡值时,会使用方法参数类型的默认值,如果没有默认值就会报错。
                ofObject(xxx)没有默认值,但在FallingBallImageView中定义了getFallingPos(),因此此处不会报错。
                 */
               // ObjectAnimator animator = ObjectAnimator.ofObject(ball_img, "fallingPos", new FallingBallEvaluator(), new Point(500, 500));

            	// fallingPos为自定义属性,因此需要自定义对应的setFallingPos(xxx)方法,见FallingBallImageView
                ObjectAnimator animator = ObjectAnimator.ofObject(ball_img, "fallingPos", new FallingBallEvaluator(), new Point(0, 0), new Point(500, 500));
                animator.setDuration(2000);
                animator.start();
            }
        });
    }

}

效果图:
自定义ObjectAnimator属性

自定义ObjectAnimator属性

ObjectAnimator自定义属性注意事项:
1、当且仅当只给动画设置一个过渡值时,程序会调用属性对应的get()方法来获得动画初始值。如果动画没有初始值,就会使用系统默认值。
2、ObjectAnimator的ofInt(xxx)、ofFloat(xxx)方法有参数类型默认值0;而ofObject(xxx)由于需要手动指定动画的参数类型,故不一定有参数类型默认值。因此如果在调用ofXxx(xxx)方法是只传入一个参数过渡值,则可能会报错。

总结起来就是:当且仅当动画只有一个过渡值时,系统才会调用对应属性的get()方法来得到动画的初始值。当不存在get()方法时,则会取动画参数类型的默认值作为初始值;当无法取得动画参数类型的默认值时,会直接崩溃。

3.4 PropertyValuesHolder与Keyframe

ValueAnimator和ObjectAnimator都有一个ofPropertyValuesHolder(xxx)方法获取动画实例,两者用法类似,先来看下这两个方法:

/**
 * ValueAnimator中的方法
 */
public static ValueAnimator ofPropertyValuesHolder(PropertyValuesHolder... values) {
    ValueAnimator anim = new ValueAnimator();
    anim.setValues(values);
    return anim;
}
      
/**
 * ObjectAnimator中的方法
 */
@NonNull
public static ObjectAnimator ofPropertyValuesHolder(Object target,
        PropertyValuesHolder... values) {
    ObjectAnimator anim = new ObjectAnimator();
    anim.setTarget(target);
    anim.setValues(values);
    return anim;
}

再来看一下PropertyValuesHolder:

package android.animation;

public class PropertyValuesHolder implements Cloneable {
	...

	public static PropertyValuesHolder ofInt(String propertyName, int... values) {
        return new IntPropertyValuesHolder(propertyName, values);
    }
    
    public static PropertyValuesHolder ofInt(Property<?, Integer> property, int... values) {
        return new IntPropertyValuesHolder(property, values);
    }
    
    public static PropertyValuesHolder ofFloat(String propertyName, float... values) {
        return new FloatPropertyValuesHolder(propertyName, values);
    }

    public static PropertyValuesHolder ofFloat(Property<?, Float> property, float... values) {
        return new FloatPropertyValuesHolder(property, values);
    }
    
    public static PropertyValuesHolder ofObject(String propertyName, TypeEvaluator evaluator,
            Object... values) {
        PropertyValuesHolder pvh = new PropertyValuesHolder(propertyName);
        pvh.setObjectValues(values);
        pvh.setEvaluator(evaluator);
        return pvh;
    }
    
    public static PropertyValuesHolder ofObject(String propertyName,
            TypeConverter<PointF, ?> converter, Path path) {
        PropertyValuesHolder pvh = new PropertyValuesHolder(propertyName);
        pvh.mKeyframes = KeyframeSet.ofPath(path);
        pvh.mValueType = PointF.class;
        pvh.setConverter(converter);
        return pvh;
    }
    
    @SafeVarargs
    public static <V> PropertyValuesHolder ofObject(Property property,
            TypeEvaluator<V> evaluator, V... values) {
        PropertyValuesHolder pvh = new PropertyValuesHolder(property);
        pvh.setObjectValues(values);
        pvh.setEvaluator(evaluator);
        return pvh;
    }
    
    @SafeVarargs
    public static <T, V> PropertyValuesHolder ofObject(Property<?, V> property,
            TypeConverter<T, V> converter, TypeEvaluator<T> evaluator, T... values) {
        PropertyValuesHolder pvh = new PropertyValuesHolder(property);
        pvh.setConverter(converter);
        pvh.setObjectValues(values);
        pvh.setEvaluator(evaluator);
        return pvh;
    }
    
    public static <V> PropertyValuesHolder ofObject(Property<?, V> property,
            TypeConverter<PointF, V> converter, Path path) {
        PropertyValuesHolder pvh = new PropertyValuesHolder(property);
        pvh.mKeyframes = KeyframeSet.ofPath(path);
        pvh.mValueType = PointF.class;
        pvh.setConverter(converter);
        return pvh;
    }
    
    public static PropertyValuesHolder ofKeyframe(String propertyName, Keyframe... values) {
        KeyframeSet keyframeSet = KeyframeSet.ofKeyframe(values);
        return ofKeyframes(propertyName, keyframeSet);
    }
    
    public static PropertyValuesHolder ofKeyframe(Property property, Keyframe... values) {
        KeyframeSet keyframeSet = KeyframeSet.ofKeyframe(values);
        return ofKeyframes(property, keyframeSet);
    }
    
    static PropertyValuesHolder ofKeyframes(String propertyName, Keyframes keyframes) {
        ...
    }

    static PropertyValuesHolder ofKeyframes(Property property, Keyframes keyframes) {
        ...
    }
    
    public void setIntValues(int... values) {
        mValueType = int.class;
        mKeyframes = KeyframeSet.ofInt(values);
    }
    
    public void setFloatValues(float... values) {
        mValueType = float.class;
        mKeyframes = KeyframeSet.ofFloat(values);
    }
    
    public void setKeyframes(Keyframe... values) {
        ...
    }
    
    public void setObjectValues(Object... values) {
        ...
    }
    
    public void setEvaluator(TypeEvaluator evaluator) {
        mEvaluator = evaluator;
        mKeyframes.setEvaluator(evaluator);
    }
    
    public void setPropertyName(String propertyName) {
        mPropertyName = propertyName;
    }
    
    public void setProperty(Property property) {
        mProperty = property;
    }
    
    ...
}

3.4.1 PropertyValuesHolder的ofInt(x)、ofFloat(x)

直接看示例

findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
    public void onClick(View v) {
        PropertyValuesHolder rotationHolder = PropertyValuesHolder.ofFloat("Rotation", 60f, -60f, 40f, -40f, -20f, 20f, 10f, -10f, 0f);
        PropertyValuesHolder alphaHolder = PropertyValuesHolder.ofFloat("alpha", 0.1f, 1f, 0.1f, 1f);
        ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(mTextView, rotationHolder, alphaHolder);
        animator.setDuration(3000);
        animator.start();
    }
});

3.4.2 PropertyValuesHolder.ofObject(x)

再次引用ValueAnimator的ofObject(x)改变字符的动画例子来展示PropertyValuesHolder.ofObject(x)的用法。

自定义一个CharEvaluator(与ValueAnimator例子中的一样)

package com.example.objectanim;

import android.animation.TypeEvaluator;

public class CharEvaluator implements TypeEvaluator<Character> {

    @Override
    public Character evaluate(float fraction, Character startValue, Character endValue) {
        int startInt = (int) startValue;
        int endInt = (int) endValue;
        int curInt = (int) (startInt + fraction * (endInt - startInt));
        char result = (char) curInt;
        return result;
    }
    
}

自定义一个MyTextView,注意,TextView中有setText(CharSequence text)方法,其参数不是Character,因此我们在MyTextView中要定义一个有Character参数的方法:

package com.example.objectanim;

import android.content.Context;
import android.util.AttributeSet;
import android.widget.TextView;


public class MyTextView extends TextView {
    
    public MyTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

	/**
     * 动画类中设置属性为CharText或charText,再通过反射调用此方法
     */
    public void setCharText(Character character) {
        setText(String.valueOf(character));
    }

}

布局文件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical">

    <Button
        android:id="@+id/btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="start anim" />

    <com.example.objectanim.MyTextView
        android:id="@+id/mytv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:background="@android:color/darker_gray"
        android:padding="20dp" />
    
</LinearLayout>

PropertyValuesHolder.ofObject(x)用法:

MyTextView mMyTv = findViewById(R.id.mytv);

findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
    public void onClick(View v) {
    	// 属性设置为CharText或charText
        PropertyValuesHolder charHolder = PropertyValuesHolder.ofObject("CharText", new CharEvaluator(), new Character('A'), new Character('Z'));
        ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(mMyTv, charHolder);
        animator.setDuration(3000);
        animator.setInterpolator(new AccelerateInterpolator());
        animator.start();
    }
});

动画效果与《ValueAnimator的进阶用法ofObject》部分相同,可返回查看。

3.4.3 PropertyValuesHolder.ofKeyframe(x)

先来看下抽象类Keyframe:

package android.animation;

public abstract class Keyframe implements Cloneable {
	...
	
	private TimeInterpolator mInterpolator = null;

	public static Keyframe ofInt(float fraction, int value) {
        return new IntKeyframe(fraction, value);
    }
    
    public static Keyframe ofInt(float fraction) {
        return new IntKeyframe(fraction);
    }
    
    public static Keyframe ofFloat(float fraction, float value) {
        return new FloatKeyframe(fraction, value);
    }
    
    public static Keyframe ofFloat(float fraction) {
        return new FloatKeyframe(fraction);
    }
    
    public static Keyframe ofObject(float fraction, Object value) {
        return new ObjectKeyframe(fraction, value);
    }
    
    public static Keyframe ofObject(float fraction) {
        return new ObjectKeyframe(fraction, null);
    }
    
    public abstract void setValue(Object value);
    
    public void setInterpolator(TimeInterpolator interpolator) {
        mInterpolator = interpolator;
    }
    
	...
}

Keyframe可译为关键帧,一个关键帧必须包含两个元素:时间和位置。即这个关键帧表示的是某个物体在哪个时间点时应该在哪个位置上。

仍然以上述改变字符的动画作为示例:

MyTextView mMyTv = findViewById(R.id.mytv);

findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
    public void onClick(View v) {
        Keyframe frame0 = Keyframe.ofObject(0f, new Character('A'));
        Keyframe frame1 = Keyframe.ofObject(0.1f, new Character('L'));
        Keyframe frame2 = Keyframe.ofObject(1, new Character('Z'));

        PropertyValuesHolder frameHolder = PropertyValuesHolder.ofKeyframe("CharText", frame0, frame1, frame2);
        frameHolder.setEvaluator(new CharEvaluator());
        ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(mMyTv, frameHolder);
        animator.setDuration(3000);
        animator.start();
    }
});

对比PropertyValuesHolder.ofObject(x)和PropertyValuesHolder.ofKeyframe(x)可以发现,用法大同小异。

动画效果与《ValueAnimator的进阶用法ofObject》部分相同,可返回查看。

用Keyframe实现动画的注意事项:
1、如果去掉第0帧,则将以第一个关键帧作为起始位置。
2、如果去掉结束帧,则将以最后一个关键帧作为结束位置。
3、使用Keyframe来构建动画,至少要有2帧。

要点:
不使用AnimatorSet,借助Keyframe也可以实现多个动画同时播放。Keyframe是ObjectAnimator中唯一的一个能实现多动画同时播放的方法,其它的如ObjectAnimator.ofInt(x)、ObjectAnimator.ofFloat(x)、ObjectAnimator.ofObject(x)等都只能实现针对一个属性做动画的操作。

4 组合动画AnimatorSet

在这里插入图片描述

AnimatorSet类图

ValueAnimator和ObjectAnimator只能单独实现一个动画,若想实现组合动画就需要用到AnimatorSet。

package android.animation;

public final class AnimatorSet extends Animator implements AnimationHandler.AnimationFrameCallback {
	...

	public void playTogether(Animator... items) {
        if (items != null) {
            Builder builder = play(items[0]);
            for (int i = 1; i < items.length; ++i) {
                builder.with(items[i]);
            }
        }
    }
    
    public void playTogether(Collection<Animator> items) {
        if (items != null && items.size() > 0) {
            Builder builder = null;
            for (Animator anim : items) {
                if (builder == null) {
                    builder = play(anim);
                } else {
                    builder.with(anim);
                }
            }
        }
    }
    
    public void playSequentially(Animator... items) {
        if (items != null) {
            if (items.length == 1) {
                play(items[0]);
            } else {
                for (int i = 0; i < items.length - 1; ++i) {
                    play(items[i]).before(items[i + 1]);
                }
            }
        }
    }
    
    public void playSequentially(List<Animator> items) {
        if (items != null && items.size() > 0) {
            if (items.size() == 1) {
                play(items.get(0));
            } else {
                for (int i = 0; i < items.size() - 1; ++i) {
                    play(items.get(i)).before(items.get(i + 1));
                }
            }
        }
    }
    
    @Override
    public void setTarget(Object target) {
        int size = mNodes.size();
        for (int i = 0; i < size; i++) {
            Node node = mNodes.get(i);
            Animator animation = node.mAnimation;
            if (animation instanceof AnimatorSet) {
                ((AnimatorSet)animation).setTarget(target);
            } else if (animation instanceof ObjectAnimator) {
                ((ObjectAnimator)animation).setTarget(target);
            }
        }
    }
    
    @Override
    public void setInterpolator(TimeInterpolator interpolator) {
        mInterpolator = interpolator;
    }
    
    /**
     * 获取Builder对象的唯一途径
     */
    public Builder play(Animator anim) {
        if (anim != null) {
            return new Builder(anim);
        }
        return null;
    }
    
    @SuppressWarnings("unchecked")
    @Override
    public void cancel() {
        if (Looper.myLooper() == null) {
            throw new AndroidRuntimeException("Animators may only be run on Looper threads");
        }
        if (isStarted() || mStartListenersCalled) {
            notifyListeners(AnimatorCaller.ON_CANCEL, false);
            callOnPlayingSet(Animator::cancel);
            mPlayingSet.clear();
            endAnimation();
        }
    }
    
     @Override
    public boolean isRunning() {
        if (mStartDelay == 0) {
            return mStarted;
        }
        return mLastFrameTime > 0;
    }

    @Override
    public boolean isStarted() {
        return mStarted;
    }
    
    @Override
    public void setStartDelay(long startDelay) {
    	...
    }
    
    @Override
    public AnimatorSet setDuration(long duration) {
        if (duration < 0) {
            throw new IllegalArgumentException("duration must be a value of zero or greater");
        }
        mDependencyDirty = true;
        // Just record the value for now - it will be used later when the AnimatorSet starts
        mDuration = duration;
        return this;
    }
    
    @Override
    public AnimatorSet setDuration(long duration) {
        if (duration < 0) {
            throw new IllegalArgumentException("duration must be a value of zero or greater");
        }
        mDependencyDirty = true;
        // Just record the value for now - it will be used later when the AnimatorSet starts
        mDuration = duration;
        return this;
    }

	...

	private static class Node implements Cloneable {
		...
	}
	
	private static class AnimationEvent {
		...
	}
	
	private class SeekState {
		...
	}
	
	public class Builder {
        private Node mCurrentNode;
        
        Builder(Animator anim) {
            mDependencyDirty = true;
            mCurrentNode = getNodeForAnimation(anim);
        }
        
        public Builder with(Animator anim) {
            Node node = getNodeForAnimation(anim);
            mCurrentNode.addSibling(node);
            return this;
        }

        public Builder before(Animator anim) {
            Node node = getNodeForAnimation(anim);
            mCurrentNode.addChild(node);
            return this;
        }

        public Builder after(Animator anim) {
            Node node = getNodeForAnimation(anim);
            mCurrentNode.addParent(node);
            return this;
        }

        public Builder after(long delay) {
            // setup a ValueAnimator just to run the clock
            ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
            anim.setDuration(delay);
            after(anim);
            return this;
        }
    }
    
}

AnimatorSet中的两个方法playSequentially(xxx)和playTogether(xxx)都可以实现组合动画效果,前者表示所有动画依次播放,后者表示所有动画一起开始(注意,只是一起开始)。
1、playSequentially(xxx)和playTogether(xxx)方法在开始动画时,只是把每个控件的动画激活,至于每个控件自身的动画是否延时、是否无限循环,只与控件本身的动画设定有关,与方法本身无关,因为方法本身只负责到时间激活动画。
2、playSequentially(xxx)方法只有在上一个控件完成动画以后才会激活下一个控件的动画。如果上一个控件的动画是无限循环的,则下一个控件就不指望做动画了。

playSequentially(xxx)和playTogether(xxx)不能自由的组合动画,而AnimatorSet.Builder类能实现两个控件一起开始的动画。

AnimatorSet继承自Animator,因此可以使用Animator中的监听器,但是:
1、AnimatorSet的监听方法只能监听AnimatorSet的状态,与其中的动画无关
2、AnimatorSet中没有设置循环的方法,所以动画执行一次就结束了,永远无法执行到onAnimationRepeat(xxx)方法。

用法示例:

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

    <Button
        android:id="@+id/btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:padding="10dp"
        android:text="start anim" />

    <TextView
        android:id="@+id/tv_1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:layout_marginRight="30dp"
        android:background="@android:color/darker_gray"
        android:padding="10dp"
        android:text="TextView-1" />

    <TextView
        android:id="@+id/tv_2"
        android:layout_width="100dp"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:background="@android:color/darker_gray"
        android:gravity="center"
        android:padding="10dp"
        android:text="TextView-2" />

</RelativeLayout>
package com.example.objectanim;

import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;


public class playSequentiallyActivity extends Activity {
    private Button mButton;
    private TextView mTv1, mTv2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.play_sequentially_activity);

        mButton = findViewById(R.id.btn);
        mTv1 = findViewById(R.id.tv_1);
        mTv2 = findViewById(R.id.tv_2);

        mButton.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                // playSequentially(xxx)
                ObjectAnimator tv1BgAnimator = ObjectAnimator.ofInt(mTv1, "BackgroundColor", 0xffff00ff, 0xffffff00, 0xffff00ff);
                ObjectAnimator tv1TranslateY = ObjectAnimator.ofFloat(mTv1, "translationY", 0, 300, 0);
                ObjectAnimator tv2TranslateY = ObjectAnimator.ofFloat(mTv2, "translationY", 0, 400, 0);

                AnimatorSet animatorSet1 = new AnimatorSet();
                animatorSet1.playSequentially(tv1BgAnimator, tv1TranslateY, tv2TranslateY);
                animatorSet1.setDuration(1000);
                animatorSet1.start();

                // playTogether(xxx)
                ObjectAnimator tv1BgAnimator2 = ObjectAnimator.ofInt(mTv1, "BackgroundColor", 0xffff00ff, 0xffffff00, 0xffff00ff);
                ObjectAnimator tv1TranslateY2 = ObjectAnimator.ofFloat(mTv1, "translationY", 0, 400, 0);
                ObjectAnimator tv2TranslateY2 = ObjectAnimator.ofFloat(mTv2, "translationY", 0, 400, 0);

                AnimatorSet animatorSet2 = new AnimatorSet();
                animatorSet2.playTogether(tv1BgAnimator2, tv1TranslateY2, tv2TranslateY2);
                animatorSet2.setDuration(1000);
                animatorSet2.start();

                // 使用AnimatorSet.Builder类
                ObjectAnimator tv1BgAnimator3 = ObjectAnimator.ofInt(mTv1, "BackgroundColor", 0xffff00ff, 0xffffff00, 0xffff00ff);
                ObjectAnimator tv1TranslateY3 = ObjectAnimator.ofFloat(mTv1, "translationY", 0, 400, 0);
                ObjectAnimator tv2TranslateY3 = ObjectAnimator.ofFloat(mTv2, "translationY", 0, 400, 0);

                AnimatorSet animatorSet3 = new AnimatorSet();

                AnimatorSet.Builder builder = animatorSet3.play(tv1TranslateY3);
                builder.with(tv2TranslateY3);
                builder.after(tv1BgAnimator3);
                // 等价于上述三行代码
                //animatorSet3.play(tv1TranslateY3).with(tv2TranslateY3).after(tv1BgAnimator3);
                
                animatorSet3.setDuration(2000);
                animatorSet3.start();
            }
        });
    }

}

效果图省略。

5 XML实现Animator

在XML中与Animator对应的三个标签:
< animator />对应ValueAnimator;
< objectAnimator />对应ObjectAnimator;
< set />对应AnimatorSet。

要用XML方式实现Animator需要用到的类方法是AnimatorInflater.loadAnimator(xxx),该方法返回Animator,根据需要将其强转为目标动画类型即可。

注意:
View Animation(视图动画)中的Tween Animation(补间动画)的XML实现方式是调用类方法AnimationUtils.loadAnimation(xxx),该方法返回值是Animation。

package android.animation;

public class AnimatorInflater {
	...
	
	public static Animator loadAnimator(Context context, @AnimatorRes int id)
            throws NotFoundException {
        return loadAnimator(context.getResources(), context.getTheme(), id);
    }
    
    public static Animator loadAnimator(Resources resources, Theme theme, int id)
            throws NotFoundException {
        return loadAnimator(resources, theme, id, 1);
    }
    
    public static Animator loadAnimator(Resources resources, Theme theme, int id,
            float pathErrorScale) throws NotFoundException {
		...
	}
	
	private static ValueAnimator loadAnimator(Resources res, Theme theme,
            AttributeSet attrs, ValueAnimator anim, float pathErrorScale)
            throws NotFoundException {
        ... 
    }
    
    ...
}

用法示例:
通用布局文件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical">

    <Button
        android:id="@+id/start_anim"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="start anim" />

    <TextView
        android:id="@+id/tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@android:color/darker_gray"
        android:padding="10dp"
        android:text="textview" />

</LinearLayout>

在res目录下新建anim文件夹,存放对应的动画的xml文件。
value_animator.xml:

<?xml version="1.0" encoding="utf-8"?>
<animator xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1000"
    android:interpolator="@android:anim/bounce_interpolator"
    android:valueFrom="0"
    android:valueTo="300"
    android:valueType="intType" />

object_animator.xml:

<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="2000"
    android:interpolator="@android:anim/accelerate_interpolator"
    android:propertyName="TranslationY"
    android:repeatCount="1"
    android:repeatMode="reverse"
    android:startOffset="2000"
    android:valueFrom="0.0"
    android:valueTo="400.0"
    android:valueType="floatType" />

animator_set.xml:

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:ordering="together">
    <animator
        android:duration="500"
        android:propertyName="x"
        android:valueFrom="0"
        android:valueTo="400"
        android:valueType="floatType" />

    <objectAnimator
        android:duration="500"
        android:propertyName="y"
        android:valueFrom="0"
        android:valueTo="300"
        android:valueType="floatType" />
</set>

在代码中使用各标签:

package com.example.objectanim;

import android.animation.AnimatorInflater;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;


public class AnimXMLActivity extends Activity {
    private TextView mTv1;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.value_anim_activity);

        mTv1 = findViewById(R.id.tv);

        findViewById(R.id.start_anim).setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                // 在代码中使用<animator/>,根据需要强转为目标动画类型
                ValueAnimator valueAnimator = (ValueAnimator) AnimatorInflater.loadAnimator(AnimXMLActivity.this, R.animator.value_animator);
                valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                    public void onAnimationUpdate(ValueAnimator animation) {
                        int offset = (Integer) animation.getAnimatedValue();
                        mTv1.layout(offset, offset, mTv1.getWidth() + offset, mTv1.getHeight() + offset);
                    }
                });
                valueAnimator.start();

                // 在代码中使用<objectAnimator/>,根据需要强转为目标动画类型
                ObjectAnimator animator = (ObjectAnimator) AnimatorInflater.loadAnimator(AnimXMLActivity.this, R.animator.object_animator);
                animator.setTarget(mTv1);
                animator.start();

                // 在代码中使用<set/>,根据需要强转为目标动画类型
                AnimatorSet set = (AnimatorSet) AnimatorInflater.loadAnimator(AnimXMLActivity.this, R.animator.animator_set);
                set.setTarget(mTv1);
                set.start();
            }
        });

    }

}

效果图略。

6 ViewPropertyAnimator

ViewPropertyAnimator在API Level 12(Android 3.1)时引入,位于包android.view中,是一个单独的类,不继承其它类,也不实现其它接口,仅包含三个具体内部类,其类图(Class Diagram)如图所示:
ViewPropertyAnimator类图

ViewPropertyAnimator类图
package android.view;

public class ViewPropertyAnimator {
	...
	
	final View mView;
	private long mDuration;
	private boolean mDurationSet = false;
	
	private TimeInterpolator mInterpolator;
	
	private Animator.AnimatorListener mListener = null;
	private ValueAnimator.AnimatorUpdateListener mUpdateListener = null;
	private ValueAnimator mTempValueAnimator;
	private AnimatorEventListener mAnimatorEventListener = new AnimatorEventListener();
	
	static final int NONE           = 0x0000;
    static final int TRANSLATION_X  = 0x0001;
    static final int TRANSLATION_Y  = 0x0002;
    static final int TRANSLATION_Z  = 0x0004;
    static final int SCALE_X        = 0x0008;
    static final int SCALE_Y        = 0x0010;
    static final int ROTATION       = 0x0020;
    static final int ROTATION_X     = 0x0040;
    static final int ROTATION_Y     = 0x0080;
    static final int X              = 0x0100;
    static final int Y              = 0x0200;
    static final int Z              = 0x0400;
    static final int ALPHA          = 0x0800;
    
    public @NonNull ViewPropertyAnimator setDuration(long duration) {
        if (duration < 0) {
            throw new IllegalArgumentException("Animators cannot have negative duration: " +
                    duration);
        }
        mDurationSet = true;
        mDuration = duration;
        return this;
    }
    
    public @NonNull ViewPropertyAnimator setStartDelay(long startDelay) {
        if (startDelay < 0) {
            throw new IllegalArgumentException("Animators cannot have negative start " +
                "delay: " + startDelay);
        }
        mStartDelaySet = true;
        mStartDelay = startDelay;
        return this;
    }
    
    public @NonNull ViewPropertyAnimator setInterpolator(TimeInterpolator interpolator) {
        mInterpolatorSet = true;
        mInterpolator = interpolator;
        return this;
    }
    
    public @NonNull ViewPropertyAnimator setListener(@Nullable Animator.AnimatorListener listener) {
        mListener = listener;
        return this;
    }
    
    public @NonNull ViewPropertyAnimator setUpdateListener(
            @Nullable ValueAnimator.AnimatorUpdateListener listener) {
        mUpdateListener = listener;
        return this;
    }
    
    public void start() {
        mView.removeCallbacks(mAnimationStarter);
        startAnimation();
    }
    
    public @NonNull ViewPropertyAnimator x(float value) {
        animateProperty(X, value);
        return this;
    }
    
    public @NonNull ViewPropertyAnimator xBy(float value) {
        animatePropertyBy(X, value);
        return this;
    }
    
    public @NonNull ViewPropertyAnimator y(float value) {
        animateProperty(Y, value);
        return this;
    }
    
    public @NonNull ViewPropertyAnimator yBy(float value) {
        animatePropertyBy(Y, value);
        return this;
    }
    
    public @NonNull ViewPropertyAnimator z(float value) {
        animateProperty(Z, value);
        return this;
    }
    
    public @NonNull ViewPropertyAnimator zBy(float value) {
        animatePropertyBy(Z, value);
        return this;
    }
    
    public @NonNull ViewPropertyAnimator rotation(float value) {
        animateProperty(ROTATION, value);
        return this;
    }
    
    public @NonNull ViewPropertyAnimator rotationBy(float value) {
        animatePropertyBy(ROTATION, value);
        return this;
    }
    
    public @NonNull ViewPropertyAnimator rotationX(float value) {
        animateProperty(ROTATION_X, value);
        return this;
    }
    
    public @NonNull ViewPropertyAnimator rotationXBy(float value) {
        animatePropertyBy(ROTATION_X, value);
        return this;
    }
    
    public @NonNull ViewPropertyAnimator rotationY(float value) {
        animateProperty(ROTATION_Y, value);
        return this;
    }
    
    public @NonNull ViewPropertyAnimator rotationYBy(float value) {
        animatePropertyBy(ROTATION_Y, value);
        return this;
    }
    
    public @NonNull ViewPropertyAnimator translationX(float value) {
        animateProperty(TRANSLATION_X, value);
        return this;
    }
    
    public @NonNull ViewPropertyAnimator translationXBy(float value) {
        animatePropertyBy(TRANSLATION_X, value);
        return this;
    }
    
    public @NonNull ViewPropertyAnimator translationY(float value) {
        animateProperty(TRANSLATION_Y, value);
        return this;
    }
    
    public @NonNull ViewPropertyAnimator translationYBy(float value) {
        animatePropertyBy(TRANSLATION_Y, value);
        return this;
    }
    
    public @NonNull ViewPropertyAnimator translationZ(float value) {
        animateProperty(TRANSLATION_Z, value);
        return this;
    }
    
    public @NonNull ViewPropertyAnimator translationZBy(float value) {
        animatePropertyBy(TRANSLATION_Z, value);
        return this;
    }
    
    public @NonNull ViewPropertyAnimator scaleX(float value) {
        animateProperty(SCALE_X, value);
        return this;
    }
    
    public @NonNull ViewPropertyAnimator scaleXBy(float value) {
        animatePropertyBy(SCALE_X, value);
        return this;
    }
    
    public @NonNull ViewPropertyAnimator scaleY(float value) {
        animateProperty(SCALE_Y, value);
        return this;
    }
    
    public @NonNull ViewPropertyAnimator scaleYBy(float value) {
        animatePropertyBy(SCALE_Y, value);
        return this;
    }
    
    public @NonNull ViewPropertyAnimator alpha(@FloatRange(from = 0.0f, to = 1.0f) float value) {
        animateProperty(ALPHA, value);
        return this;
    }
    
    public @NonNull ViewPropertyAnimator alphaBy(float value) {
        animatePropertyBy(ALPHA, value);
        return this;
    }

	private void startAnimation() {
		...
	}
	
	private void animateProperty(int constantName, float toValue) {
        float fromValue = getValue(constantName);
        float deltaValue = toValue - fromValue;
        animatePropertyBy(constantName, fromValue, deltaValue);
    }
    
    private void animatePropertyBy(int constantName, float byValue) {
        float fromValue = getValue(constantName);
        animatePropertyBy(constantName, fromValue, byValue);
    }
    
    /**
     * 实现动画的核心逻辑
     * 
     * Utility function, called by animateProperty() and animatePropertyBy(), which handles the
     * details of adding a pending animation and posting the request to start the animation.
     *
     * @param constantName The specifier for the property being animated
     * @param startValue The starting value of the property
     * @param byValue The amount by which the property will change
     */
    private void animatePropertyBy(int constantName, float startValue, float byValue) {
        // First, cancel any existing animations on this property
        if (mAnimatorMap.size() > 0) {
            Animator animatorToCancel = null;
            Set<Animator> animatorSet = mAnimatorMap.keySet();
            for (Animator runningAnim : animatorSet) {
                PropertyBundle bundle = mAnimatorMap.get(runningAnim);
                if (bundle.cancel(constantName)) {
                    // property was canceled - cancel the animation if it's now empty
                    // Note that it's safe to break out here because every new animation
                    // on a property will cancel a previous animation on that property, so
                    // there can only ever be one such animation running.
                    if (bundle.mPropertyMask == NONE) {
                        // the animation is no longer changing anything - cancel it
                        animatorToCancel = runningAnim;
                        break;
                    }
                }
            }
            if (animatorToCancel != null) {
                animatorToCancel.cancel();
            }
        }

        NameValuesHolder nameValuePair = new NameValuesHolder(constantName, startValue, byValue);
        mPendingAnimations.add(nameValuePair);
        mView.removeCallbacks(mAnimationStarter);
        mView.postOnAnimation(mAnimationStarter);
    }
	
	private static class PropertyBundle {
		...
	}
	
	static class NameValuesHolder {
        int mNameConstant;
        float mFromValue;
        float mDeltaValue;
        NameValuesHolder(int nameConstant, float fromValue, float deltaValue) {
            mNameConstant = nameConstant;
            mFromValue = fromValue;
            mDeltaValue = deltaValue;
        }
    }

	private class AnimatorEventListener
            implements Animator.AnimatorListener, ValueAnimator.AnimatorUpdateListener {
            
        @Override
        public void onAnimationStart(@NonNull Animator animation) {
            ...
        }

        @Override
        public void onAnimationCancel(@NonNull Animator animation) {
            if (mListener != null) {
                mListener.onAnimationCancel(animation);
            }
            if (mAnimatorOnEndMap != null) {
                mAnimatorOnEndMap.remove(animation);
            }
        }

        @Override
        public void onAnimationRepeat(@NonNull Animator animation) {
            if (mListener != null) {
                mListener.onAnimationRepeat(animation);
            }
        }

        @Override
        public void onAnimationEnd(@NonNull Animator animation) {
            ...
        }

        @Override
        public void onAnimationUpdate(@NonNull ValueAnimator animation) {
            ...
        }
    }

	...
}

ViewPropertyAnimator通过与View类关联来实现动画效果:

package android.view;

@UiThread
public class View implements Drawable.Callback, KeyEvent.Callback,
        AccessibilityEventSource {
	...
	
	protected Animation mCurrentAnimation = null;
	
	@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
    private ViewPropertyAnimator mAnimator = null;

	public void startAnimation(Animation animation) {
        animation.setStartTime(Animation.START_ON_FIRST_FRAME);
        setAnimation(animation);
        invalidateParentCaches();
        invalidate(true);
    }
    
    /**
     * View子类可通过该方法获取ViewPropertyAnimator对象
     */
    public ViewPropertyAnimator animate() {
        if (mAnimator == null) {
            mAnimator = new ViewPropertyAnimator(this);
        }
        return mAnimator;
    }
	
	...
}

通过ViewPropertyAnimator实现动画效果非常简单,如布局文件中定义一个TextView,直接链式调用animate()方法即可,且链式调用后也可直接设置监听(其内部有两个设置监听的方法):

findViewById(R.id.tv).animate().x(50).scaleX(100).alphaBy(0.5f)
        .setListener(new Animator.AnimatorListener() {
         @Override
         public void onAnimationStart(@NonNull Animator animation) {
             
         }

         @Override
         public void onAnimationEnd(@NonNull Animator animation) {

         }

         @Override
         public void onAnimationCancel(@NonNull Animator animation) {

         }

         @Override
         public void onAnimationRepeat(@NonNull Animator animation) {

         }
     });

注意事项
1、animate():动画从调用View的animate()方法开始,它返回一个ViewPropertyAnimator对象,可通过这个对象的其它方法设置需要实现动画的属性。
2、自动开始:这种方式启动动画是隐式的,不需要显示调用start()方法。但实际上,这个动画会在下一次界面刷新的时候启动。ViewPropertyAnimator正是通过这个机制将所有动画结合在一起的,如果继续声明动画,它会继续将这些动画添加到将在下一帧开始的动画列表中,当声明完毕并结束对UI线程的控制后,事件队列机制开始起作用,动画就开始了。
3、流畅:ViewPropertyAnimator允许将多个方法串行起来进行链式调用,且每个方法都返回ViewPropertyAnimator实例。

7 性能对比

ViewPropertyAnimator并没有像ObjectAnimator一样使用反射或JNI技术,ViewPropertyAnimator会根据预设的每一个动画帧计算出对应的所有属性值,并设置给控件,然后再调用一次invalidate()方法进行重绘,从而解决了在使用ObjectAnimator时每个属性单独计算、单独重绘的问题。因此ViewPropertyAnimator相对于ObjectAnimator和组合动画,性能有所提升。

ObjectAnimator可以灵活、方便地为任何对象和属性做动画,但当需要同时为View的多个属性(SDK提供的、非自定义扩展的)做动画时,ViewPropertyAnimator会更方便。

使用ObjectAnimator并不需要担心性能问题,使用反射和JNI等技术所带来的开销相对于整个程序来说影响很小。且ViewPropertyAnimator的优势也不是性能提升,而是代码的可读性较好。

另外,实现一个组合动画(动画不一定是同时播放)有三种方式:
1、AnimatorSet

ObjectAnimator animX = ObjectAnimator.ofFloat(myView, "x", 50f);
ObjectAnimator animY = ObjectAnimator.ofFloat(myView, "y", 100f);
AnimatorSet animSet = new AnimatorSet();
animSet.playTogether(animX, animY);
animSet.start();

2、PropertyValuesHolder

PropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat("x", 50f);
PropertyValuesHolder pvhY = PropertyValuesHolder.ofFloat("y", 100f);
ObjectAnimator.ofPropertyValuesHolder(myView, pvhX, pvhY);

3、ViewPropertyAnimator
使用ViewPropertyAnimator的相关方法直接链式调用即可。

8 为ViewGroup内的组件添加动画

ViewAnimator、ObjectAnimator、AnimatorSet都只能针对一个控件做动画,如果想对ViewGroup内部控件做统一入场动画、出场动画,如给ListView的每个Item添加入场、出场动画是无法实现的。

为ViewGroup内的组件添加动画,Android提供了4种方法:
1、layoutAnimation标签与LayoutAnimationController
2、gridLayoutAnimation标签与GridLayoutAnimationController
3、android:animateLayoutChanges属性
4、LayoutTransition

前两种是在API 1时引入的,layoutAnimation标签是专门用于新建ListView时添加入场动画的,且动画可以自定义,但不能对之后再次添加的item实现入场动画。gridLayoutAnimation标签是专门用于新建GridView时添加入场动画的,其与layoutAnimation标签的性质和用法相似。这两种有bug不做演示。
后两种是在API 11时引入的,为了支持ViewGroup类控件在添加或移除其中的控件时自动添加动画,在XML中设置属性android:animateLayoutChanges="true/false"就能实现在添加或删除其中的控件时带有默认动画,所有ViewGroup子类均具有此属性,缺点是该动画不能自定义。
LayoutTransition可实现在ViewGroup动态添加或删除其中的控件时指定动画,且动画可以自定义。

8.1 android:animateLayoutChanges属性

开启该动画后是默认的哦,且不能自定义。

示例布局文件:

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

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <Button
            android:id="@+id/add_btn"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="添加控件" />

        <Button
            android:id="@+id/remove_btn"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="移除控件" />
    </LinearLayout>

    <LinearLayout
        android:id="@+id/linearlayoutContainer"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:animateLayoutChanges="true"
        android:orientation="vertical" />

</LinearLayout>  

示例代码:

public class AnimateLayoutChangesActivity extends Activity {
    private LinearLayout linearLayoutContainer;
    private int i = 0;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.animate_layout_changes_activity);
        linearLayoutContainer = (LinearLayout) findViewById(R.id.linearlayoutContainer);

        findViewById(R.id.add_btn).setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                addButtonView();
            }
        });

        findViewById(R.id.remove_btn).setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                removeButtonView();
            }
        });
    }

    private void addButtonView() {
        i++;
        Button button = new Button(this);
        button.setText("button " + i);
        button.setAllCaps(false);
        LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
                ViewGroup.LayoutParams.WRAP_CONTENT,
                ViewGroup.LayoutParams.WRAP_CONTENT
        );
        button.setLayoutParams(params);
        linearLayoutContainer.addView(button, 0);
    }

    private void removeButtonView() {
        if (i > 0) {
            linearLayoutContainer.removeViewAt(0);
        }
        i--;
    }

}

效果图:
在这里插入图片描述

8.2 LayoutTransition

该动画可以自定义。

ViewGroup中有一个setLayoutTransition(x)方法,源码:

package android.view;

@UiThread
public abstract class ViewGroup extends View implements ViewParent, ViewManager {
	...
	
	private LayoutTransition mTransition;
	
	public void setLayoutTransition(LayoutTransition transition) {
        if (mTransition != null) {
            LayoutTransition previousTransition = mTransition;
            previousTransition.cancel();
            previousTransition.removeTransitionListener(mLayoutTransitionListener);
        }
        mTransition = transition;
        if (mTransition != null) {
            mTransition.addTransitionListener(mLayoutTransitionListener);
        }
    }
    
    private LayoutTransition.TransitionListener mLayoutTransitionListener =
            new LayoutTransition.TransitionListener() {
        @Override
        public void startTransition(LayoutTransition transition, ViewGroup container,
                View view, int transitionType) {
            // We only care about disappearing items, since we need special logic to keep
            // those items visible after they've been 'removed'
            if (transitionType == LayoutTransition.DISAPPEARING) {
                startViewTransition(view);
            }
        }

        @Override
        public void endTransition(LayoutTransition transition, ViewGroup container,
                View view, int transitionType) {
            if (mLayoutCalledWhileSuppressed && !transition.isChangingLayout()) {
                requestLayout();
                mLayoutCalledWhileSuppressed = false;
            }
            if (transitionType == LayoutTransition.DISAPPEARING && mTransitioningViews != null) {
                endViewTransition(view);
            }
        }
    };

	...
}

而LayoutTransition是一个单独的类,源码:

package android.animation;

public class LayoutTransition {
	/**
     * 5个transitionType
     */
	public static final int CHANGE_APPEARING = 0;
	public static final int CHANGE_DISAPPEARING = 1;
	public static final int APPEARING = 2;
	public static final int DISAPPEARING = 3;
	public static final int CHANGING = 4;
	
	...
	
	private static long DEFAULT_DURATION = 300;
	
	public void setDuration(long duration) {
        mChangingAppearingDuration = duration;
        mChangingDisappearingDuration = duration;
        mChangingDuration = duration;
        mAppearingDuration = duration;
        mDisappearingDuration = duration;
    }
    
    public void setStartDelay(int transitionType, long delay) {
        ...
    }
    
    public void setDuration(int transitionType, long duration) {
        ...
    }
    
    public void setStagger(int transitionType, long duration) {
        ...
    }
    
    public void setInterpolator(int transitionType, TimeInterpolator interpolator) {
        ...
    }
    
    public void setAnimator(int transitionType, Animator animator) {
        ...
    }
    
    public void layoutChange(ViewGroup parent) {
        if (parent.getWindowVisibility() != View.VISIBLE) {
            return;
        }
        if ((mTransitionTypes & FLAG_CHANGING) == FLAG_CHANGING  && !isRunning()) {
            runChangeTransition(parent, null, CHANGING);
        }
    }
    
    public void addTransitionListener(TransitionListener listener) {
        if (mListeners == null) {
            mListeners = new ArrayList<TransitionListener>();
        }
        mListeners.add(listener);
    }
    
    public void removeTransitionListener(TransitionListener listener) {
        if (mListeners == null) {
            return;
        }
        mListeners.remove(listener);
    }
	
	public interface TransitionListener {
        public void startTransition(LayoutTransition transition, ViewGroup container,
                View view, int transitionType);

        public void endTransition(LayoutTransition transition, ViewGroup container,
                View view, int transitionType);
    }

	...
}

LayoutTransition的用法步骤:
1、创建LayoutTransition实例

LayoutTransition transition = new LayoutTransition();

2、创建并设置Animator动画

ObjectAnimator animIn = ObjectAnimator.ofFloat(null, "rotationY", 0f, 360f, 0f);
transition.setAnimator(LayoutTransition.APPEARING, animIn);

3、将LayoutTransition设置到ViewGroup

linearLayoutContainer.setLayoutTransition(transition);

布局文件同上,示例代码:

package com.example.myapplication;

import android.animation.Keyframe;
import android.animation.LayoutTransition;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.LinearLayout;


public class AnimateLayoutChangesActivity extends Activity {
    private LinearLayout linearLayoutContainer;
    private int i = 0;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.animate_layout_changes_activity);

        linearLayoutContainer = (LinearLayout) findViewById(R.id.linearlayoutContainer);
        LayoutTransition transition = new LayoutTransition();

        // 入场动画:view在这个容器中消失时触发的动画
        ObjectAnimator animIn = ObjectAnimator.ofFloat(null, "rotationY", 0f, 360f, 0f);
        transition.setAnimator(LayoutTransition.APPEARING, animIn);
        // 出场动画:view显示时的动画
        ObjectAnimator animOut = ObjectAnimator.ofFloat(null, "rotation", 0f, 90f, 0f);
        transition.setAnimator(LayoutTransition.DISAPPEARING, animOut);

        transition.addTransitionListener(new LayoutTransition.TransitionListener() {
            @Override
            public void startTransition(LayoutTransition transition, ViewGroup container, View view, int transitionType) {
                Log.d(
                        "rizhi",
                        "开始" + "transitionType: " + transitionType + ", count: " + container.getChildCount() + ", view: " + view.getClass().getName()
                );
            }

            @Override
            public void endTransition(LayoutTransition transition, ViewGroup container, View view, int transitionType) {
                Log.d(
                        "rizhi",
                        "结束" + "transitionType: " + transitionType + ",count: " + container.getChildCount() + ",view: " + view.getClass().getName()
                );
            }
        });

        linearLayoutContainer.setLayoutTransition(transition);

        findViewById(R.id.add_btn).setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                addButtonView();
            }
        });

        findViewById(R.id.remove_btn).setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                removeButtonView();
            }
        });
    }

    private void addButtonView() {
        i++;
        Button button = new Button(this);
        button.setText("button " + i);
        button.setAllCaps(false);
        LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
                ViewGroup.LayoutParams.WRAP_CONTENT,
                ViewGroup.LayoutParams.WRAP_CONTENT
        );
        button.setLayoutParams(params);
        linearLayoutContainer.addView(button, 0);
    }

    private void removeButtonView() {
        if (i > 0) {
            linearLayoutContainer.removeViewAt(0);
        }
        i--;
    }

}

效果图:
在这里插入图片描述
源码中标注的transitionType共有5个,上述示例测试的是LayoutTransition.APPEARING和LayoutTransition.DISAPPEARING,而LayoutTransition.CHANGE_APPEARING和LayoutTransition.CHANGE_DISAPPEARING的用法有很多注意事项,它两必须使用PropertyValuesHolder所构造的动画才有效果,各自使用实例如下,具体不再演示了。LayoutTransition.CHANGING也不演示。

测试LayoutTransition.CHANGE_APPEARING,其余代码相同:

PropertyValuesHolder pvhLeft = PropertyValuesHolder.ofInt("left", 0, 0);
PropertyValuesHolder pvhTop = PropertyValuesHolder.ofInt("top", 0, 0);
PropertyValuesHolder pvhScaleX = PropertyValuesHolder.ofFloat("scaleX", 1f, 0f, 1f);
Animator changeAppearAnimator = ObjectAnimator.ofPropertyValuesHolder(
        linearLayoutContainer,
        pvhLeft,
        pvhTop,
        pvhScaleX
);
transition.setAnimator(LayoutTransition.CHANGE_APPEARING, changeAppearAnimator);

测试LayoutTransition.CHANGE_DISAPPEARING,其余代码相同:

PropertyValuesHolder outLeft = PropertyValuesHolder.ofInt("left", 0, 0);
PropertyValuesHolder outTop = PropertyValuesHolder.ofInt("top", 0, 0);

Keyframe frame0 = Keyframe.ofFloat(0f, 0);
Keyframe frame1 = Keyframe.ofFloat(0.1f, -20f);
Keyframe frame2 = Keyframe.ofFloat(0.2f, 20f);
Keyframe frame3 = Keyframe.ofFloat(0.3f, -20f);
Keyframe frame4 = Keyframe.ofFloat(0.4f, 20f);
Keyframe frame5 = Keyframe.ofFloat(0.5f, -20f);
Keyframe frame6 = Keyframe.ofFloat(0.6f, 20f);
Keyframe frame7 = Keyframe.ofFloat(0.7f, -20f);
Keyframe frame8 = Keyframe.ofFloat(0.8f, 20f);
Keyframe frame9 = Keyframe.ofFloat(0.9f, -20f);
Keyframe frame10 = Keyframe.ofFloat(1, 0);
PropertyValuesHolder mPropertyValuesHolder = PropertyValuesHolder.ofKeyframe(
        "rotation",
        frame0,
        frame1,
        frame2,
        frame3,
        frame4,
        frame5,
        frame6,
        frame7,
        frame8,
        frame9,
        frame10
);

ObjectAnimator mObjectAnimatorChangeDisAppearing = ObjectAnimator.ofPropertyValuesHolder(
        this,
        outLeft,
        outTop,
        mPropertyValuesHolder
);
transition.setAnimator(LayoutTransition.CHANGE_DISAPPEARING, mObjectAnimatorChangeDisAppearing);

参考文献:
[1] UML中的类图及类图之间的关系
[2] 启舰.Android自定义控件开发入门与实战[M].北京:电子工业出版社,2018

微信公众号:TechU
在这里插入图片描述

  • 5
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值