自定义控件之帧动画

来源:互联网 发布:音频转文字软件 编辑:程序博客网 时间:2024/05/16 12:44

看到了这篇文章的标题,你也大概知道了这篇文章主要讲什么:利用原生的<animation-list>标签配置帧动画时,如果图片过大,或者图片过多,就会OOM。所以,为了避免这样情况,有以下两种方式:

1. 让UI把帧动画设计成gif动画,然后使用某个gif开源框架加载,如:https://github.com/koral--/android-gif-drawable
2. 自定义控件实现帧动画


本文主要讲的是第二种方式,自定义控件实现帧动画,废话少说,直接上代码。


等等,还是先讲一下思路吧。

1. 怎么才能不OOM呢?

答:只要一次加载一张图片不就可以了,在加载第二章图片的时候,把第一张图片recycle掉。


2. 难道要直接继承view吗?

答:直接继承View也可以,但本人没那么强大的能力。本人选择的是SurfaceView。


3. 为啥选择SurfaceView?

答:

a:SurfaceView是View的继承类,这个视图里内嵌了一个专门用于绘制的Surface。你可以控制这个Surface的格式和尺寸。Surfaceview控制这个Surface的绘制位置。

b:Surfaceview提供了两个线程:UI线程和渲染线程,他的特性是:可以在主线程之外的线程中向屏幕绘图上,这样可以避免画图任务繁重的时候造成主线程阻塞。


4. 如何像<animation-list>那样,配置图片位置,图片显示时间呢?

答:在assets中搞一个配置文件,配置好,然后在代码中解析不就行啦。


5. 额,没有5了



好啦,现在可以上代码了。

先上这个:配置文件frame_anim.txt(不要问我为什么用txt,我自己也不知道)

<anim>    <frame>        <icon>frame_anim/sing01.jpg</icon>        <duration>300</duration>    </frame>    <frame>        <icon>frame_anim/sing02.jpg</icon>        <duration>300</duration>    </frame>    <frame>        <icon>frame_anim/sing03.jpg</icon>        <duration>300</duration>    </frame>    <frame>        <icon>frame_anim/sing04.jpg</icon>        <duration>300</duration>    </frame></anim>

里面的icon路径也是assets下的。


配置文件搞定,然后上下一位:frame_anim.txt里面每一项对应的javaBean文件

public class AnimItem {    /**     * 图片地址     */    private String iconUrl;    /**     * 显示时间     */    private long duration;    public String getIconUrl() {        return iconUrl;    }    public void setIconUrl(String iconUrl) {        this.iconUrl = iconUrl;    }    public long getDuration() {        return duration;    }    public void setDuration(long duration) {        this.duration = duration;    }}


在接下来上这个:辅助类代码,包括解析txt,加载assets中的图片等

