在App中合理地使用动画能够获得友好愉悦的用户体验,Android中的动画有View动画、属性动画、帧动画、布局动画、转场动画等,在5.x以后有又新增了矢量动画,这些动画在平常开发中使用较为普遍,所以有必要做一次完整的总结。
一、View动画
View动画定义了渐变Alpha、旋转Rotate、缩放Scale、平移Translate四种基本动画,并且通过这四种基本动画的组合使用,可以实现多种交互效果。
View动画使用非常简单,不仅可以通过XML文件来定义动画,同样可以通过Java代码来实现动画过程。
1.Xml文件定义View动画
通过xml来定义View动画涉及到一些公有的属性(在AndroidStudio上不能提示):
android:duration 动画持续时间
android:fillAfter 为true动画结束时,View将保持动画结束时的状态
android:fillBefore 为true动画结束时,View将还原到开始开始时的状态
android:repeatCount 动画重复执行的次数
android:repeatMode 动画重复模式 ,重复播放时restart重头开始,reverse重复播放时倒叙回放,该属性需要和android:repeatCount一起使用
android:interpolator 插值器,相当于变速器,改变动画的不同阶段的执行速度复制代码
这些属性是从Animation中继承下来的,在alpha、rotate、scale、translate标签中都可以直接使用。
利用xml文件定义View动画需要在工程的res目录下创建anim文件夹,所有的xml定义的View动画都要放在anim目录下。
渐变view_anim_alpha.xml:
<?xml version="1.0" encoding="utf-8"?><alphaxmlns:android="http://schemas.android.com/apk/res/android"android:duration="2000"android:fromAlpha="1.0"android:toAlpha="0"></alpha>复制代码
旋转view_anim_rotate.xml:
<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="2000"
android:fillAfter="true"
android:fromDegrees="0"
android:pivotX="50%"
android:pivotY="50%"
android:toDegrees="360">
</rotate>复制代码
缩放view_anim_scale.xml:
<?xml version="1.0" encoding="utf-8"?>
<scale xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="2000"
android:fromXScale="1.0"
android:fromYScale="1.0"
android:pivotX="50%"
android:pivotY="50%"
android:toXScale="0.5"
android:toYScale="0.5">
</scale>复制代码
平移view_anim_translate.xml:
<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="2000"
android:fromXDelta="0"
android:fromYDelta="0"
android:toXDelta="100%"
android:toYDelta="100%">
</translate>复制代码
rotate、scale动画的android:pivotX和android:pivotY属性、translate动画的android:toXDelta和android:toYDelta属性的取值都可以是都可以数值、百分数、百分数p,比如:50、50%、50%p,他们取值的代表的意义各不相同:
50表示以View左上角为原点沿坐标轴正方向(x轴向右,y轴向下)偏移50px的位置;
50%表示以View左上角为原点沿坐标轴正方向(x轴向右,y轴向下)偏移View宽度或高度的50%处的位置;
50%p表示以View左上角为原点沿坐标轴正方向(x轴向右,y轴向下)偏移父控件宽度或高度的50%处的位置(p表示相对于ParentView的位置)。
"50"位置点
"50%"位置点
"50%p"位置点
通过定义xml动画资源文件,在Activity中调用:
public void clickToAlpha(View view) {
Animation alphaAnim = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.view_anim_alpha);
mTargetView.startAnimation(alphaAnim);
}
public void clickToRotate(View view) {
Animation rotateAnim = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.view_anim_rotate);
mTargetView.startAnimation(rotateAnim);
}
public void clickToScale(View view) {
Animation scaleAnim = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.view_anim_scale);
mTargetView.startAnimation(scaleAnim);
}
public void clickToTranslate(View view) {
Animation translateAnim = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.view_anim_translate);
mTargetView.startAnimation(translateAnim);
}
public void clickToSet(View view) {
Animation setAnim = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.view_anim_set);
mTargetView.startAnimation(setAnim);
}复制代码
2.Java代码实现View动画
在平常的业务逻辑中也可以直接用Java代码来实现Veiw动画,Android系统给我们提供了AlphaAnimation、RotateAnimation、ScaleAnimation、TranslateAnimation四个动画类分别来实现View的渐变、旋转、缩放、平移动画。
渐变:
public void clickToAlpha(View view) {
AlphaAnimation alphaAnimation = new AlphaAnimation(1, 0);
alphaAnimation.setDuration(2000);
mTargetView.startAnimation(alphaAnimation);
}复制代码
旋转:
public void clickToRotate(View view) {
RotateAnimation rotateAnimation = new RotateAnimation(
0, 360,
Animation.RELATIVE_TO_SELF, 0.5f,
Animation.RELATIVE_TO_SELF, 0.5f);
rotateAnimation.setDuration(2000);
mTargetView.startAnimation(rotateAnimation);
}复制代码
缩放:
public void clickToScale(View view) {
ScaleAnimation scaleAnimation = new ScaleAnimation(
1, 0.5f,
1, 0.5f,
Animation.RELATIVE_TO_SELF, 0.5f,
Animation.RELATIVE_TO_SELF, 0.5f);
scaleAnimation.setDuration(2000);
mTargetView.startAnimation(scaleAnimation);
}复制代码
平移:
public void clickToTranslate(View view) {
TranslateAnimation translateAnimation = new TranslateAnimation(
Animation.RELATIVE_TO_SELF, 0,
Animation.RELATIVE_TO_SELF, 1,
Animation.RELATIVE_TO_SELF, 0,
Animation.RELATIVE_TO_SELF, 1);
translateAnimation.setDuration(2000);
mTargetView.startAnimation(translateAnimation);
}复制代码
组合:
public void clickToSet(View view) {
AlphaAnimation alphaAnimation = new AlphaAnimation(1, 0);
alphaAnimation.setDuration(2000);
RotateAnimation rotateAnimation = new RotateAnimation(
0, 360,
Animation.RELATIVE_TO_SELF, 0.5f,
Animation.RELATIVE_TO_SELF, 0.5f);
rotateAnimation.setDuration(2000);
ScaleAnimation scaleAnimation = new ScaleAnimation(
1, 0.5f,
1, 0.5f,
Animation.RELATIVE_TO_SELF, 0.5f,
Animation.RELATIVE_TO_SELF, 0.5f);
scaleAnimation.setDuration(2000);
TranslateAnimation translateAnimation = new TranslateAnimation(
Animation.RELATIVE_TO_SELF, 0,
Animation.RELATIVE_TO_SELF, 1,
Animation.RELATIVE_TO_SELF, 0,
Animation.RELATIVE_TO_SELF, 1);
translateAnimation.setDuration(2000);
AnimationSet animationSet = new AnimationSet(true);
animationSet.addAnimation(alphaAnimation);
animationSet.addAnimation(rotateAnimation);
animationSet.addAnimation(scaleAnimation);
animationSet.addAnimation(translateAnimation);
mTargetView.startAnimation(animationSet);
}复制代码
View动画效果
View动画可以设置一个动画执行的监听器:
animation.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
// 动画开始
}
@Override
public void onAnimationEnd(Animation animation) {
// 动画结束
}
@Override
public void onAnimationRepeat(Animation animation) {
//动画重复
}
});复制代码
通过设置监听器可以在动画执行的开始、结束、重复时做一些其他的业务逻辑。
二、属性动画
所谓属性动画,就是改变对象Object的属性来实现动画过程。属性动画是对View的动画的扩展,通过它可以实现更多漂亮的动画效果。同时属性动画的作用对象不仅仅是View,任何对象都可以。
属性动画的作用效果就是:在一个指定的时间段内将对象的一个属性的属性值动态地变化到另一个属性值。
1.ObjectAnimator
ObjectAnimator是最常用的属性动画执行类。
privatevoidstartJavaPropertyAnimator() {
ObjectAnimator
.ofFloat(mImageView, "rotationY", 0f, 360f)
.setDuration(2000)
.start();
}复制代码
上面的代码就是通过ObjectAnimator在2000ms内将mImageView的rotationY属性的属性值从0f变化的360f。
ObjectAnimator实现属性动画
ObjectAnimtor可以用ofInt、ofFloat、ofObject等静态方法,传入动画作用的目标Object、属性字段、属性开始值、属性中间值、属性结束值等参数来构造动画对象。
在动画更新的过程中,通过不断去调用对象属性的setter方法改变属性值,不断重绘实现动画过程。如果没有给定动画开始属性值,那么系统会通过反射去获取Object对象的初始值作为动画的开始值。
属性动画也同样可以通过xml文件来定义,同样在工程的res目录下创建animator文件夹,xml文件定义的objectAnimator动画要放在该文件夹下。
property_animator.xml:
<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="2000"
android:propertyName="rotationY"
android:valueFrom="0"
android:valueTo="360"
android:valueType="floatType">
</objectAnimator>复制代码
Java代码调用:
private void startXmlPropertyAnimator() {
Animator animator = AnimatorInflater.loadAnimator(getApplicationContext(),
R.animator.property_animator);
animator.setTarget(mImageView);
animator.start();
}复制代码
最终效果如上图。
属性动画也同样可以组合使用,通过AnimatorSet类和xml文件的set标签都可以同时改变对象的多个属性,实现更加丰富的动画效果。
通过AnimatorSet创建动画集:
private void startJavaPropertyAnimatorSet() {
Animator scaleXAnimator = ObjectAnimator.ofFloat(mImageView, "scaleX", 1, 0.5f);
scaleXAnimator.setDuration(2000);
Animator scaleYAnimator = ObjectAnimator.ofFloat(mImageView, "scaleY", 1, 0.5f);
scaleYAnimator.setDuration(2000);
Animator rotationXAnimator = ObjectAnimator.ofFloat(mImageView, "rotationX", 0, 360);
rotationXAnimator.setDuration(2000);
Animator rotationYAnimator = ObjectAnimator.ofFloat(mImageView, "rotationY", 0, 360);
rotationYAnimator.setDuration(2000);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.play(scaleXAnimator)
.with(scaleYAnimator)
.before(rotationXAnimator)
.after(rotationYAnimator);
animatorSet.start();
}复制代码
AnimatorSet通过before、with、after三个方法可以组合多个属性动画,with表示与给定动画同时执行,before在给定动画执行之前执行,after表示在给定动画执行之后执行。
AnimatorSet构建属性动画集
通过xml文件定义属性动画集:
property_animator_set.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<objectAnimator
android:duration="2000"
android:propertyName="scaleX"
android:valueFrom="1"
android:valueTo="0.5"
android:valueType="floatType"/>
<objectAnimator
android:duration="2000"
android:propertyName="scaleY"
android:valueFrom="1"
android:valueTo="0.5"
android:valueType="floatType"/>
<objectAnimator
android:duration="2000"
android:propertyName="alpha"
android:valueFrom="1"
android:valueTo="0.5"
android:valueType="floatType"/>
</set>复制代码
在Java代码中调用属性动画集:
private void startxmlPropertyAnimatorSet() {
Animator animator = AnimatorInflater.loadAnimator(getApplicationContext(),
R.animator.property_animator_set);
animator.setTarget(mImageView);
animator.start();
}复制代码
xml定义属性动画集
同样,属性动画也可以添加动画执行监听器:
animator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
// 动画开始
}
@Override
public void onAnimationEnd(Animator animation) {
// 动画结束
}
@Override
public void onAnimationCancel(Animator animation) {
// 动画取消
}
@Override
public void onAnimationRepeat(Animator animation) {
// 动画重复
}
});复制代码
在监听到属性动画开始、结束、取消、重复时可以去做一些其他的逻辑业务。
2.ValueAnimator
ValueAnimator是ObjectAnimator的父类,它继承自Animator。ValueAnimaotor同样提供了ofInt、ofFloat、ofObject等静态方法,传入的参数是动画过程的开始值、中间值、结束值来构造动画对象。可以将ValueAnimator看着一个值变化器,即在给定的时间内将一个目标值从给定的开始值变化到给定的结束值。在使用ValueAnimator时通常需要添加一个动画更新的监听器,在监听器中能够获取到执行过程中的每一个动画值。
privatevoidstartValueAnimator() {
ValueAnimatorvalueAnimator= ValueAnimator.ofFloat(0, 1);
valueAnimator.setDuration(300);
valueAnimator.start();
valueAnimator.addUpdateListener(newValueAnimator.AnimatorUpdateListener() {
@OverridepublicvoidonAnimationUpdate(ValueAnimator animation) {
// 动画更新过程中的动画值,可以根据动画值的变化来关联对象的属性,实现属性动画floatvalue= (float) animation.getAnimatedValue();
Log.d("ValueAnimator", "动画值:" + value);
}
});
}复制代码
在300ms内将数值0变化到1的动画值的变化log:
02-25 23:16:57.586 D/ValueAnimator:动画值:0.002-25 23:16:57.596 D/ValueAnimator:动画值:0.00790217502-25 23:16:57.616 D/ValueAnimator:动画值:0.02955961202-25 23:16:57.636 D/ValueAnimator:动画值:0.06698727602-25 23:16:57.646 D/ValueAnimator:动画值:0.11810201402-25 23:16:57.666 D/ValueAnimator:动画值:0.1812879702-25 23:16:57.686 D/ValueAnimator:动画值:0.254548202-25 23:16:57.706 D/ValueAnimator:动画值:0.3306310202-25 23:16:57.716 D/ValueAnimator:动画值:0.416615702-25 23:16:57.736 D/ValueAnimator:动画值:0.505235902-25 23:16:57.746 D/ValueAnimator:动画值:0.593690602-25 23:16:57.766 D/ValueAnimator:动画值:0.6791839602-25 23:16:57.786 D/ValueAnimator:动画值:0.754520802-25 23:16:57.796 D/ValueAnimator:动画值:0.8267103402-25 23:16:57.826 D/ValueAnimator:动画值:0.8885729302-25 23:16:57.836 D/ValueAnimator:动画值:0.9381532702-25 23:16:57.856 D/ValueAnimator:动画值:0.972188202-25 23:16:57.876 D/ValueAnimator:动画值:0.9938441502-25 23:16:57.886 D/ValueAnimator:动画值:1.0复制代码
ValueAnimator的使用一般会结合更新监听器AnimatorUpdateListener,大多数时候是在自定义控件时使用。
下面是用ValueAnimator自定义控件实现动画打开关闭效果。
expanded_veiw.xml:
<?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:background="@android:color/white"
android:divider="@drawable/divider"
android:orientation="vertical"
android:showDividers="middle">
<LinearLayout
android:id="@+id/ll_expanded_question"
android:layout_width="match_parent"
android:layout_height="48dp"
android:gravity="center_vertical">
<TextView
android:id="@+id/tv_expanded_question"
android:layout_width="0dp"
android:layout_height="48dp"
android:layout_weight="1"
android:gravity="center_vertical"
android:padding="8dp"
android:text="如何把一本很难的书看懂?"
android:textColor="#999999"
android:textSize="16sp"/>
<ImageView
android:id="@+id/iv_expanded_indicator"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_marginRight="16dp"
android:src="@drawable/img_up"/>
</LinearLayout>
<TextView
android:id="@+id/tv_expanded_answer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="8dp"
android:text="多读几遍。真的特别有用。至少看三遍。从开头看,看到中间,重头再来,再看得多一点,在从新开始,建议看到快结束时再从新开始。"
android:textColor="#999999"
android:textSize="16sp"/>
</LinearLayout>复制代码
publicclassExpandedViewextendsFrameLayout {
private TextView mTvAnswer;
privateboolean isClosed;
private ImageView mIvIndicator;
publicExpandedView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
privatevoidinit(Context context) {
Viewview= LayoutInflater.from(context).inflate(R.layout.expanded_view, this, true);
LinearLayoutllQuestion= (LinearLayout) view.findViewById(R.id.ll_expanded_question);
llQuestion.setOnClickListener(newOnClickListener() {
@OverridepublicvoidonClick(View v) {
anim();
}
});
mTvAnswer = (TextView) view.findViewById(R.id.tv_expanded_answer);
mIvIndicator = (ImageView) view.findViewById(R.id.iv_expanded_indicator);
}
privatevoidanim() {
// 指示器旋转ValueAnimatorvalueAnimator1= isClosed
? ValueAnimator.ofFloat(180, 0)
: ValueAnimator.ofFloat(0, 180);
valueAnimator1.setDuration(500);
valueAnimator1.addUpdateListener(newValueAnimator.AnimatorUpdateListener() {
@OverridepublicvoidonAnimationUpdate(ValueAnimator animation) {
floatvalue= (float) animation.getAnimatedValue();
mIvIndicator.setRotation(value);
}
});
valueAnimator1.start();
// 打开开关闭操作finalintanswerHeight= mTvAnswer.getMeasuredHeight();
ValueAnimatorvalueAnimator2= isClosed
? ValueAnimator.ofInt(-answerHeight, 0)
: ValueAnimator.ofInt(0, -answerHeight);
valueAnimator2.setDuration(500);
finalMarginLayoutParamsparams= (MarginLayoutParams) mTvAnswer.getLayoutParams();
valueAnimator2.addUpdateListener(newValueAnimator.AnimatorUpdateListener() {
@OverridepublicvoidonAnimationUpdate(ValueAnimator animation) {
intvalue= (int) animation.getAnimatedValue();
params.bottomMargin = value;
mTvAnswer.setLayoutParams(params);
}
});
valueAnimator2.addListener(newAnimator.AnimatorListener() {
@OverridepublicvoidonAnimationStart(Animator animation) {
}
@OverridepublicvoidonAnimationEnd(Animator animation) {
isClosed = !isClosed;
}
@OverridepublicvoidonAnimationCancel(Animator animation) {
}
@OverridepublicvoidonAnimationRepeat(Animator animation) {
}
});
valueAnimator2.start();
}
}复制代码
ValueAnimator自定义控件效果图
3.TypeEvaluator
ObjectAnimator和ValueAnimator都有ofObject方法,传入的都有一个TypeEvaluator类型的参数。TypeEvaluator是一个接口,里面也只有一个抽象方法:
public T evaluate(float fraction, T startValue, T endValue);复制代码
再看ofInt方法中没有传入该参数,但实际上调用ofInt方法时,系统已经有实现了TypeEvaluator接口的IntEvaluator,它的源码也非常简单:
public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
int startInt = startValue;
return (int)(startInt + fraction * (endValue - startInt));
}复制代码
fraction范围为0到1,表示动画执行过程中已完成程度。
泛型T即为动画执行的属性类型。
所以我们要用属性动画来执行复杂对象的动画过程,就需要自定义TypeEvaluator,实现动画逻辑。
先来定义一个对象
publicclassCircle {
privateint raduis; // 半径privateint color; // 颜色privateint elevation; // 高度publicCircle(int raduis, int color, int elevation) {
this.raduis = raduis;
this.color = color;
this.elevation = elevation;
}
publicintgetRaduis() {
return raduis;
}
publicvoidsetRaduis(int raduis) {
this.raduis = raduis;
}
publicintgetColor() {
return color;
}
publicvoidsetColor(int color) {
this.color = color;
}
publicintgetElevation() {
return elevation;
}
publicvoidsetElevation(int elevation) {
this.elevation = elevation;
}
}复制代码
自定义控件CircleView,将Circle作为它的一个属性:
public class CircleView extends View {
private Circle circle;
private Paint mPaint;
public CircleView(Context context, AttributeSet attrs) {
super(context, attrs);
circle = new Circle(168, Color.RED, 0);
mPaint = new Paint();
mPaint.setAntiAlias(true);
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
setElevation(circle.getElevation());
mPaint.setColor(circle.getColor());
canvas.drawCircle(getMeasuredHeight() / 2, getMeasuredHeight() / 2, circle.getRaduis(), mPaint);
}
public void setCircle(Circle circle) {
this.circle = circle;
postInvalidate();
}
public Circle getCircle() {
return circle;
}
}复制代码
ObjectAnimator使用:
private void start1() {
Circle startCircle = new Circle(168, Color.RED, 0);
Circle middleCircle = new Circle(300, Color.GREEN, 15);
Circle endCircle = new Circle(450, Color.BLUE, 30);
ObjectAnimator.ofObject(mCircleView, "circle", new CircleEvaluator(), startCircle, middleCircle, endCircle)
.setDuration(5000)
.start();
}复制代码
ValueAnimator使用:
private void start2() {
Circle startCircle = new Circle(168, Color.RED, 0);
Circle middleCircle = new Circle(300, Color.GREEN, 15);
Circle endCircle = new Circle(450, Color.BLUE, 30);
ValueAnimator valueAnimator = ValueAnimator.ofObject(new CircleEvaluator(), startCircle, middleCircle, endCircle);
valueAnimator.setDuration(5000);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
Circle circle = (Circle) animation.getAnimatedValue();
mCircleView.setCircle(circle);
}
});
valueAnimator.start();
}复制代码
自定义TypeEvaluator效果图
需要注意的是,系统调用获取控件setter、getter方法是通过反射获取的,属性的名称必须和getter、setter方法名称后面的字符串一致,比如上面的getter、setter方法分别为getCircle、setCircle,那么属性名字就必须为circle。
三、帧动画
帧动画需要开发者制定好动画每一帧,系统一帧一帧的播放图片。
private void start1() {
AnimationDrawable ad = new AnimationDrawable();
for (int i = 0; i < 7; i++) {
Drawable drawable = getResources().getDrawable(getResources().getIdentifier("ic_fingerprint_" + i, "drawable", getPackageName()));
ad.addFrame(drawable, 100);
}
ad.setOneShot(false);
mImageView.setImageDrawable(ad);
ad.start();
}复制代码
帧动画同样也可以在xml文件中配置,直接在工程drawable目录新建animation-list标签:
<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
android:oneshot="false">
<item android:drawable="@drawable/ic_fingerprint_0" android:duration="100"/>
<item android:drawable="@drawable/ic_fingerprint_1" android:duration="100"/>
<item android:drawable="@drawable/ic_fingerprint_2" android:duration="100"/>
<item android:drawable="@drawable/ic_fingerprint_3" android:duration="100"/>
<item android:drawable="@drawable/ic_fingerprint_4" android:duration="100"/>
<item android:drawable="@drawable/ic_fingerprint_5" android:duration="100"/>
<item android:drawable="@drawable/ic_fingerprint_6" android:duration="100"/>
</animation-list>复制代码
private void start2() {
mImageView.setImageResource(R.drawable.frame_anim);
AnimationDrawable animationDrawable = (AnimationDrawable) mImageView.getDrawable();
animationDrawable.start();
}复制代码
其中android:onshot属性和setOneShot方法表示是否只执行一次。
AnimationDrawable实际是上是一个Drawable动画,动画执行的过程总会给你不断重绘Drawable的每一帧图像,实现动态播放效果。
帧动画效果图