Android 自定义方式实现帧动画效果

前言

首先说下为啥要通过自定义处理的方式去实现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<Integer> ids = new ArrayList<Integer>();
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
<?xml version="1.0" encoding="utf-8"?><!--
    根标签为animation-list,其中oneshot代表着是否只展示一遍,设置为false会不停的循环播放动画
    根标签下,通过item标签对动画中的每一个图片进行声明
    android:duration 表示展示所用的该图片的时间长度
 -->
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
    android:oneshot="false">
    <item
        android:drawable="@drawable/footer_loading_710000"
        android:duration="60" />
    <item
        android:drawable="@drawable/footer_loading_710001"
        android:duration="60" />
    ......
    <item
        android:drawable="@drawable/footer_loading_710008"
        android:duration="60" />
    <item
        android:drawable="@drawable/footer_loading_710009"
        android:duration="60" />
</animation-list>

3)设置播放的图片资源

//通过代码添加图片id资源对应的播放动画方式
frameAnimation.setAnimation(iv, ids);

//通过xml定义图片id资源列表对应的播放动画方式
frameAnimation.setAnimation(context, R.drawable.footer_loading_list, iv);

4)开始动画

frameAnimation.start(true, 80);
  1. 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<Integer> 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<Integer> resourceIdList){
        mImageView = imageview;
        mResourceIdList = new ArrayList<Integer>();
        mResourceIdList.clear();
        mResourceIdList.addAll(resourceIdList);
    }

    /**
     * 设置动画播放资源
     */
    public void setAnimation(Context context, int resourceId, ImageView imageview){
        this.mImageView = imageview;
        mResourceIdList = new ArrayList<Integer>();
        mResourceIdList.clear();

        loadFromXml(context, resourceId, new OnParseListener() {
            @Override
            public void onParse(List<Integer> 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<Integer> res = new ArrayList<Integer>();
        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<Integer> 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();
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,992评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,212评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,535评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,197评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,310评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,383评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,409评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,191评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,621评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,910评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,084评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,763评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,403评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,083评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,318评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,946评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,967评论 2 351

推荐阅读更多精彩内容