package com.wind.gifassistant.views.gifview; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.InputStream; import com.wind.gifassistant.utils.AppConfigs; import android.content.Context; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Rect; import android.os.Handler; import android.os.Message; import android.os.SystemClock; import android.util.AttributeSet; import android.util.Log; import android.view.View; /** * GifView<br> * 本类可以显示一个gif动画,其使用方法和android的其它view(如imageview)一样。<br> * 如果要显示的gif太大,会出现OOM的问题。 * @author liao * */ public class GifView extends View implements GifAction{ private static String TAG = AppConfigs.APP_TAG + "GifView"; private static boolean DEBUG = true; private static boolean DEBUG_DRAW_THREAD = true; /**gif解码器*/ private GifDecoder gifDecoder = null; /**当前要画的帧的图*/ private Bitmap currentImage = null; private boolean mStop = false; private boolean pause = false; private boolean mShowCover = false; private int mShowWidth = -1; private int mShowHeight = -1; private Rect rect = new Rect(); private DrawThread drawThread = null; private GifParsingShowType animationType = GifParsingShowType.SYNC_DECODER; private GifShowGravity mShowGravity = GifShowGravity.FILL_FULL_SCREEN; private GifSourceInfos mGifSourceInfos = null; /** * 解码过程中,Gif动画显示的方式<br> * 如果图片较大,那么解码过程会比较长,这个解码过程中,gif如何显示 * @author liao * */ public enum GifParsingShowType{ /** * 在解码过程中,不显示图片,直到解码全部成功后,再显示 */ WAIT_FINISH (0), /** * 和解码过程同步,解码进行到哪里,图片显示到哪里 */ SYNC_DECODER (1), /** * 在解码过程中,只显示第一帧图片 */ COVER (2); GifParsingShowType(int i){ nativeInt = i; } final int nativeInt; } /** * Gif的显示方式,居中,满屏, * */ public enum GifShowGravity{ /** * 满铺 */ FILL_FULL_SCREEN (0), /** * 上下左右都居中 */ CENTER_FULL (1), /** * 只保证竖向居中 */ CENTER_VERTICAL (2), /** * 只保证横向居中 */ CENTER_HORIZONTAL(3); GifShowGravity(int i){ nativeInt = i; } final int nativeInt; } /** * 正在显示的GIF图片的一些文件信息 * GifView其实并不真正需要这些信息 * 加在这里是为了DEBUG方便 */ public enum GifImageType{ /** * 从文件显示 */ GIF_TYPE_FROM_FILE (0), /** * 从资源文件显示 */ GIF_TYPE_FROM_RES (1), /** * 从byte字节数据显示 */ GIF_TYPE_FROM_DATA (2), /** * 从byte字节数据显示 */ GIF_TYPE_FROM_INPUTSTREAM (3); GifImageType(int i){ nativeInt = i; } final int nativeInt; } private class GifSourceInfos { private GifImageType mGifType; private String infos; public GifSourceInfos(GifImageType type) { mGifType = type; infos = ""; } public void setInfo(String info) { infos = info; } public String toString() { String res; switch(mGifType) { case GIF_TYPE_FROM_FILE: res = "GIF_TYPE_FROM_FILE: " + infos; break; case GIF_TYPE_FROM_RES: res = "GIF_TYPE_FROM_RES: " + infos; break; case GIF_TYPE_FROM_DATA: res = "GIF_TYPE_FROM_DATA: "; break; case GIF_TYPE_FROM_INPUTSTREAM: res = "GIF_TYPE_FROM_INPUTSTREAM: "; break; default: res = "GIF_TYPE ERROR ";break; } return res; } } public GifView(Context context) { super(context); } public GifView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public GifView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } /** * 设置图片,并开始解码 * @param gif 要设置的图片 * @param justShowCover 是否只显示第一帧,当该参数为true时, * decoder只解析得到一张图片就为停止工作 * */ private void showGifDecoderImage(byte[] gif, boolean justShowCover){ if(gifDecoder != null){ gifDecoder.free(); gifDecoder = null; } mShowCover = justShowCover; gifDecoder = new GifDecoder(gif,this, mShowCover); gifDecoder.start(); } /** * 设置图片,开始解码 * @param is 要设置的图片 * @param justShowCover 是否只显示第一帧,当该参数为true时, * decoder只解析得到一张图片就为停止工作 */ private void showGifDecoderImage(InputStream is, boolean justShowCover){ if(gifDecoder != null){ gifDecoder.free(); gifDecoder= null; } mShowCover = justShowCover; gifDecoder = new GifDecoder(is,this, mShowCover); gifDecoder.start(); } /** * 以字节数据形式设置gif图片 * @param gif 图片 * @param justShowCover 是否只显示第一帧,当该参数为true时, * decoder只解析得到一张图片就为停止工作 */ public void showGifImage(byte[] gif, boolean justShowCover){ // 记录gif图片信息,方便debug mGifSourceInfos = new GifSourceInfos(GifImageType.GIF_TYPE_FROM_DATA); showGifDecoderImage(gif, justShowCover); } /** * 以字节流形式设置gif图片 * @param is 图片 * @param justShowCover 是否只显示第一帧,当该参数为true时, * decoder只解析得到一张图片就为停止工作 */ public void showGifImage(InputStream is, boolean justShowCover){ // 记录gif图片信息,方便debug mGifSourceInfos = new GifSourceInfos(GifImageType.GIF_TYPE_FROM_INPUTSTREAM); showGifDecoderImage(is, justShowCover); } /** * 以资源形式设置gif图片 * @param resId gif图片的资源ID * @param justShowCover 是否只显示第一帧,当该参数为true时, * decoder只解析得到一张图片就为停止工作 */ public void showGifImage(int resId, boolean justShowCover){ // 记录gif图片信息,方便debug mGifSourceInfos = new GifSourceInfos(GifImageType.GIF_TYPE_FROM_RES); mGifSourceInfos.setInfo("resId [" + resId + "]"); Resources r = this.getResources(); InputStream is = r.openRawResource(resId); showGifDecoderImage(is, justShowCover); } /** * 以文件路径形式设置gif图片 * @param path gif图片的文件路径 * @param justShowCover 是否只显示第一帧,当该参数为true时, * decoder只解析得到一张图片就为停止工作 */ public void showGifImage(String path, boolean justShowCover){ // 记录gif图片信息,方便debug mGifSourceInfos = new GifSourceInfos(GifImageType.GIF_TYPE_FROM_FILE); mGifSourceInfos.setInfo("path [" + path + "]"); File f = new File(path); InputStream is; try { is = new FileInputStream(f); } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); return; } showGifDecoderImage(is, justShowCover); } /** * 以字节数据形式设置gif图片 * @param gif 图片 */ public void showGifImage(byte[] gif){ showGifDecoderImage(gif, false); } /** * 以字节流形式设置gif图片 * @param is 图片 */ public void showGifImage(InputStream is){ showGifDecoderImage(is, false); } /** * 以资源形式设置gif图片 * @param resId gif图片的资源ID */ public void showGifImage(int resId){ // 记录gif图片信息,方便debug mGifSourceInfos = new GifSourceInfos(GifImageType.GIF_TYPE_FROM_RES); mGifSourceInfos.setInfo("resId [" + resId + "]"); Resources r = this.getResources(); InputStream is = r.openRawResource(resId); showGifDecoderImage(is, false); } /** * 以文件路径形式设置gif图片 * @param path gif图片的文件路径 */ public void showGifImage(String path){ // 记录gif图片信息,方便debug mGifSourceInfos = new GifSourceInfos(GifImageType.GIF_TYPE_FROM_FILE); mGifSourceInfos.setInfo("path [" + path + "]"); File f = new File(path); InputStream is; try { is = new FileInputStream(f); } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); return; } showGifDecoderImage(is, false); } protected void onDraw(Canvas canvas) { super.onDraw(canvas); if(gifDecoder == null) return; if(currentImage == null){ currentImage = gifDecoder.getImage(); } if(currentImage == null){ return; } int saveCount = canvas.getSaveCount(); canvas.save(); canvas.translate(getPaddingLeft(), getPaddingTop()); calculateDrawRect(); canvas.drawBitmap(currentImage, null, rect, null); canvas.restoreToCount(saveCount); } private void calculateDrawRect() { // 先假设最大化 int viewWidth = getWidth(); int viewHeight = getHeight(); int imageWidth; int imageHeight; switch(mShowGravity) { case FILL_FULL_SCREEN: // 满铺会自动拉伸长宽,会忽略 setShowDimension 的设置 rect.left = 0; rect.top = 0; rect.right = viewWidth; rect.bottom = viewHeight; break; case CENTER_FULL: imageWidth = currentImage.getWidth(); imageHeight = currentImage.getHeight(); //logd("imageWidth = " + imageWidth + ", imageHeight = " + imageHeight); //logd("viewWidth = " + viewWidth + ", viewHeight = " + viewHeight); // 总以其中一个边长全部填充 boolean calculateDone = false; // 先尝试填充满高 int centerH = (int) Math.floor(viewWidth/2); int newHeight = viewHeight; int halfNewWidth = (int) Math.floor(((double)imageWidth*(double)newHeight/(double)imageHeight)/2); if (2*halfNewWidth > viewWidth) { calculateDone = false; } else { rect.left = centerH - halfNewWidth; rect.top = 0; rect.right = centerH + halfNewWidth; rect.bottom = newHeight; calculateDone = true; } // 如果上面的可以使gif图片显示在view内,则不会进行下面的计算 // 否则以宽为填充重新计算 if (!calculateDone) { int centerV = (int) Math.floor(viewHeight/2); int newWidth = viewWidth; int halfNewHeight = (int) Math.floor(((double)imageHeight*(double)newWidth/(double)imageWidth)/2); rect.left = 0; rect.top = centerV - halfNewHeight; rect.right = newWidth; rect.bottom = centerV + halfNewHeight; } //logd("rect[(" + rect.left + ", " + rect.top + ") (" + rect.right + ", " + rect.bottom + ")]"); break; case CENTER_VERTICAL: break; case CENTER_HORIZONTAL: break; default: break; } // user have set the show size if (mShowWidth != -1 && mShowHeight != -1) { } } protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int pleft = getPaddingLeft(); int pright = getPaddingRight(); int ptop = getPaddingTop(); int pbottom = getPaddingBottom(); int widthSize; int heightSize; int w; int h; if(gifDecoder == null){ w = 1; h = 1; }else{ w = gifDecoder.width; h = gifDecoder.height; } w += pleft + pright; h += ptop + pbottom; w = Math.max(w, getSuggestedMinimumWidth()); h = Math.max(h, getSuggestedMinimumHeight()); widthSize = resolveSize(w, widthMeasureSpec); heightSize = resolveSize(h, heightMeasureSpec); setMeasuredDimension(widthSize, heightSize); } /** * 继续显示动画<br> * 本方法在调用showCover后,会让动画继续显示,如果没有调用showCover方法,则没有任何效果 */ public void showAnimation(){ if(pause){ pause = false; } } /** * 设置gif在解码过程中的显示方式<br> * <strong>本方法只能在showGifImage方法之前设置,否则设置无效</strong> * @param type 显示方式 */ public void setGifParsingShowType(GifParsingShowType type){ if(gifDecoder == null) { animationType = type; } } /** * 设置gif的显示方式:居中、满铺等<br> * <strong>本方法只能在showGifImage方法之前设置,否则设置无效</strong> * @param showGravity 显示方式 */ public void setGifShowGravity(GifShowGravity showGravity) { if(gifDecoder == null) { mShowGravity = showGravity; } } /** * 设置要显示的图片的大小<br> * 当设置了图片大小 之后,会按照设置的大小来显示gif(按设置后的大小来进行拉伸或压缩) * @param width 要显示的图片宽 * @param height 要显示的图片高 */ public void setShowDimension(int width,int height){ if(width > 0 && height > 0){ mShowWidth = width; mShowHeight = height; rect.left = 0; rect.top = 0; rect.right = width; rect.bottom = height; } } public void parseOk(boolean parseStatus,int frameIndex){ if(parseStatus){ if(gifDecoder != null){ logd("parseOk, frameIndex = " + frameIndex); switch(animationType){ case WAIT_FINISH: if(frameIndex == -1){ if(gifDecoder.getFrameCount() > 1){ //当帧数大于1时,启动动画线程 if (!mShowCover) { DrawThread dt = new DrawThread(mGifSourceInfos.toString()); dt.start(); } else { reDraw(); } }else{ reDraw(); } } break; case COVER: if(frameIndex == 1) { currentImage = gifDecoder.getImage(); reDraw(); }else if(frameIndex == -1){ if(gifDecoder.getFrameCount() > 1){ if (!mShowCover) { if(drawThread == null){ drawThread = new DrawThread(mGifSourceInfos.toString()); drawThread.start(); } } else { reDraw(); } }else{ reDraw(); } } break; case SYNC_DECODER: if(frameIndex == 1){ currentImage = gifDecoder.getImage(); reDraw(); }else if(frameIndex == -1){ reDraw(); }else{ if (!mShowCover) { if(drawThread == null){ drawThread = new DrawThread(mGifSourceInfos.toString()); drawThread.start(); } } else { reDraw(); } } break; } }else{ Log.e("gif","parse error"); } } } private void reDraw(){ if(redrawHandler != null){ Message msg = redrawHandler.obtainMessage(); redrawHandler.sendMessage(msg); } } private Handler redrawHandler = new Handler(){ public void handleMessage(Message msg) { invalidate(); } }; /** * 动画线程 * @author liao * */ private class DrawThread extends Thread{ private final String mGifInfos; /* 构造 * @param gifinfos 要显示的gif图片的一些文件信息 * 用于调试用的字串,如文件路径等 */ public DrawThread(String gifinfos) { mGifInfos = gifinfos; } public void run(){ if(DEBUG_DRAW_THREAD) { Log.d(TAG, "DrawThread[" + getId() + "]" + " started, " + mGifInfos); } if(gifDecoder == null){ if(DEBUG_DRAW_THREAD) { Log.d(TAG, "DrawThread[" + getId() + "]" + " exit for (gifDecoder == null)"); } return; } while(!mStop){ if(pause == false){ //if(gifDecoder.parseOk()){ GifFrame frame = gifDecoder.next(); currentImage = frame.image; long sp = frame.delay; if(redrawHandler != null){ Message msg = redrawHandler.obtainMessage(); redrawHandler.sendMessage(msg); SystemClock.sleep(sp); }else{ break; } // }else{ // currentImage = gifDecoder.getImage(); // break; // } }else{ SystemClock.sleep(10); } } if(DEBUG_DRAW_THREAD) { Log.d(TAG, "DrawThread[" + getId() + "]" + " exit"); } } } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); setDrawThreadOut(); if (currentImage != null && !currentImage.isRecycled()) { currentImage.recycle(); currentImage = null; } if(gifDecoder != null) { gifDecoder.free(); gifDecoder = null; } } public void setDrawThreadOut() { mStop = true; } private void logd(String message) { if(DEBUG) { Log.d(TAG, message); } } }