动画实际上就是在指定的时间段内持续修改某个属性的值, 使得该值在指定取值范围内平滑过渡。
动画可以分为三大类, 帧动画(Frame Animation)、补间动画(Tweened Animation)、属性动画(Android 3.0 之后增加的属性动画)
帧动画
帧动画是最容易实现的一种动画,这种动画更多的依赖于完善的UI资源, 原理是一定的时间段内切换多张有细微差异的图片从而在视觉上产生一种动画的效果,帧动画可以定义在xml 文件中,也可以使用代码实现
xml 文件中的定义如下:
<?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/loading1"
android:duration="50" />
<item
android:drawable="@drawable/loading2"
android:duration="50" />
<item
android:drawable="@drawable/loading3"
android:duration="50" />
<item
android:drawable="@drawable/loading4"
android:duration="50" />
<item
android:drawable="@drawable/loading5"
android:duration="50" />
</animation-list>
oneshot 为true 次动画只会执行一次, 为false的话则一直循环, duration 代表此帧持续的时间 ,单位为毫秒
View 中的设置
<ImageView
android:id="@+id/image_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="100dp"
android:layout_gravity="center_horizontal"
android:background="@drawable/swipe_anim_loading_view"/>
此时动画不会在View 显示的时候启动,我们需要Java 代码启动该动画,代码如下
@Override
protected void onResume() {
super.onResume();
ImageView mImageView = findViewById(R.id.image_view);
((AnimationDrawable)mImageView.getBackground()).start();
}
当然我们也可以通过Java 代码来构建动画代码如下:
@Override
protected void onResume() {
super.onResume();
ImageView mImageView = findViewById(R.id.image_view);
AnimationDrawable animationDrawable = new AnimationDrawable();
for (int i = 0; i < 10; i++) {
int id = getResources().getIdentifier("loading" + i, "drawble", getPackageName());
Drawable drawable = getResources().getDrawable(id);
animationDrawable.addFrame(drawable, 50);
}
animationDrawable.setOneShot(false);
mImageView.setBackground(animationDrawable);
animationDrawable.start();
}
当然推荐的是xml 方式, 因为它将动画的代码从复杂的Java代码逻辑中隔离, 更易于维护。
补间动画
补间动画是操作某个控件让其展现出旋转、渐变、移动、缩放的一种转换过程,我们可以以xml的形式定义动画,也可以在Java 代码中实现。补间动画又可以分为四种形式,分别是 alpha(淡入淡出),translate(位移),scale(缩放大小),rotate(旋转)。
XML实现
首先,在res/anim/ 文件夹下定义如下的动画实现方式
alpha_anim.xml 动画实现
<?xml version="1.0" encoding="utf-8"?>
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="5000"
android:fromAlpha="1.0"
android:interpolator="@android:anim/accelerate_decelerate_interpolator"
android:toAlpha="0.0" />
scale.xml 动画实现
<?xml version="1.0" encoding="utf-8"?>
<scale xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="1000"
android:fromXScale="0.0"
android:fromYScale="0.0"
android:pivotX="50%"
android:pivotY="50%"
android:toXScale="1.0"
android:toYScale="1.0"/>
Java 代码中启动动画
ImageView mImageView = findViewById(R.id.image_view);
Animation animation = AnimationUtils.loadAnimation(MainActivity.this, R.alpha.rotate_anim);
mImageView.startAnimation(animation);
可以使用set 标签将多个动画组合(代码源自Android SDK API)
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@[package:]anim/interpolator_resource"
android:shareInterpolator=["true" | "false"] >
<alpha
android:fromAlpha="float"
android:toAlpha="float" />
<scale
android:fromXScale="float"
android:toXScale="float"
android:fromYScale="float"
android:toYScale="float"
android:pivotX="float"
android:pivotY="float" />
<translate
android:fromXDelta="float"
android:toXDelta="float"
android:fromYDelta="float"
android:toYDelta="float" />
<rotate
android:fromDegrees="float"
android:toDegrees="float"
android:pivotX="float"
android:pivotY="float" />
<set>
...
</set>
</set>
是一个动画容器, 管理多个动画的群组,与之相对应的动画对象是AnimationSet,它主要有两个属性,android:interpolator 代表一个插值器资源,android:shareInterpolator 代表多个动画是否共享插值器
下面主要讲述下 interpolator 和 pivot。
Interpolator 主要作用是可以控制动画的变化速率 ,就是动画进行的快慢节奏。
Android 系统已经为我们提供了一些Interpolator ,比如 accelerate_decelerate_interpolator,accelerate_interpolator等。更多的interpolator 及其含义可以在Android SDK 中查看。同时这个Interpolator也是可以自定义的
pivot 决定了当前动画执行的参考位置
pivot 这个属性主要是在translate 和 scale 动画中,这两种动画都牵扯到view 的“物理位置“发生变化,所以需要一个参考点。
而pivotX和pivotY就共同决定了这个点;它的值可以是float或者是百分比数值。
50 距离动画所在view自身左边缘10像素
10% 距离动画所在view自身左边缘 的距离是整个view宽度的10%
10%p 距离动画所在view父控件左边缘的距离是整个view宽度的10%
Java 代码中的实现
Animation animation = new RotateAnimation(0f, 360f,Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
animation.setDuration(5000);
mTextView.startAnimation(animation);
补间动画只能用在View 对象之上, 并且功能相对来说比较局限,因此, 补间动画只能执行一些相对比较简单的动画。
属性动画
核心类 ValueAnimator
ValueAnimator 是整个属性动画的核心类之一,它的作用是在一定的时间范围内不断的修个对象的某个属性值, 从而实现平滑的过度,使用代码如下:
private void startValueAnimator() {
ValueAnimator animator = ValueAnimator.ofFloat(0.0f, 1.0f);
animator.setDuration(5000);
animator.start();
}
ValueAnimator.AnimatorUpdateListener mAnimatorUpdateListener = new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
Log.e("Owen", "value:" + animation.getAnimatedValue());
mTextView.setAlpha((Float) animation.getAnimatedValue());
}
};
对任意属性进行动画操作 ObjectAnimator
首先我们来看看如何用属性动画实现上面补间动画的效果
ObjectAnimator animator = ObjectAnimator.ofFloat(mTextView, "rotation", 0f, 360f);
animator.setDuration(5000);
animator.start();
ObjectAnimator animator1 = ObjectAnimator.ofFloat(mTextView, "alpha", 1.0f, 0.1f);
animator1.setRepeatCount(3);
animator1.setDuration(2000);
animator.start();
这两个方法用属性动画的方式分别实现了旋转动画和淡入淡出动画,可以看到,属性动画强大了许多,实现很方便,同时动画可变化的值也有了更多的选择,动画所能呈现的细节也更多。
多个属性动画的组合 AnimatorSet
独立的动画能够实现的视觉效果毕竟是相当有限的,AnimatorSet 可以将多个动画组合在一起执行, 代码如下:
private void playtAnimatorSet() {
ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(mImageView, "alpha", 1.0f, 0.5f, 0.8f, 1.0f);
ObjectAnimator scaleXAnim = ObjectAnimator.ofFloat(mImageView, "scaleX", 0.0f, 1.0f);
ObjectAnimator scaleYAnim = ObjectAnimator.ofFloat(mImageView, "scaleY", 0.0f, 2.0f);
AnimatorSet set = new AnimatorSet();
set.play(alphaAnim).with(scaleXAnim).after(scaleXAnim);
set.setDuration(3000);
set.start();
}
通过上面的方法可以控制动画同事播放或者按顺序播放。
属性动画的原理
在上面实现属性动画的时候,我们反复的使用到了ObjectAnimator 这个类,这个类继承自ValueAnimator,使用这个类可以对任意对象的任意属性进行动画操作,属性动画的运行机制是通过不断地对值进行操作来实现的,而初始值和结束值之间的动画过渡就是由ValueAnimator这个类来负责计算的。它的内部使用一种时间循环的机制来计算值与值之间的动画过渡,我们只需要将初始值和结束值提供给ValueAnimator,并且告诉它动画所需运行的时长,那么ValueAnimator就会自动帮我们完成从初始值平滑地过渡到结束值这样的效果。除此之外,ValueAnimator还负责管理动画的播放次数、播放模式、以及对动画设置监听器等。
通过duration、startPropertyValue和endPropertyValue 等值,我们就可以定义动画运行时长,初始值和结束值。然后通过start方法开始动画。那么ValueAnimator 到底是怎样实现从初始值平滑过渡到结束值的呢?这个就是由TypeEvaluator 和TimeInterpolator 共同决定的,TypeEvaluator 决定了动画如何从初始值过渡到结束值。TimeInterpolator 决定了动画从初始值过渡到结束值的节奏。
通过下面的例子说明
用TypeEvaluator 确定运动轨迹
TypeEvaluator决定了动画如何从初始值过渡到结束值。这个TypeEvaluator是个接口,我们可以实现这个接口。
PointAnimView 代码
public class CircleAnimView extends View {
public static final int RADIUS = 20;
private Point currentPoint;
private Paint circlePaint;
private Paint linePaint;
private int color;
private int radius = RADIUS;
private AnimatorSet animSet;
private TimeInterpolator interpolatorType = new LinearInterpolator();
public CircleAnimView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public float getRadius() {
return radius;
}
public void setRadius(int radius) {
this.radius = radius;
}
public int getColor() {
return color;
}
public void setColor(int color) {
this.color = color;
circlePaint.setColor(this.color);
}
private void init() {
circlePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
circlePaint.setColor(Color.TRANSPARENT);
linePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
linePaint.setColor(Color.RED);
linePaint.setStrokeWidth(2);
}
@Override
protected void onDraw(Canvas canvas) {
if (currentPoint == null) {
currentPoint = new Point(RADIUS, RADIUS);
drawCircle(canvas);
StartAnimation();
} else {
drawCircle(canvas);
}
drawLine(canvas);
}
private void drawCircle(Canvas canvas) {
float x = currentPoint.x;
float y = currentPoint.y;
canvas.drawCircle(x, y, radius, circlePaint);
}
private void drawLine(Canvas canvas) {
canvas.drawLine(10, getHeight() / 2, getWidth(), getHeight() / 2, linePaint);
canvas.drawLine(10, getHeight() / 2 - 150, 10, getHeight() / 2 + 150, linePaint);
canvas.drawPoint(currentPoint.x, currentPoint.y, linePaint);
}
public void StartAnimation() {
Point startP = new Point(RADIUS, RADIUS);
Point endP = new Point(getWidth() - RADIUS, getHeight() - RADIUS);
final ValueAnimator valueAnimator = ValueAnimator.ofObject(new PointEvaluator(), startP, endP);
valueAnimator.setRepeatCount(5);
valueAnimator.setRepeatMode(ValueAnimator.REVERSE);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
currentPoint = (Point) animation.getAnimatedValue();
postInvalidate();
}
});
ObjectAnimator animColor = ObjectAnimator.ofObject(this, "color", new ArgbEvaluator(), Color.GREEN,
Color.YELLOW, Color.BLUE, Color.WHITE, Color.RED);
animColor.setRepeatCount(-1);
animColor.setRepeatMode(ValueAnimator.REVERSE);
ValueAnimator animScale = ValueAnimator.ofFloat(30f, 80f, 50f, 10f, 35f,55f,10f);
animScale.setRepeatCount(-1);
animScale.setRepeatMode(ValueAnimator.REVERSE);
animScale.setDuration(5000);
animScale.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
radius = Math.round((float)animation.getAnimatedValue());
}
});
animSet = new AnimatorSet();
animSet.play(valueAnimator).with(animColor).with(animScale);
animSet.setDuration(5000);
animSet.setInterpolator(interpolatorType);
animSet.start();
}
public void stopAnimation() {
if (animSet != null) {
animSet.cancel();
this.clearAnimation();
}
}
}
TimeInterpolator 介绍
Interpolator的概念其实我们并不陌生,在补间动画中我们就使用到了。他就是用来控制动画快慢节奏的;而在属性动画中,TimeInterpolator 也是类似的作用;TimeInterpolator 继承自Interpolator。我们可以继承TimerInterpolator 以自己的方式控制动画变化的节奏,也可以使用Android 系统提供的Interpolator。
这里我们使用的Interpolator就决定了 前面我们提到的fraction。变化的节奏决定了动画所执行的百分比。不得不说,这么ValueAnimator的设计的确是很巧妙。
补间动画与属性动画的比较
第一点 、虽然使用translate将图片移动了,但是点击原来的位置,依旧可以发生点击事件,而属性动画却不是。因此我们可以确定,属性动画才是真正的实现了view的移动,补间动画对view的移动更像是在不同地方绘制了一个影子,实际的对象还是处于原来的地方。补间动画其实只是调整了子view画布canvas的坐标系,其实并没有修改任何属性,所以只能在原位置才能处理触摸事件。
第二点 当我们把动画的repeatCount设置为无限循环时,如果在Activity退出时没有及时将动画停止,属性动画会导致Activity无法释放而导致内存泄漏,而补间动画却没有问题。因此,使用属性动画时切记在Activity执行 onStop 方法时顺便将动画停止。