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

2018-08-29  本文已影响0人  只取一勺

前言

首先说下为啥要通过自定义处理的方式去实现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();
    }
}
上一篇下一篇

猜你喜欢

热点阅读