本文对Android中的常用动画技术进行了较为全面的总结,并给出了代码示例(Java代码实现和XML中实现)。由于内容较多,所以尽可能简洁表述,并重点指出其中不易理解、容易出错的内容。
本文提到的Android动画主要有三类:
- Drawable动画
- Animation与AnimationSet
- Animator与AnimatorSet
示例代码、思维导图可在此下载
https://github.com/jzj1993/AndroidAnimation
Animatable Drawable 动画
用Drawable实现动画,适用于不需要变换View,只需要对View中所展示的Drawable图形产生动画的情况。
由于不像Animation需要对View进行矩阵变换,更不需要像Animator使用反射机制,实现同样的效果,Drawable动画通常性能较好,推荐使用。
原理简介
当给一个View的背景、ImageView的src设置了Drawable后,View会将自身设置为Drawable的Drawable.Callback(View实现了Drawable.Callback接口)。于是在Drawable需要刷新时,可通过这个接口调用View的invalidate,从而触发View.onDraw方法进行重绘。具体可参考Android源码。
支持动画的Drawable,应实现Animatable接口。有些View属性会对Drawable进行判断(例如ImageView的src属性),如果实现了Animatable接口,就会自动调用其start方法启动动画。
public class ImageView extends View implements Drawable.Callback {
public void setImageDrawable(Drawable drawable) {
if (mDrawable != drawable) {
// ...
updateDrawable(drawable);
// ...
}
}
private void updateDrawable(Drawable d) {
if (mDrawable != null) {
mDrawable.setCallback(null);
unscheduleDrawable(mDrawable);
}
mDrawable = d;
if (d != null) {
d.setCallback(this);
if (d.isStateful()) {
d.setState(getDrawableState());
}
d.setLevel(mLevel);
d.setLayoutDirection(getLayoutDirection());
d.setVisible(getVisibility() == VISIBLE, true);
mDrawableWidth = d.getIntrinsicWidth();
mDrawableHeight = d.getIntrinsicHeight();
applyColorMod();
configureBounds();
} else {
mDrawableWidth = mDrawableHeight = -1;
}
}
}
FrameAnimation 逐帧动画(AnimationDrawable)
逐帧动画实际上就是一种支持动画效果的Drawable
public class AnimationDrawable extends DrawableContainer implements Runnable, Animatable { }
从XML创建,使用Java代码加载:
将每一帧的图片放在资源文件夹
res/drawable
在XML中定义动画每一帧及其持续时间
在Java代码中加载动画并设置给View,然后启动动画
OneShot属性为true则只播放一次,否则不断循环播放
<?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/icon1"
android:duration="200" />
<item
android:drawable="@drawable/icon2"
android:duration="200" />
<item
android:drawable="@drawable/icon3"
android:duration="200" />
<item
android:drawable="@drawable/icon4"
android:duration="200" />
</animation-list>
mFrameAnimation = (AnimationDrawable) getResources().getDrawable(R.drawable.frame_anim); // 从XML加载动画
mTextView.setBackground(mFrameAnimation);
mFrameAnimation.start();
- 也可以在Java代码中实例化AnimationDrawable对象,并添加帧和持续时间
mFrameAnimation = new AnimationDrawable();
mFrameAnimation.addFrame(getResources().getDrawable(R.drawable.icon1), 200);
mFrameAnimation.addFrame(getResources().getDrawable(R.drawable.icon2), 200);
注意:
Android系统提供的这种逐帧动画,通常每一帧是一个BitmapDrawable,会在内存中一直保存每一帧的Bitmap,如果帧数较多、每一帧图片较大,消耗的内存会很大。
如果对内存有要求,可以自行实现逐帧动画,每切换一帧的时候临时加载该帧的BitmapDrawable,这样虽然增加了一些CPU资源消耗,但减少了内存占用。
AnimatedRotateDrawable旋转动画
和逐帧动画类似,Android系统还提供了AnimatedRotateDrawable,可以实现图片旋转的效果,常用于展示进度条动画。
由于构造函数是私有的,AnimatedRotateDrawable不支持在Java代码中实例化,只能从XML加载。
res/drawable/loading.png是一个PNG文件
res/drawable/animated_rotate.xml
<?xml version="1.0" encoding="utf-8"?>
<animated-rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/loading"
android:fromDegrees="0.0"
android:pivotX="50.0%"
android:pivotY="50.0%"
android:toDegrees="360.0" />
注:因为是Drawable,animated_rotate的XML文件应该放在drawable文件夹下。
在XML中引用Drawable即可
<ProgressBar
android:id="@+id/progress"
android:layout_width="50dp"
android:layout_height="50dp"
android:indeterminate="true"
android:indeterminateDrawable="@drawable/animated_rotate" />
<ImageView
android:id="@+id/image_view"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_marginTop="60dp"
android:src="@drawable/animated_rotate" />
需要注意的是,不同的View和属性,是否会自动启动Drawable动画的行为不同。例如ProgressBar从XML加载indeterminateDrawable是可以启动动画的,但从Java加载则需要显示调用start方法。而ImageView的src属性,则可以自动启动动画。
如果用Java代码给ProgressBar的indeterminateDrawable设置Drawable,一般需要用setBounds指定drawable的宽高,并显示调用Animatable.start()方法启动动画。
如果用Java代码给ImageView的src设置Drawable,ImageView会自动处理Drawable的尺寸,并判断其是否实现了Animatable接口,从而启动动画。
ProgressBar progressBar = (ProgressBar) findViewById(R.id.progress);
final Drawable drawable = getResources().getDrawable(R.drawable.animated_rotate);
if (drawable != null) {
drawable.setBounds(0, 0, progressBar.getWidth(), progressBar.getHeight());
progressBar.setIndeterminateDrawable(drawable);
if (drawable instanceof Animatable) {
((Animatable) drawable).start();
}
}
ImageView imageView = (ImageView) findViewById(R.id.image_view);
imageView.setImageDrawable(getResources().getDrawable(R.drawable.animated_rotate));
自定义DrawableAnimation
通过实现Animatable接口,可以自行定义支持动画效果的Drawable。
示例代码是一个每隔0.5s切换一种随机颜色值的Drawable,具体效果可参考工程源码。
public class MyAnimDrawable extends Drawable implements Animatable, Runnable {
private boolean mRunning = false;
@Override
public void draw(Canvas canvas) {
canvas.drawARGB(128, (int) (Math.random() * 255), (int) (Math.random() * 255), (int) (Math.random() * 255));
}
@Override
public void setAlpha(int alpha) {
}
@Override
public void setColorFilter(ColorFilter cf) {
}
@Override
public int getOpacity() {
return 0;
}
@Override
public boolean setVisible(boolean visible, boolean restart) {
boolean changed = super.setVisible(visible, restart);
if (visible) {
if (changed || restart) {
nextFrame();
}
} else {
unscheduleSelf(this);
}
return changed;
}
@Override
public void start() {
if (!mRunning) {
mRunning = true;
nextFrame();
}
}
@Override
public void stop() {
unscheduleSelf(this);
mRunning = false;
}
@Override
public boolean isRunning() {
return mRunning;
}
@Override
public void run() {
invalidateSelf();
nextFrame();
}
private void nextFrame() {
unscheduleSelf(this);
scheduleSelf(this, SystemClock.uptimeMillis() + 500);
}
}
最后,欢迎扫码关注微信公众号。程序员同行学习交流,聊天交友,国内外名企求职内推(微软 / 小冰 / Amazon / Shopee / Coupang / ATM / 头条 / 拼多多等),可加我微信 jzj2015 进技术群(备注进技术群,并简单自我介绍)。

本文由jzj1993原创,转载请注明来源:https://www.paincker.com/android-animation-1
(标注了原文链接的文章除外)
暂无评论