本文内容:Android 动画
版权声明:本文为原创文章,未经允许不得转载
博客地址:http://blog.csdn.net/kevindgk
就实现原理来分析,Property Aniamtion和Tween Animation之间最大的区别就是前者在为对象添加动画效果时,其更改的是对象的实际属性,而后者改变的只是View的绘制效果,View的实际属性值是不发生改变的。
比如对一个Button实现左右移动的动画,若使用Property Animation实现的话,Button在移动的过程中,由于Property Animation改变的是Button的实际属性值,因此Button的事件响应焦点也随着移动,若点击移动中的Button,其可响应事件点击;而使用View Animation实现的话,点击移动中的Button,则无法响应事件点击,原因是Button控件本身的实际位置依然在初始位置,其事件响应焦点也依然在初始位置。
顺序播放一组预先定义好的图片,类似于电影播放,关键类是AnimationDrawable
名称 | 标签 | 对应的Java类 |
---|---|---|
帧动画 | AnimationDrawable |
1. 在XML中使用标签定义图片列表,作为一个AnimationDrawable
路径:res/drawable/anim_frame_example01.xml
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
android:oneshot="false">
<item android:drawable="@mipmap/signal01" android:duration="200"/>
<item android:drawable="@mipmap/signal02" android:duration="200"/>
<item android:drawable="@mipmap/signal03" android:duration="200"/>
<item android:drawable="@mipmap/signal04" android:duration="200"/>
<item android:drawable="@mipmap/signal05" android:duration="200"/>
<item android:drawable="@mipmap/signal06" android:duration="200"/>
animation-list>
<ImageView
android:id="@+id/iv_signal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingTop="30px"
android:background="@drawable/anim_frame_example01"/>
mAnim = (AnimationDrawable) mIvSignal.getBackground();
// 启动动画
mAnim.start();
// 关闭动画
mAnim.stop();
开发者只需指定动画开始、动画结束等关键帧,系统自动补全中间帧的动画,类似与Flash的补间动画。
名称 | 标签 | 对应Animation的子类 |
---|---|---|
平移动画 | TranslateAnimation | |
缩放动画 | ScaleAnimation | |
旋转动画 | RotateAnimation | |
透明度动画 | AlphaAnimation |
- AnimationSet:动画集合
路径:res/anim/xxx.xml
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="500"
android:fillAfter="true"
android:interpolator="@android:anim/accelerate_interpolator"
android:shareInterpolator="true">
<alpha
android:fromAlpha="0.0"
android:toAlpha="1.0" />
<scale
android:fromXScale="0.0"
android:fromYScale="0.0"
android:pivotX="0.5"
android:pivotY="0.5"
android:toXScale="2.0"
android:toYScale="2.0" />
<translate
android:fromXDelta="0"
android:fromYDelta="0"
android:toXDelta="100"
android:toYDelta="100" />
<rotate
android:fromDegrees="0"
android:pivotX="0.5"
android:pivotY="0.5"
android:toDegrees="360" />
set>
Animation animation = AnimationUtils.loadAnimation(this, R.anim.xxx);
btn.startAnimation(animation);
// 透明度动画
AlphaAnimation alpha = new AlphaAnimation(0, 1);
alpha.setDuration(500);
alpha.setFillAfter(true);
alpha.setInterpolator(new AccelerateDecelerateInterpolator());
// 设置动画监听器
alpha.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {}
@Override
public void onAnimationEnd(Animation animation) {}
@Override
public void onAnimationRepeat(Animation animation) {}
});
// 设置动画执行次数
alpha.setRepeatMode(Animation.INFINITE); // 无限次循环/反转
// 开始执行动画
btn.startAnimation(alpha);
// 取消执行动画
alpha.cancel();
// 如果取消动画,下次执行之前必须重置动画
alpha.reset();
继承Animation这个抽象类,并重写方法:
Activity在切换或者是退出的时候可以使用渐入,滑动,缩放等动态效果。
/*
该行代码必须在finish()或者startActivity()之后执行
- args1:进入的Activity的动画
- args2:退出的Activity的动画
如果不想要进入或者退出的Activity有动画,那么args为0即可
*/
overridePendingTransition(args1, args2);
详细的使用方式请见Library中动画章节。
名称 | 类 | 标签 |
---|---|---|
值动画 | ValueAnimator | < animator > |
对象动画 | ObjectAnimator | < objectAnimator > |
动画集合 | AnimatorSet | < set > |
定义目录:res/animator/,XML定义动画格式如下:
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:ordering="together|sequentially">
<animator
android:duration="int"
android:interpolator="@android:anim/accelerate_decelerate_interpolator"
android:repeatCount="int"
android:repeatMode="restart|reverse"
android:startOffset="int"
android:valueFrom="int|float|color"
android:valueTo="int|float|color"
android:valueType="intType|floatType|colorType|pathType" />
<objectAnimator
android:duration="int"
android:interpolator="@android:anim/accelerate_decelerate_interpolator"
android:propertyName="string"
android:repeatCount="int"
android:repeatMode="restart|reverse"
android:startOffset="int"
android:valueFrom="int|float|color"
android:valueTo="int|float|color"
android:valueType="intType|floatType|colorType|pathType" />
set>
使用XML定义的属性动画:
AnimatorSet set = (AnimatorSet) AnimatorInflater.loadAnimator(this, R.animator.test);
set.setTarget(view);
set.start();
工作流程:
创建ValueAnimator,并设置参数
设置完毕后,开启动画;
时间流逝的百分比=动画已经执行的时间/动画执行的总时间,Elapsed fraction,0=
TimeInterpolator
时间插值器,该类定义了动画的变化的速率。该接口中定义了一个方法
float getInterpolation(float input);
该方法输入时间流逝的百分比,返回插值因子,即当前属性值改变的百分比。
名称 | 名称 | 特点 |
---|---|---|
AccelerateDecelerateInterpolator | 加速减速插值器 | 开始和结束的时候减速,中间加速 |
AnticipateInterpolator | 在开始的时候先反向执行,然后再正向执行 | |
PathInterpolator | ||
BounceInterpolator | 回弹插值器 | 在结束的时候回弹一些 |
OvershootInterpolator | 在结束的时候超过结束值一些,然后回弹 | |
AnticipateOvershootInterpolator | 在开始的时候先反向执行,然后再正向执行;在结束的时候超过结束值一些,然后回弹 | |
LinearInterpolator | 线性插值器 | 线性变化,比率为常数 |
AccelerateInterpolator | 加速插值器 | 在开始的时候减速,然后加速执行 |
DecelerateInterpolator | 减速插值器 | 在开始的时候加速,然后减速执行 |
CycleInterpolator | 周期插值器 | 重复执行指定周期的动画,动画变化比率执行正弦函数(sin) |
线性插值器源码:
public class LinearInterpolator extends BaseInterpolator {
...
public float getInterpolation(float input) {
return input;
}
}
例如,在40ms内View的x属性从0到40的变换,如下图所示:
动画的默认刷新时间为10ms/帧,所以该动画有5帧,当第三帧的时候,t=20ms,此时时间流逝的百分比为P1=20ms/40ms=0.5,根据线性插值器,输入input=0.5,直接返回插值因子p2=0.5,即属性的变化比率应该为50%。然后再根据下面的估值器来计算属性。
继承BaseInterpolator即可,实现float getInterpolation(float input);方法,input即表示动画执行的时间的进度,即时间流逝的百分比,0~1之间。
根据插值因子(属性变化的比率)来计算真实的属性值。
TypeEvaluator 类型估值器
该方法中包含一个方法evaluate,该方法输入一个初始值、结束值、属性变化的比率,输出属性值。
public T evaluate(float fraction, T startValue, T endValue);
IntEvaluator 整型估值器
public class IntEvaluator implements TypeEvaluator<Integer> {
...
public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
int startInt = startValue;
return (int)(startInt + fraction * (endValue - startInt));
}
}
该估值器输入上文线性插值器举例的数据,startInt=0,endValue=40,fraction=0.5,那么返回的数值为
value = 0 + ( 40 - 0 ) * 0.5 = 20
实现TypeEvaluator接口,实现evaluate()方法即可。根据插值因子,初始值、结束值,计算出当前帧的value。
// 动画监听器:该listner在动画开始、结束、重复、取消的时候都会收到回调
valueAnimator.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) {
}
});
// 动画刷新监听器,在动画生命周期的每一帧,当value被计算出之后,就会收到回调,所以说如果是使用的ValueAnimator,则必须实现此监听器,来实现动画。
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
// 在收到回调的时候,可以通过getAnimatedValue()获取到当前帧的value值,并将它赋值给view
Integer animatedValue = (Integer) animation.getAnimatedValue();
mBtn02.setBackgroundColor(animatedValue);
}
});
该类提供了一个简单的时间选择器来执行动画,能够计算出动画执行的数值,并将它们设置给目标对象。
// 使用ValueAnimator.ofXxx()方法创建ValueAnimator对象
ValueAnimator valueAnimator = ValueAnimator.ofArgb(0xFFFF0000, 0xFF00FF00);
// 设置动画参数
valueAnimator.setDuration(3000);
valueAnimator.setRepeatCount(ValueAnimator.INFINITE);
valueAnimator.setRepeatMode(ValueAnimator.REVERSE);
// 设置插值器,如果没有则使用默认值
valueAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
// 设置估值器,如果没有则使用默认值
valueAnimator.setEvaluator(new IntEvaluator());
// 设置动画监听
valueAnimator.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) {
}
});
// 设置动画刷新监听器
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
// 在没一帧的回调用,将value值赋值给对象
Integer animatedValue = (Integer) animation.getAnimatedValue();
mBtn02.setBackgroundColor(animatedValue);
}
});
// 开启动画
valueAnimator.start();
/**
* 从左向右移动
*/
private void leftToRight() {
// 获取屏幕属性
DisplayMetrics metrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(metrics);
// 构建ValueAnimator
ValueAnimator valueAnimator = ValueAnimator.ofInt(0, metrics.widthPixels - iv.getWidth());
valueAnimator.setDuration(2000);
valueAnimator.setRepeatCount(1);
valueAnimator.setRepeatMode(ValueAnimator.REVERSE);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int widthUpdate = (int) animation.getAnimatedValue();
LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) iv.getLayoutParams();
layoutParams.leftMargin = widthUpdate;
iv.setLayoutParams(layoutParams);
}
});
valueAnimator.start();
}
/**
* 缩放
* - 在缩放的时候,实际上同时动画的属性有X和Y,
* 如果只有一个属性或者两个属性变化相同的时候,仍然可以使用ValueAnimator.ofFloat()来实现,
* 但是,如果想要多个属性同时动画,并且每个属性变化并不相同,那么就需要使用PropertyValuesHolder
* 来实现动画了。
*/
private void scaleChange() {
// 为每个属性值设置初始值、结束值、属性名称(随意写,只要有含义即可)
PropertyValuesHolder x = PropertyValuesHolder.ofFloat("scaleX", 1.0f, 0f);
PropertyValuesHolder y = PropertyValuesHolder.ofFloat("scaleY", 0.5f, 0f);
// 使用ofPropertyValuesHolder()创建构造器
ValueAnimator valueAnimator = ValueAnimator.ofPropertyValuesHolder(x, y);
valueAnimator.setDuration(2000);
valueAnimator.setRepeatCount(1);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
// 通过定义的属性名,取出来各自属性计算的数值
float scaleX = (float) animation.getAnimatedValue("scaleX");
float scaleY = (float) animation.getAnimatedValue("scaleY");
iv.setScaleX(scaleX);
iv.setScaleY(scaleY);
}
});
valueAnimator.start();
}
通过对ValueAnimator的简单使用就可以看出,只要经过以下步骤就可以轻松的对任何对象的属性实现动画效果:
整体思路还是比较清晰的,而且使用比较简单,但是在Android 3.0中,google给View增加了一些新的属性以及相应的getter、setter方法:
属性 | 含义 |
---|---|
x,y | 坐标,绝对值 |
translationX、translationY | 坐标变化量,相对于容器的左上角 |
rotation、rotationX、rotationY | 旋转角度 |
scaleX、scaleY | 缩放比例 |
pivotX、pivotY | 缩放和旋转的中心点 |
alpha | 透明度,0-透明;1-不透明 |
如果想要给View的一个属性实现动画,并且该属性具有setter/getter方法,那么就可以直接使用下文的ObjectAnimator来实现。
对View的具有getter\setter方法的属性xxx实现动画,可以使用ObjectAnimator。
前提条件:
/**
* 使用ObjectAnimator实现平移动画
*/
private void translationByObjectAnimator() {
// 获取屏幕属性
DisplayMetrics metrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(metrics);
// 宽度变化:从左到右
ObjectAnimator objectAnimatorX = ObjectAnimator.ofFloat(iv, "translationX", 0, metrics.widthPixels - iv.getWidth());
objectAnimatorX.setRepeatCount(1);
objectAnimatorX.setRepeatMode(ValueAnimator.REVERSE);
// 高度变化:从上到下,屏幕的高度-顶部标题栏的高度-手机状态栏的高度
ObjectAnimator objectAnimatorY = ObjectAnimator.ofFloat(iv, "translationY", 0,
metrics.heightPixels - iv.getHeight() - AutoUtils.getPercentHeightSize(120) - 50);
objectAnimatorY.setRepeatCount(1);
objectAnimatorY.setRepeatMode(ValueAnimator.REVERSE);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playTogether(objectAnimatorX, objectAnimatorY);
animatorSet.setDuration(2000);
animatorSet.setInterpolator(new AccelerateDecelerateInterpolator());
animatorSet.start();
}
/**
* 使用ObjectAnimator实现旋转动画
*/
private void rotationByObjectAnimator() {
ObjectAnimator rotation = ObjectAnimator.ofFloat(iv, "rotation", 0f, 360f);
rotation.setInterpolator(new LinearInterpolator()); // 线性插值器,匀速旋转
rotation.setDuration(1000);
rotation.setRepeatCount(ValueAnimator.INFINITE);
rotation.setRepeatMode(ValueAnimator.RESTART);
rotation.start();
}
Android动画集合分为两种,一种是AnimationSet表示补间动画集合,还有一种就是AnimatorSet表示属性动画集合。注意,不能给动画集合设置动画重复的次数和方式,需要给每个动画分别设置。
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playTogether(objectAnimatorX, objectAnimatorY);
animatorSet.setDuration(2000);
animatorSet.setInterpolator(new AccelerateDecelerateInterpolator());
animatorSet.start();
对于满足条件的,可以直接使用ObjectAnimator做属性动画;
对于不满足条件的,可以采用以下方法:
给对象的属性添加getter/setter方法,通常自定义View可以使用;
直接使用ValueAnimator,监听动画过程,手动的将value值设置给View对象的属性;
用一个类来包装原始对象,间接的为其提供getter和setter方法,方法如下
/**
通过包装对象来实现缩放效果
*/
private void scaleByViewWrapper() {
LogUtil.i(tag, “scaleByViewWrapper”);
ViewWrapper viewWrapper = new ViewWrapper(iv);
ObjectAnimator objectAnimator = ObjectAnimator.ofInt(viewWrapper, “width”, 0);
objectAnimator.setDuration(2000);
objectAnimator.setRepeatCount(1);
objectAnimator.setRepeatMode(ValueAnimator.REVERSE);
objectAnimator.start();
}
/**
public ViewWrapper(View view) {
this.view = view;
}
public int getWidth() {
return view.getWidth();
}
public void setWidth(int width) {
// 获取布局参数
view.getLayoutParams().width = width;
// 当布局参数变化的时候,调用该方法进行刷新界面
view.requestLayout();
}
}
使用ViewWrapper,可以很方便的实现复用,棒棒哒~
View的setWidth()方法并不起作用,所以如果想要动态修改View的宽度和高度,可以使用:
// 获取布局参数
view.getLayoutParams().width = width;
// 当布局参数变化的时候,调用该方法进行刷新界面
view.requestLayout();
并且要求View在XML中的布局参数的宽度和高度为wrap_content,如果还想要显示宽度和高度,可以配合maxWidth和maxHeight使用。