美文网首页
Android 自定义方式实现帧动画效果

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

作者: 只取一勺 | 来源:发表于2018-08-29 16:03 被阅读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();
        }
    }
    

    相关文章

      网友评论

          本文标题:Android 自定义方式实现帧动画效果

          本文链接:https://www.haomeiwen.com/subject/wwqkwftx.html