前言

首先说下为啥要通过自定义处理的方式去实现Android的帧动画效果,因为通过系统原生支持的xml和java代码这两种方式实现,在播放的图片量很多时,会出现内存溢出,此现象也是在做项目当中有遇到,出现的情景:loading视图,由于项目中的加载视图采用的是播放一组连续图片来实现动画效果。殊不知这样做是有隐患的,那就是造成了大名鼎鼎的OOM。经过几番折腾和各种尝试,最终还是决定放弃原来帧动画实现方式,另辟蹊径。

方式一:
1.定义类XAnimationDrawable,在内部采用定时器给ImageView设置图片。
2.使用步骤:
1)实例XAnimationDrawable和ImageView
XAnimationDrawable frameAnimation = new XAnimationDrawable();
ImageView iv = (ImageView)findViewById(R.id.iv_animation);
2)准备图片id资源,以下提供了两种方式
//通过代码添加图片id资源
List ids = new ArrayList();
ids.add(R.drawable.footer_loading_710000);
ids.add(R.drawable.footer_loading_710001);
......
ids.add(R.drawable.footer_loading_710015);
ids.add(R.drawable.footer_loading_710016);
//通过xml的定义,footer_loading_list.xml
android:oneshot="false">
android:drawable="@drawable/footer_loading_710000"
android:duration="60" />
android:drawable="@drawable/footer_loading_710001"
android:duration="60" />
......
android:drawable="@drawable/footer_loading_710008"
android:duration="60" />
android:drawable="@drawable/footer_loading_710009"
android:duration="60" />
3)设置播放的图片资源
//通过代码添加图片id资源对应的播放动画方式
frameAnimation.setAnimation(iv, ids);
//通过xml定义图片id资源列表对应的播放动画方式
frameAnimation.setAnimation(context, R.drawable.footer_loading_list, iv);
4)开始动画
frameAnimation.start(true, 80);
XAnimationDrawable.java
public class XAnimationDrawable {
private static final int MSG_START = 0xf1;
private static final int MSG_STOP = 0xf2;
private static final int STATE_STOP = 0xf3;
private static final int STATE_RUNNING = 0xf4;
//运行状态
private int mState = STATE_RUNNING;
//显示图片的View
private ImageView mImageView = null;
//图片资源的ID列表
private List mResourceIdList = null;
//定时任务器
private Timer mTimer = null;
//定时任务
private AnimTimerTask mTimeTask = null;
//记录播放位置
private int mFrameIndex = 0;
//播放形式
private boolean isLooping = false;
public XAnimationDrawable() {
mTimer = new Timer();
}
/**
* 设置动画播放资源
*/
public void setAnimation(ImageView imageview, List resourceIdList){
mImageView = imageview;
mResourceIdList = new ArrayList();
mResourceIdList.clear();
mResourceIdList.addAll(resourceIdList);
}
/**
* 设置动画播放资源
*/
public void setAnimation(Context context, int resourceId, ImageView imageview){
this.mImageView = imageview;
mResourceIdList = new ArrayList();
mResourceIdList.clear();
loadFromXml(context, resourceId, new OnParseListener() {
@Override
public void onParse(List res) {
mResourceIdList.addAll(res);
}
});
}
/**
* 解析xml
*
* @param context
* @param resourceId 资源id
*/
private void loadFromXml(final Context context, final int resourceId,
final OnParseListener onParseListener) {
if (context == null) {
return;
}
final List res = new ArrayList();
XmlResourceParser parser = context.getResources().getXml(resourceId);
try {
int eventType = parser.getEventType();
while (eventType != XmlPullParser.END_DOCUMENT) {
if (eventType == XmlPullParser.START_DOCUMENT) {
} else if (eventType == XmlPullParser.START_TAG) {
if (parser.getName().equals("item")) {
for (int i = 0; i < parser.getAttributeCount(); i++) {
if (parser.getAttributeName(i).equals("drawable")) {
int resId = Integer.parseInt(parser.getAttributeValue(i).substring(1));
res.add(resId);
}
}
}
} else if (eventType == XmlPullParser.END_TAG) {
} else if (eventType == XmlPullParser.TEXT) {
}
eventType = parser.next();
}
} catch (IOException e) {
// TODO: handle exception
e.printStackTrace();
} catch (XmlPullParserException e2) {
// TODO: handle exception
e2.printStackTrace();
} finally {
parser.close();
}
if (onParseListener != null) {
onParseListener.onParse(res);
}
}
/**
* 开始播放动画
* @param loop 是否循环播放
* @param duration 动画播放时间间隔
*/
public void start(boolean loop, int duration){
stop();
if (mResourceIdList == null || mResourceIdList.size() == 0) {
return;
}
if (mTimer == null) {
mTimer = new Timer();
}
isLooping = loop;
mFrameIndex = 0;
mState = STATE_RUNNING;
mTimeTask = new AnimTimerTask( );
mTimer.schedule(mTimeTask, 0, duration);
}
/**
* 停止动画播放
*/
public void stop(){
if (mTimer != null) {
mTimer.purge();
mTimer.cancel();
mTimer = null;
}
if (mTimeTask != null) {
mFrameIndex = 0;
mState = STATE_STOP;
mTimeTask.cancel();
mTimeTask = null;
}
//移除Handler消息
if (AnimHandler != null) {
AnimHandler.removeMessages(MSG_START);
AnimHandler.removeMessages(MSG_STOP);
AnimHandler.removeCallbacksAndMessages(null);
}
}
/**
* 定时器任务
*/
class AnimTimerTask extends TimerTask {
@Override
public void run() {
if (mFrameIndex < 0 || mState == STATE_STOP) {
return;
}
if (mFrameIndex < mResourceIdList.size()) {
Message msg = AnimHandler.obtainMessage(MSG_START, 0, 0, null);
msg.sendToTarget();
} else {
mFrameIndex = 0;
if(!isLooping){
Message msg = AnimHandler.obtainMessage(MSG_STOP, 0, 0, null);
msg.sendToTarget();
}
}
}
}
/**
* Handler
*/
private Handler AnimHandler = new Handler(){
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_START:{
if(mFrameIndex >= 0 && mFrameIndex < mResourceIdList.size() && mState == STATE_RUNNING){
mImageView.setImageResource(mResourceIdList.get(mFrameIndex));
mFrameIndex++;
}
}
break;
case MSG_STOP:{
if (mTimeTask != null) {
mFrameIndex = 0;
mTimer.purge();
mTimeTask.cancel();
mState = STATE_STOP;
mTimeTask = null;
if (isLooping) {
mImageView.setImageResource(0);
}
}
}
break;
default:
break;
}
}
};
public interface OnParseListener {
void onParse(List res);
}
}
方式二:
1.定义类XFrameAnimation,继承自Drawable类,同时实现Animatable接口。
2.XFrameAnimation内部通过ValueAnimator(动画的数值发生器)来有序的产生图片资源的resId,然后在自身的draw方法中将resId对应的资源绘制到Canvas上。传入的是一个图片资源数组,所以呈现出来的就是一个帧动画的效果。
3.使用
// 图片资源Id数组
int[] RES_IDS = new int[]{
R.drawable.loading_1840000,
R.drawable.loading_1840001,
......
};
// 构建播放图片的XFrameAnimation
XFrameAnimation loadingDrawable = new XFrameAnimation(600, RES_IDS, getContext().getResources());
ImageView ivLoadingImage = (ImageView) findViewById(R.id.iv_loading_image);
ivLoadingImage.setImageDrawable(loadingDrawable);
4.代码(参考自网上一位大神分享的,具体原链接暂时找不着了,这个代码是之前写的)
public class XFrameAnimation extends Drawable implements Animatable {
private static final long DEFAULT_DURATION = 500;
private long duration = DEFAULT_DURATION;
private final Paint mPaint;
private final int[] RES_IDS;
private int resIndex;
private final Resources mResources;
private ValueAnimator mAnimator;
private ValueAnimator.AnimatorUpdateListener mAnimUpdateListener;
//取第一帧,用于获取图片宽高
private Drawable mFirstDrawable;
public XFrameAnimation(int[] RES_IDS, Resources resources) {
this(DEFAULT_DURATION, RES_IDS, resources);
}
public XFrameAnimation(long duration, int[] RES_IDS, Resources resources) {
this.duration = duration;
this.RES_IDS = RES_IDS;
this.mResources = resources;
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setFilterBitmap(true);
mPaint.setDither(true);
if (this.RES_IDS == null || this.RES_IDS.length <= 0) {
throw new RuntimeException(" XFrameAnimation RES_IDS can not null or empty !!!");
}
mFirstDrawable = resources.getDrawable(this.RES_IDS[0]);
createAnimator();
}
/**
* 初始化动画
*/
private void createAnimator() {
mAnimator = ValueAnimator.ofInt(RES_IDS.length - 1);
mAnimator.setInterpolator(new LinearInterpolator());
mAnimator.setRepeatCount(ValueAnimator.INFINITE);
mAnimator.setRepeatMode(ValueAnimator.RESTART);
mAnimator.setDuration(duration);
mAnimUpdateListener = new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
invalidate(((int) animation.getAnimatedValue()));
}
};
}
/**
* 重绘
*
* @param index 帧索引
*/
public void invalidate(int index) {
this.resIndex = index;
invalidateSelf();
}
/**
* 获取动画帧数
*
* @return 帧数量
*/
public int getFrameCount(){
return RES_IDS.length;
}
@Override
public void draw(Canvas canvas) {
if (mResources != null) {
BitmapDrawable drawable = (BitmapDrawable) mResources.getDrawable(RES_IDS[resIndex % RES_IDS.length]);
Bitmap bitmap = drawable.getBitmap();
canvas.drawBitmap(bitmap, 0, 0, mPaint);
}
}
@Override
public void setAlpha(int alpha) {
}
@Override
public void setColorFilter(ColorFilter colorFilter) {
mPaint.setColorFilter(colorFilter);
}
@Override
public int getOpacity() {
return PixelFormat.OPAQUE;
}
@Override
public void start() {
// If the animators has not ended, do nothing.
if (mAnimator.isStarted()) {
return;
}
startAnimator();
invalidateSelf();
}
/**
* 开始执行动画
*/
private void startAnimator() {
if (mAnimator != null) {
mAnimator.addUpdateListener(mAnimUpdateListener);
mAnimator.start();
}
}
@Override
public void stop() {
if (mAnimator != null && mAnimator.isStarted()) {
mAnimator.removeAllUpdateListeners();
mAnimator.end();
}
}
@Override
public boolean isRunning() {
return mAnimator.isRunning();
}
@Override
public int getIntrinsicWidth() {
return mFirstDrawable.getIntrinsicWidth();
}
@Override
public int getIntrinsicHeight() {
return mFirstDrawable.getIntrinsicHeight();
}
}