package com.mrwang.imageframe; import android.content.res.Resources; import android.graphics.drawable.BitmapDrawable; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.SystemClock; import android.support.annotation.IntDef; import android.support.annotation.NonNull; import android.support.annotation.RawRes; import android.text.TextUtils; import java.io.File; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.ref.SoftReference; /** * 直接内置序列帧解析的View * User: chengwangyong([email protected]) * Date: 2017/5/1 * Time: 上午11:09 */ public class ImageFrameHandler implements WorkHandler.WorkMessageProxy { private Resources resources; private int[] resArray; private int width; private int height; private boolean isRunning; private final WorkHandler workHandler; private File[] files; private static final int FILE = 0; private static final int RES = 1; private int type; private boolean isOpenCache; @Retention(RetentionPolicy.SOURCE) @IntDef({FILE, RES}) public @interface Operation {} ImageFrameHandler(@Operation int type) { this.type = type; imageCache = new ImageCache(); workHandler = new WorkHandler(); } @Deprecated public ImageFrameHandler() { imageCache = new ImageCache(); workHandler = new WorkHandler(); } private ImageCache imageCache; private Handler handler = new Handler(Looper.getMainLooper()) { @Override public void handleMessage(Message msg) { if (onImageLoadListener != null) { onImageLoadListener.onImageLoad(bitmapDrawable); } switch (msg.what) { case RES: load((int[]) msg.obj); break; case FILE: load((File[]) msg.obj); break; } } }; private volatile float frameTime; private volatile int index = 0; private volatile BitmapDrawable bitmapDrawable; private OnImageLoadListener onImageLoadListener; private boolean loop; /** * load frame form file directory; * * @param width request width * @param height request height * @param fileDir file directory * @param fps The number of broadcast images per second * @param onPlayFinish finish callback */ @Deprecated public void loadImage(final String fileDir, int width, int height, int fps, OnImageLoadListener onPlayFinish) { if (!isRunning) { isRunning = true; this.width = width; this.height = height; loadImage(fileDir, fps, onPlayFinish); } } /** * load frame form file directory; * * @param fileDir file directory * @param fps The number of broadcast images per second * @param onPlayFinish finish callback */ @Deprecated public void loadImage(final String fileDir, int fps, OnImageLoadListener onPlayFinish) { if (!isRunning && !TextUtils.isEmpty(fileDir) && fps > 0) { isRunning = true; File dir = new File(fileDir); if (dir.exists() && dir.isDirectory()) { File[] files = dir.listFiles(); loadImage(files, width, height, fps, onPlayFinish); } } } /** * load frame form file files Array; * * @param width request width * @param height request height * @param files files Array * @param fps The number of broadcast images per second * @param onPlayFinish finish callback */ @Deprecated public void loadImage(final File[] files, int width, int height, int fps, OnImageLoadListener onPlayFinish) { if (!isRunning) { setImageFrame(files, width, height, fps, onPlayFinish); load(files); } } /** * load frame form file files Array; * * @param files files Array * @param fps The number of broadcast images per second * @param onPlayFinish finish callback */ @Deprecated public void loadImage(final File[] files, int fps, OnImageLoadListener onPlayFinish) { loadImage(files, width, height, fps, onPlayFinish); } private void setImageFrame(final File[] files, int width, int height, int fps, OnImageLoadListener onPlayFinish) { this.width = width; this.height = height; if (imageCache == null) { imageCache = new ImageCache(); } this.onImageLoadListener = onPlayFinish; frameTime = 1000f / fps + 0.5f; workHandler.addMessageProxy(this); this.files = files; } /** * load frame form file resources Array; * * @param resArray resources Array * @param width request width * @param height request height * @param fps The number of broadcast images per second * @param onPlayFinish finish callback */ @Deprecated public void loadImage(Resources resources, @RawRes int[] resArray, int width, int height, int fps, OnImageLoadListener onPlayFinish) { loadImage(resources, resArray, width, height, fps, onPlayFinish); } private void setImageFrame(Resources resources, @RawRes int[] resArray, int width, int height, int fps, OnImageLoadListener onPlayFinish) { this.width = width; this.height = height; this.resources = resources; if (imageCache == null) { imageCache = new ImageCache(); } this.onImageLoadListener = onPlayFinish; frameTime = 1000f / fps + 0.5f; workHandler.addMessageProxy(this); this.resArray = resArray; } /** * load frame form file resources Array; * * @param resArray resources Array * @param fps The number of broadcast images per second * @param onPlayFinish finish callback */ @Deprecated public void loadImage(Resources resources, @RawRes int[] resArray, int fps, OnImageLoadListener onPlayFinish) { if (!isRunning) { setImageFrame(resources, resArray, width, height, fps, onPlayFinish); load(resArray); } } /** * loop play frame * * @param loop true is loop */ public ImageFrameHandler setLoop(boolean loop) { if (!isRunning) { this.loop = loop; } return this; } /** * stop play frame */ public void stop() { workHandler.getHandler().removeCallbacksAndMessages(null); workHandler.removeMessageProxy(this); handler.removeCallbacksAndMessages(null); resources = null; isRunning = false; } /** * 新增方法 * 暂停 * stop play frame */ public void pause() { isRunning = false; workHandler.getHandler().removeCallbacksAndMessages(null); // workHandler.removeMessageProxy(this); handler.removeCallbacksAndMessages(null); } public void start() { if (!isRunning) { isRunning = true; switch (type) { case FILE: load(files); break; case RES: load(resArray); break; } } } private void load(final File[] files) { Message message = Message.obtain(); message.obj = files; message.what = FILE; workHandler.getHandler().sendMessage(message); } private void load(@RawRes int[] res) { Message message = Message.obtain(); message.obj = res; message.what = RES; workHandler.getHandler().sendMessage(message); } private void loadInThreadFromFile(final File[] files) { if (index < files.length) { File file = files[index]; if (file.isFile() && isPicture(file)) { if (bitmapDrawable != null) { imageCache.mReusableBitmaps.add(new SoftReference<>(bitmapDrawable.getBitmap())); } long start = System.currentTimeMillis(); bitmapDrawable = BitmapLoadUtils.decodeSampledBitmapFromFile(file.getAbsolutePath(), width, height, imageCache, isOpenCache); long end = System.currentTimeMillis(); float updateTime = (frameTime - (end - start)) > 0 ? (frameTime - (end - start)) : 0; Message message = Message.obtain(); message.what = FILE; message.obj = files; handler.sendMessageAtTime(message, index == 0 ? 0 : (int) (SystemClock.uptimeMillis() + updateTime)); index++; } else { index++; loadInThreadFromFile(files); } } else { if (loop) { index = 0; loadInThreadFromFile(files); } else { index++; bitmapDrawable = null; frameTime = 0; if (onImageLoadListener != null) { onImageLoadListener.onPlayFinish(); } isRunning = false; onImageLoadListener = null; } } } private void loadInThreadFromRes(final int[] resIds) { if (index < resIds.length) { int resId = resIds[index]; if (bitmapDrawable != null) { imageCache.mReusableBitmaps.add(new SoftReference<>(bitmapDrawable.getBitmap())); } long start = System.currentTimeMillis(); bitmapDrawable = BitmapLoadUtils.decodeSampledBitmapFromRes(resources, resId, width, height, imageCache, isOpenCache); long end = System.currentTimeMillis(); float updateTime = (frameTime - (end - start)) > 0 ? (frameTime - (end - start)) : 0; Message message = Message.obtain(); message.what = RES; message.obj = resIds; handler.sendMessageAtTime(message, index == 0 ? 0 : (int) (SystemClock.uptimeMillis() + updateTime)); index++; } else { if (loop) { index = 0; loadInThreadFromRes(resIds); } else { index++; bitmapDrawable = null; frameTime = 0; if (onImageLoadListener != null) { onImageLoadListener.onPlayFinish(); } isRunning = false; onImageLoadListener = null; } } } private boolean isPicture(File file) { return file.getName().endsWith("png") || file.getName().endsWith("jpg"); } @Override public void handleMessage(Message msg) { switch (msg.what) { case RES: loadInThreadFromRes((int[]) msg.obj); break; case FILE: loadInThreadFromFile((File[]) msg.obj); break; } } public void setFps(int fps) { frameTime = 1000f / fps + 0.5f; } public void setOnImageLoaderListener(OnImageLoadListener onPlayFinish) { this.onImageLoadListener = onPlayFinish; } public interface OnImageLoadListener { void onImageLoad(BitmapDrawable drawable); void onPlayFinish(); } /** * 改造成build构建者模式 * <p> * 两种 一种是file * 一种是Resource */ public static class FileHandlerBuilder implements FrameBuild { private int width; private int height; private int fps = 30; private File[] files; private OnImageLoadListener onPlayFinish; private ImageFrameHandler imageFrameHandler; private int startIndex; private int endIndex; public FileHandlerBuilder(@NonNull File[] files) { if (files.length == 0) { throw new IllegalArgumentException("fileDir is not empty"); } this.files = files; createHandler(); } public FileHandlerBuilder(@NonNull String fileDir) { if (TextUtils.isEmpty(fileDir)) { throw new IllegalArgumentException("fileDir is not empty"); } File dir = new File(fileDir); if (dir.exists() && dir.isDirectory()) { files = dir.listFiles(); } createHandler(); } @Override public FrameBuild setLoop(boolean loop) { imageFrameHandler.setLoop(loop); return this; } @Override public FrameBuild stop() { imageFrameHandler.stop(); return this; } // 只需要改变数组长度即可 @Override public FrameBuild setStartIndex(int startIndex) { if (startIndex >= files.length) { throw new IllegalArgumentException("startIndex is not big to files length"); } this.startIndex = startIndex; return this; } @Override public FrameBuild setEndIndex(int endIndex) { if (endIndex > files.length) { throw new IllegalArgumentException("endIndex is not big to files length"); } if (endIndex <= startIndex) { throw new IllegalArgumentException("endIndex is not to small startIndex"); } this.endIndex = endIndex; return this; } @Override public FrameBuild setWidth(int width) { this.width = width; return this; } @Override public FrameBuild setHeight(int height) { this.height = height; return this; } @Override public FrameBuild setFps(int fps) { this.fps = fps; imageFrameHandler.setFps(fps); return this; } @Override public FrameBuild setOnImageLoaderListener(OnImageLoadListener onPlayFinish) { this.onPlayFinish = onPlayFinish; return this; } @Override public FrameBuild openLruCache(boolean isOpenCache) { imageFrameHandler.openLruCache(isOpenCache); return this; } @Override public ImageFrameHandler build() { if (!imageFrameHandler.isRunning) { clip(); imageFrameHandler.setImageFrame(files, width, height, fps, onPlayFinish); } return imageFrameHandler; } private void createHandler() { if (imageFrameHandler == null) { imageFrameHandler = new ImageFrameHandler(FILE); } } @Override public FrameBuild clip() { if (startIndex >= 0 && endIndex > 0 && startIndex < endIndex) { files = split(files, startIndex, endIndex); } return this; } File[] split(File[] resArray, int start, int end) { File[] ints = new File[end - start]; int index = 0; for (int i = start; i < end; i++) { ints[index] = resArray[i]; index++; } return ints; } } private void openLruCache(boolean isOpenCache) { this.isOpenCache = isOpenCache; } /** * 改造成build构建者模式 * <p> * 两种 一种是file * 一种是Resource */ public static class ResourceHandlerBuilder implements FrameBuild { @NonNull private final Resources resources; private int width; private int height; private int fps = 30; private int[] resArray; private OnImageLoadListener onPlayFinish; private ImageFrameHandler imageFrameHandler; private int startIndex; private int endIndex; public ResourceHandlerBuilder(@NonNull Resources resources, @NonNull @RawRes int[] resArray) { if (resArray.length == 0) { throw new IllegalArgumentException("resArray is not empty"); } this.resources = resources; this.resArray = resArray; createHandler(); } @Override public FrameBuild setLoop(boolean loop) { imageFrameHandler.setLoop(loop); return this; } @Override public FrameBuild stop() { imageFrameHandler.stop(); return this; } @Override public FrameBuild setStartIndex(int startIndex) { if (startIndex >= resArray.length) { throw new IllegalArgumentException("startIndex is not to big resArray length"); } this.startIndex = startIndex; return this; } @Override public FrameBuild setEndIndex(int endIndex) { if (endIndex > resArray.length) { throw new IllegalArgumentException("endIndex is not big to resArray length"); } if (endIndex <= startIndex) { throw new IllegalArgumentException("endIndex is not to small startIndex"); } this.endIndex = endIndex; return this; } @Override public FrameBuild clip() { if (startIndex >= 0 && endIndex > 0 && startIndex < endIndex) { resArray = split(resArray, startIndex, endIndex); } return this; } @Override public FrameBuild setWidth(int width) { this.width = width; return this; } @Override public FrameBuild setHeight(int height) { this.height = height; return this; } @Override public FrameBuild setFps(int fps) { this.fps = fps; imageFrameHandler.setFps(fps);// 这里有一个重复计算 后期想个更好的办法支持动态换 return this; } @Override public FrameBuild openLruCache(boolean isOpenCache) { imageFrameHandler.openLruCache(isOpenCache); return this; } @Override public FrameBuild setOnImageLoaderListener(OnImageLoadListener onPlayFinish) { this.onPlayFinish = onPlayFinish; return this; } @Override public ImageFrameHandler build() { if (!imageFrameHandler.isRunning) { clip(); imageFrameHandler.setImageFrame(resources, resArray, width, height, fps, onPlayFinish); } return imageFrameHandler; } private void createHandler() { if (imageFrameHandler == null) { imageFrameHandler = new ImageFrameHandler(RES); } } int[] split(int[] resArray, int start, int end) { int[] ints = new int[end - start]; int index = 0; for (int i = start; i < end; i++) { ints[index] = resArray[i]; index++; } return ints; } } }