import android.content.Context;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.util.Xml;import org.xmlpull.v1.XmlPullParser;import java.io.ByteArrayOutputStream;import java.io.IOException;import java.io.InputStream;import java.util.ArrayList;import java.util.List;/** * className: AnimationHelper * function: 自定义帧动画辅助类 * <p> * create at 2017/5/26 15:49 * * @author pg */public class AnimationHelper {    /**     * 加载帧动画图片     *     * @param context     * @param url     * @param reqWidth     * @param reqHeight     * @return     */    public static Bitmap loadFromAsset(Context context, String url, int reqWidth, int reqHeight) {        InputStream is = null;        Bitmap bitmap;        try {            is = context.getAssets().open(url);        } catch (IOException e) {            e.printStackTrace();        }        byte[] data = input2byte(is);        BitmapFactory.Options newOpts = new BitmapFactory.Options();        // 开始读入图片,此时把options.inJustDecodeBounds 设回true,即只读边不读内容        newOpts.inJustDecodeBounds = true;        BitmapFactory.decodeByteArray(data, 0, data.length, newOpts);        newOpts.inJustDecodeBounds = false;        int width = newOpts.outWidth;        int height = newOpts.outHeight;        // 缩放比。由于是固定比例缩放,只用高或者宽其中一个数据进行计算即可        int inSampleSize = 1;//be=1表示不缩放        if (height > reqHeight || width > reqWidth) {            // 计算出实际宽高和目标宽高的比率            final int heightRatio = Math.round((float) height                    / (float) reqHeight);            final int widthRatio = Math.round((float) width                    / (float) reqWidth);            // 选择宽和高中最小的比率作为inSampleSize的值,这样可以保证最终图片的宽和高            // 一定都会大于等于目标的宽和高。            inSampleSize = heightRatio < widthRatio ? heightRatio                    : widthRatio;        }        newOpts.inSampleSize = inSampleSize;//设置缩放比例        // 开始压缩图片,注意此时已经把options.inJustDecodeBounds 设回false了        bitmap = BitmapFactory.decodeByteArray(data, 0, data.length, newOpts);        //根据需求宽高,定向设置大小        bitmap = Bitmap.createScaledBitmap(bitmap, reqWidth, reqHeight, true);        return bitmap;    }    /**     * 输入流转byte[]     *     * @param inStream InputStream     * @return Byte数组     */    public static final byte[] input2byte(InputStream inStream) {        if (inStream == null)            return null;        ByteArrayOutputStream swapStream = new ByteArrayOutputStream();        byte[] buff = new byte[100];        int rc = 0;        try {            while ((rc = inStream.read(buff, 0, 100)) > 0) {                swapStream.write(buff, 0, rc);            }        } catch (IOException e) {            e.printStackTrace();        }        return swapStream.toByteArray();    }    /**     * 解析xml文件,获取帧动画内容     *     * @param context     * @param file     * @return     * @throws Exception     */    public static List<AnimItem> getAnimFromXml(Context context, String file) {        //解析的结果        List<AnimItem> resultList = null;        try {            //获取文件流            InputStream ins = context.getAssets().open(file);            //xml解析器            XmlPullParser parser = Xml.newPullParser();            parser.setInput(ins, "utf-8");            //获取节点状态            int eventType = parser.getEventType();            AnimItem subItem = null;            while (eventType != XmlPullParser.END_DOCUMENT) {                switch (eventType) {                    //解析到标签的开始                    case XmlPullParser.START_TAG:                        //解析到anim,创建结果集                        if ("anim".equals(parser.getName())) {                            resultList = new ArrayList<>();                        }                        //检测到frame,取文件序号                        else if ("frame".equals(parser.getName())) {                            subItem = new AnimItem();                        }                        //检测到icon,设置icon                        else if ("icon".equals(parser.getName())) {                            parser.next();                            subItem.setIconUrl(parser.getText());                        }                        //检测到duration,设置duration                        else if ("duration".equals(parser.getName())) {                            parser.next();                            subItem.setDuration(Long.parseLong(parser.getText()));                        }                        break;                    //检测到结束标签,把frame放入集合                    case XmlPullParser.END_TAG:                        if ("frame".equals(parser.getName())) {                            resultList.add(subItem);                            subItem = null;                        }                        break;                }                eventType = parser.next();            }        } catch (Exception e) {            e.printStackTrace();        }        return resultList;    }}

在接下来,上压轴的这位:SurfaceAnimView.java

import android.content.Context;import android.graphics.Bitmap;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.PixelFormat;import android.os.Handler;import android.os.HandlerThread;import android.util.AttributeSet;import android.view.SurfaceHolder;import android.view.SurfaceView;import java.util.List;/** * className: SurfaceAnimView * function: 自定义播放帧动画组件 * <p> * create at 2017/5/26 16:11 * * @author pg */public class SurfaceAnimView extends SurfaceView {    /**     * 新建子线程,执行耗时任务     */    private static HandlerThread drawThread = new HandlerThread("surface_anim");    static {        drawThread.start();    }    private static Handler dHandler = new Handler(drawThread.getLooper());    /**     * 源数据     */    private List<AnimItem> imageList;    /**     * 耗时操作的runnable     */    private DrawRunnable mDrawRunnable;    public SurfaceAnimView(Context context) {        this(context, null);    }    public SurfaceAnimView(Context context, AttributeSet attrs) {        super(context, attrs);        getHolder().addCallback(callback);        //设置位于顶层,解决有背景是无法显示canvas的问题        this.setZOrderOnTop(true);        //设置背景透明,默认是不透明(黑色)        getHolder().setFormat(PixelFormat.TRANSPARENT);    }    /**     * 设置数据源     *     * @param imageList     */    public void setImageList(final List<AnimItem> imageList) {        this.imageList = imageList;    }    SurfaceHolder.Callback callback = new SurfaceHolder.Callback() {        @Override        public void surfaceCreated(SurfaceHolder holder) {            startDraw();        }        @Override        public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {        }        @Override        public void surfaceDestroyed(SurfaceHolder holder) {            stopDraw();        }    };    /**     * 开始绘制帧动画     */    private void startDraw() {        if (mDrawRunnable != null) {            mDrawRunnable.setStop(true);            dHandler.removeCallbacks(mDrawRunnable);        }        mDrawRunnable = new DrawRunnable();        dHandler.post(mDrawRunnable);    }    /**     * 结束绘制帧动画     */    private void stopDraw() {        if (mDrawRunnable != null) {            mDrawRunnable.setStop(true);            dHandler.removeCallbacks(mDrawRunnable);        }    }    class DrawRunnable implements Runnable {        private boolean isStop = false;        public void setStop(boolean isStop) {            this.isStop = isStop;        }        @Override        public void run() {            while (!isStop) {                if (imageList != null && imageList.size() > 0) {                    for (int i = 0; i < imageList.size(); i++) {                        AnimItem item = imageList.get(i);                        //得到需要的bitmap                        Bitmap subBitmap = AnimationHelper.loadFromAsset(getContext(), item.getIconUrl(), getWidth(), getHeight());                        if (subBitmap != null) {                            if (!isStop && getHolder() != null) {                                Canvas c = getHolder().lockCanvas(null);                                if (c != null) {                                    //设置除去bitmap之外的区域                                    c.drawColor(Color.YELLOW);                                    //绘制图片                                    c.drawBitmap(subBitmap, 0, 0, null);                                    getHolder().unlockCanvasAndPost(c);                                }                            }                        }                        long duration = item.getDuration();                        try {                            Thread.sleep(duration);                        } catch (InterruptedException e) {                            e.printStackTrace();                        }                    }                }            }        }    }    @Override    public void setVisibility(int visibility) {        super.setVisibility(visibility);    }}


嗯,结束。。。。。


还是先不结束把,还没说怎么调用呢,

xml.layout中,配置宽、高、id即可


activity中,获取到这个view,然后:!!!!

List<AnimItem> animItemList = AnimationHelper.getAnimFromXml(this, "frame_anim.txt");ivFrameAnim.setImageList(animItemList);

Game Over。上完走人