【Android实战】记录自学自己定义GifView过程,能同一时候支持gif和其它图片!【有用篇】...
之前寫了一篇博客。《【Android實(shí)戰(zhàn)】記錄自學(xué)自己定義GifView過(guò)程,具體解釋屬性那些事!
【學(xué)習(xí)篇】》
關(guān)于自己定義GifView的,具體解說(shuō)了學(xué)習(xí)過(guò)程及遇到的一些類的解釋,然后完畢了一個(gè)項(xiàng)目,能通過(guò)在xml增加自己定義 view (MyGifView)中增加自己定義屬性(my:gif_src = “@drawable/coffee”)。達(dá)到播放gif圖片的效果。
可是。有幾個(gè)問(wèn)題
1.gif_src 屬性僅僅支持 gif 圖,并不支持其它類型的圖片
2.僅僅支持默認(rèn)的引用圖片,不能另外設(shè)置
問(wèn)題一
gif_src 屬性僅僅支持 gif 圖,并不支持其它類型的圖片。
解決思路:
ImageView本身有個(gè)屬性 src 是定義好的。已經(jīng)能夠用它播放靜態(tài)圖片。假設(shè)再能通過(guò)它播放動(dòng)態(tài)圖片,不就解決這個(gè)問(wèn)題啦?!
于是查看 ImageView 類的源代碼,看到構(gòu)造函數(shù)
public ImageView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {//...final TypedArray a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.ImageView, defStyleAttr, defStyleRes);Drawable d = a.getDrawable(com.android.internal.R.styleable.ImageView_src);if (d != null) {setImageDrawable(d);}//... }有沒(méi)有非常眼熟?!
對(duì),之前自己定義屬性的時(shí)候用過(guò)!這里只是把屬性路徑改了。之前我們用的是自己定義的路徑 R.styleable.GifView 。
于是乎。我也想著,要是能在繼承類(MyGifView)里面復(fù)制上段代碼,然后再用movie轉(zhuǎn)化,轉(zhuǎn)換成功說(shuō)明是 gif。就用之前的方法播放。轉(zhuǎn)換失敗說(shuō)明是其它格式的圖片,就交給 ImageView 自己處理!
真是好辦法!
然而。根本不能這么用:
不能導(dǎo)入internal的包!
可是思路是對(duì)的!
通過(guò)參考《 Android PowerImageView實(shí)現(xiàn)。能夠播放動(dòng)畫的強(qiáng)大ImageView》
得知了能夠用反射!
中心代碼:
* * @param a 屬性組 * @param context * @return 返回布局文件里指定圖片資源所相應(yīng)的id,沒(méi)有指定不論什么圖片資源就返回0。 */
private int getResourceId(TypedArray a, Context context) { try { Field field = TypedArray.class.getDeclaredField("mValue"); field.setAccessible(true); TypedValue typedValueObject = (TypedValue) field.get(a); return typedValueObject.resourceId; } catch (Exception e) { e.printStackTrace(); } finally { if (a != null) { a.recycle(); } } return 0; }之前在自己定義view初始化中的代碼。我是用得到自己定義屬性值的方法獲取gif的數(shù)據(jù)
int resId = typedArray.getResourceId(R.styleable.GifView_gif_src, 0); //gif_src屬性相應(yīng)值如今僅僅須要改這一句就好啦!
//int resId = typedArray.getResourceId(R.styleable.GifView_gif_src, 0); //gif_src屬性相應(yīng)值int resId = getResourceId(typedArray, context); //src屬性相應(yīng)值然后后面都不用改啦!
(可是轉(zhuǎn)換成 InputStream 的時(shí)候。還是要加一句推斷 if (resId != 0)再進(jìn)行轉(zhuǎn)換)
if (resId != 0) {InputStream iStream = getResources().openRawResource(resId); //此方法能通過(guò)資源文件id查找到資源文件并轉(zhuǎn)化為輸入流mMovie = Movie.decodeStream(iStream); //輸入流轉(zhuǎn)化為Movie (mMovie 為全局變量,類型 Movie) }好了問(wèn)題解決。如今能夠在xml用src屬性指向.gif文件,并且進(jìn)行正常播放了!
問(wèn)題二
僅僅支持默認(rèn)的引用圖片,不能另外設(shè)置
解決思路:
從外面設(shè)置無(wú)非就是外面調(diào)用setImageResource(int resId),setImageDrawable(Drawable drawable),setImageBitmap(Bitmap bm)等這些方法去改變 ImageView 屬性 src 所相應(yīng)的值!
那么。重寫這些方法。把資源改成我們的 movie 就好啦。so easy!
首先重寫setImageResource(int resId)
@Override public void setImageResource(int resId) {if (resId != 0) {InputStream iStream = getResources().openRawResource(resId);setMovie(iStream);if (mMovie == null) {super.setImageResource(resId);}} else {super.setImageResource(resId);}invalidate(); } /*** 設(shè)置movie* @param iStream 輸入流*/ public void setMovie(InputStream iStream) {mMovie = Movie.decodeStream(iStream);if (mMovie == null) { //說(shuō)明不是gif。直接退出return;}//設(shè)置圖片寬高Bitmap bitmap = BitmapFactory.decodeStream(iStream);if (bitmap == null) {return;}mWidth = bitmap.getWidth();mHeight = bitmap.getHeight();bitmap.recycle(); }然后在外面(比方MainActivity),調(diào)用gifView.setImageResource(R.drawable.coffee),能夠顯示gif,其它格式的圖片也能夠正常顯示。
but…
出現(xiàn)了一個(gè)bug…
就是如今必須在xml里面的自己定義MyGifView增加默認(rèn)的 src 引用 或者 backgroud 附初始值,不然會(huì)報(bào)錯(cuò)崩潰,假設(shè)不想增加默認(rèn)圖片。能夠把background設(shè)置為透明 #00000000
報(bào)錯(cuò)的原因,大概是沒(méi)設(shè)置src屬性時(shí)。調(diào)用反射int resId = getResourceId(typedArray, context);得到的 resId 也并不為0 (具體得到的是什么我也還不知)。然后進(jìn)入 if 語(yǔ)句執(zhí)行InputStream iStream = getResources().openRawResource(resId);轉(zhuǎn)換流的時(shí)候報(bào)了空指針,導(dǎo)致程序崩潰。
依照設(shè)置默認(rèn)src或者backgroud的方法能夠臨時(shí)解決。假設(shè)廣大網(wǎng)友知道是什么原因,有什么更好的辦法解決它,懇求告知一下!
接著重寫setImageDrawable(Drawable drawable)和setImageBitmap(Bitmap bm)
=-=-=-=-=-=-=-=-= 為了完畢以下的實(shí)現(xiàn),另花費(fèi)了非常久時(shí)間,思緒可能和前面不大連貫了 =-=-=-=-=-=-=-=-=
我本來(lái)以為會(huì)如setImageResource(int resId)一樣順利,但事實(shí)上按原來(lái)的思路并不能夠轉(zhuǎn)換成gif,而是轉(zhuǎn)換成了png/jepg!最后還是借助了三方包才勉強(qiáng)完畢任務(wù)。
接下來(lái)我按順序解說(shuō)下我實(shí)現(xiàn)的過(guò)程。
首先講下原來(lái)的想法以及為什么后來(lái)推翻了。
按著setImageResource(int resId)的實(shí)現(xiàn)思路。setImageDrawable(Drawable drawable)應(yīng)該也就是把 drawable 先轉(zhuǎn)換成 input sream,然后再轉(zhuǎn)換成movie,假設(shè)成功就說(shuō)明是gif,不成功說(shuō)明是其它格式則調(diào)用父類方法。
@Override public void setImageDrawable(Drawable drawable) {if(drawable == null) {super.setImageDraable(drawable);} else {mWidth = drawable.getIntrinsicWidth(); //獲得寬mHeight = drawable.getIntrinsicHeight(); //獲得高InputStream iStream = FomatUtils.Drawable2InputStream(drawable); //通過(guò)工具類將drawable轉(zhuǎn)換成input streamsetMovie(iStream);if (mMovie == null) { //說(shuō)明不是gifsuper.setImageDrawable(drawable);}}invalidate(); }@Override public void setImageBitmap(Bitmap bm) {this.setImageDrawable(new BitmapDrawable(getContext().getResources(), bm)); }這個(gè)時(shí)候我們?cè)贏ctivity增加語(yǔ)句調(diào)用setImageDrawable()方法。在執(zhí)行,出現(xiàn)的是靜態(tài)圖片!
說(shuō)明代碼是有問(wèn)題的,焦點(diǎn)在drawable轉(zhuǎn)換成inputstream的地方
InputStream iStream = FomatUtils.Drawable2InputStream(drawable); //通過(guò)工具類將drawable轉(zhuǎn)換成input stream看下轉(zhuǎn)換類的具體代碼 (參考《Android Bitmap與DrawAble與byte[]與InputStream之間的轉(zhuǎn)換工具類》)
/** * Bitmap與DrawAble與byte[]與InputStream之間的轉(zhuǎn)換工具類 * @author azz */ public class FormatUtils { /*** drawable -> input stream*/public InputStream Drawable2InputStream(Drawable d) { //drawable -> bitmapBitmap bitmap = this.Drawable2Bitmap(d);//bitmap -> input stream return this.Bitmap2InputStream(bitmap); } /*** drawable -> bitmap*/public Bitmap Drawable2Bitmap(Drawable drawable) { Bitmap bitmap = Bitmap .createBitmap( drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), drawable.getOpacity() != PixelFormat.OPAQUE ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565); Canvas canvas = new Canvas(bitmap); drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()); drawable.draw(canvas); return bitmap; } /*** bitmap -> input stream*/public InputStream Bitmap2InputStream(Bitmap bm, int quality) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); bm.compress(Bitmap.CompressFormat.PNG, quality, baos); //就是這里限制了轉(zhuǎn)換成PNG類型InputStream is = new ByteArrayInputStream(baos.toByteArray()); return is; } }注意第三個(gè)轉(zhuǎn)換函數(shù)Bitmap2InputStream()方法內(nèi)的第二句
bm.compress(Bitmap.CompressFormat.PNG, quality, baos);非常明顯這里將Bitmap壓縮為了PNG格式。也就是說(shuō)GIF被壓縮成了PNG!
這個(gè)時(shí)候非常容易想到,把PNG改成GIF不就能夠了嘛!~
可是點(diǎn)擊進(jìn)入 CompressFormat 類后發(fā)現(xiàn),僅僅支持三種格式
public enum CompressFormat {JPEG (0),PNG (1),WEBP (2); //Sdk-14后開(kāi)始支持 }而這三種都不能實(shí)現(xiàn)壓縮成gif格式的流。
這個(gè)時(shí)候我就突發(fā)奇想了,能不能不轉(zhuǎn)換成 input stream!看看Movie還支持其它的什么轉(zhuǎn)換方法么!
結(jié)果是這種:
Movie.decodeByteArray(byte[] data, int offset, int length)
Movie.decodeFile(String pathName)
Movie.decodeStream(InputStream is)
1.decodeByteArray通過(guò) byte[] 轉(zhuǎn)換的話。不管bitmap還是drawable都要經(jīng)過(guò)下一步驟
/*** bitmap -> byte[]*/public byte[] Bitmap2Bytes(Bitmap bm) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); bm.compress(Bitmap.CompressFormat.PNG, 100, baos); return baos.toByteArray(); }能夠看到還是要壓縮。所以這種方法放棄。
2.decodeFile通過(guò)文件轉(zhuǎn)化的方法好像行之有效。并且Image自帶方法setImageURL(Uri uri),那么重寫它看看!
@Override public void setImageURI(Uri uri) {mMovie = Movie.decodeFile(uri.toString());if (mMovie == null) {super.setImageURI(uri);} }懷著激動(dòng)的心情執(zhí)行。
結(jié)果 —— 并沒(méi)有顯示不論什么東西。
—(2015.7.21更新。找到方法破解該問(wèn)題!
可略過(guò)以下一大段直接跳到最后看更新內(nèi)容!)—
希望破滅了~
問(wèn)度娘要安慰~
檢索后。發(fā)現(xiàn)大部分情況都是將GIF轉(zhuǎn)換成了PNG/JEPG就不了了之了,去 stackoverflow 發(fā)現(xiàn)有人問(wèn)卻也沒(méi)有人給出行之有效的辦法,去Github找各種第三方。發(fā)現(xiàn)非常多也僅僅是和我【學(xué)習(xí)篇】實(shí)現(xiàn)一樣,并不能解決實(shí)際問(wèn)題(我如今項(xiàng)目確實(shí)要用到,從sd卡讀取圖片并顯示,支持gif和其它格式圖片)。
搜索了一下午。也有了一些思路,大概例如以下:
方案1.通過(guò)先把 gif 圖片轉(zhuǎn)換為若干幀的 bitmap 保存起來(lái),然后等要使用的時(shí)候,再合成 gif 。并利用線程播放。
(參看:《Android 載入.gif格式圖片》。
這種方法聽(tīng)上去就比較麻煩,看了代碼量又好大我沒(méi)時(shí) (zi) 間 (xi) 看……)
方案2.自己寫一個(gè)壓縮類,實(shí)現(xiàn)壓縮 gif 格式圖片。
(參看:《java圖片壓縮處理 支持gif》和《最終搞定多張JPG圖片轉(zhuǎn)成GIF動(dòng)畫這個(gè)難題,解決方法例如以下。》。
這種方法聽(tīng)上去簡(jiǎn)單好用,可是代碼量好大我沒(méi)仔 (shi) 細(xì) (jian) 看……)
方案3.通過(guò)第三方寫好的拿來(lái)用。依據(jù)需求更改源代碼。
(參看:《android開(kāi)源庫(kù)android-gif-drawable的使用》和“GifView項(xiàng)目源代碼”。這兩個(gè)好像看評(píng)論第一個(gè)更好,用 JNI 攻克了內(nèi)存泄露的問(wèn)題。
可是。我用的是第二個(gè)……第二個(gè)自帶 javadoc。自學(xué)沒(méi)有問(wèn)題。)
—(2015.8.28更新,還是把我逼到用jni的地步,已經(jīng)會(huì)用。不難。比movie效率高!
往后跳過(guò)看。)—
懶人看過(guò)來(lái):
我是個(gè)聰明人。也就是俗稱的懶人~我一定是朝著“怎么能高速解決這個(gè)問(wèn)題”這個(gè)方向走的。我如今攻克了,但并非完美地解決的方法,鼓舞大家都是勤快人。能自己去琢磨~也能夠參考我的做法。
首先下載“GifView項(xiàng)目源代碼”。文件夾結(jié)構(gòu)是這個(gè)樣子的
能夠看到有個(gè)Activity!~那么說(shuō)明能夠直接執(zhí)行,我們看一下。
哇,感覺(jué)好強(qiáng)大的樣子。
可是翻閱Activity的實(shí)現(xiàn)發(fā)現(xiàn),它都是調(diào)用項(xiàng)目res資源的gif,這個(gè)我們已經(jīng)實(shí)現(xiàn)了,看看它還有沒(méi)有其它的設(shè)置圖片方法?
然后我們看看doc,哇好全的樣子(自學(xué)就靠它了!)
在GifView的API中。我們發(fā)現(xiàn)了三個(gè)方法:
經(jīng)試驗(yàn)。setGifImage(String filePath)能夠播放本地(指SD卡)gif,可是也僅僅是支持 .gif 格式的,假設(shè)路徑目標(biāo)是其它格式(比方.png),程序就會(huì)掛掉。
感覺(jué)又回到了原點(diǎn)……忙了一天了。毫無(wú)成果。非常挫敗。
這時(shí)候我懶人思想冒了出來(lái):既然是讀取文件路徑,那就說(shuō)明能夠預(yù)先推斷后綴名是否是gif。假設(shè)是的話就調(diào)用該方法,不是調(diào)用默認(rèn)方法不就能夠了。
另外,我發(fā)現(xiàn)。GifView并沒(méi)有重寫父類的“onDraw()”,”onMeasure()”。“setImageResource(int)”等方法,而我的 MyGifView 剛好寫了,于是結(jié)合一下。用 MyGifView 繼承 GifView!在MyGifView進(jìn)行改動(dòng)!
@Override public void setImageURI(Uri uri) {String path = uri.toString();if (isGif(path)) { //依據(jù)路徑名推斷后綴是否為gifsetGifImage(path); //調(diào)用父類GifView的方法} else {this.pauseGifAnimation(); //暫停之前動(dòng)畫,不然設(shè)置別的圖片的時(shí)候,原來(lái)的gif還在播放動(dòng)畫super.setImageURI(uri); //調(diào)用原始父類ImageView的方法} } //以下兩個(gè)方法能夠?qū)懙焦ぞ哳惱?/span> /*** @Description 推斷是否是gif圖片* @param path 文件路徑* @return true 是gif; false 不是gif*/ public boolean isGif(final String path) {if ("gif".equals(getExtFromFileName)) {return true;}return false; } /*** @Description 獲取文件后綴名* @param fileName 文件名稱或文件路徑* @return 后綴名*/ public String getExtFromFileName(final String fileName) {int dot = fileName.lastIndexOf('.'); //取得最后一個(gè).的位置if (dot != -1) {return fileName.substring(dot + 1, fileName.length());}return ""; }在Activity里面調(diào)用
myGifView.setImageURI(Uri.parse("mnt/sda/sda1/test/coffee.gif"));//myGifView.setImageURI(Uri.parse("mnt/sda/sda1/test/me.png"));這個(gè)時(shí)候執(zhí)行,發(fā)現(xiàn)gif和普通圖片都能正常顯示。可是卡頓非常嚴(yán)重!
用GifView的設(shè)置方法卻不會(huì)。
原因我也找到了,是由于我的MyGifView重寫了setImageDrawable(Drawable drawable)和setImageBitmap(Bitmap bitmap),依據(jù)打印發(fā)現(xiàn)播放的時(shí)候,這兩個(gè)方法頻繁被調(diào)用,可能GifView播放動(dòng)畫的時(shí)候用到了這兩個(gè)方法,反正我如今也用不到這兩個(gè)方法,于是。我就干脆屏蔽掉了。
屏蔽掉之后果然不卡頓了。
最后有人要問(wèn)了,那我的圖片不在本地怎么辦呀?我從網(wǎng)上下下來(lái)的圖片怎么辦呀?
我的懶人思想:
方案1. 你下下來(lái)先保存嘛……
方案2. 假設(shè)你能夠得到byte[]數(shù)據(jù)的話,能夠試試Movie的處理byte[]的辦法,也能夠自己把byte[]轉(zhuǎn)換成InputStream。然后調(diào)用setMovie(InputStream),還能夠試試用GifView處理byte[]的方法。
總而言之,能繞過(guò)bitmap轉(zhuǎn)input stream就好辦!
原來(lái)這篇博客寫了八千字了……該結(jié)貼了。
源代碼地址:https://github.com/Xieyupeng520/MyGifView_V1.1
2015.7.21 更新。解決 Movie.decodeFile 不起作用的問(wèn)題
本來(lái)用上面的第三方攻克了也挺好。可是我發(fā)現(xiàn)幾個(gè)問(wèn)題,第三方的代碼播放幀數(shù)較多的 gif 圖片卡頓非常嚴(yán)重!并且 gif 放大后清晰度也失真嚴(yán)重。
非常幸運(yùn)的是。我在網(wǎng)上找解決的方法的時(shí)候,找到了個(gè)非常easy的方法,還是用Movie。并且用 Movie 通過(guò)流的方式播放 gif 的效果是最好的。和原圖無(wú)差。
這種方法我之前也用過(guò)。就是用 Movie 的 decodeFile 方法,以下是 GifView 里面重寫父類的 setImageURI 方法。
@Override public void setImageURI(Uri uri) {mMovie = Movie.decodeFile(uri.toString());if (mMovie == null) {super.setImageURI(uri);} }這個(gè)之前試過(guò)是不行的。
看我找到的解決的方法。
方法一:
參考了“貼吧五樓”和博客 《android 播放網(wǎng)絡(luò)或本地gif格式的動(dòng)態(tài)圖片》后。得到的解決的方法是:
將FileInputStream轉(zhuǎn)化為btye[]數(shù)組。然后調(diào)用Movie.decodeByteArray(byte[] array,0,array.length); 去完畢。
寫成代碼是這個(gè)樣子:
@Override public void setImageURI(Uri uri) {InputStream is = new FileInputStream(uri.toString());//把 sream 轉(zhuǎn)換成 byte[]byte[] array = streamToBytes(iStream);mMovie = Movie.decodeByteArray(array, 0, array.length);if (mMovie == null) {super.setImageURI(uri);}//設(shè)置圖片寬高Bitmap bitmap = BitmapFactory.decodeByteArray(array, 0, array.length);if (bitmap != null) {mWidth = bitmap.getWidth();mHeight = bitmap.getHeight();bitmap.recycle(); //不須要了,釋放掉} }//把 sream 轉(zhuǎn)換成 byte[] private byte[] streamToBytes(InputStream is) {ByteArrayOutputStream os = new ByteArrayOutputStream(1024); //親測(cè)也可不寫1024byte[] buffer = new byte[1024]; //緩存bufferint len;try {while ((len = is.read(buffer)) >= 0) {os.write(buffer, 0, len); //寫入輸出流}} catch (Exception e) {e.printStackTrace();}return os.toByteArray(); }方法二:
假設(shè)你連方法一都嫌麻煩的話,那方法二真的是代碼少到死!
直接上代碼!
只是有個(gè)問(wèn)題是,Bitmap bitmap = BitmapFactory.decodeStream(iStream);這一句得到的bitmap為null,用方法一不會(huì)出現(xiàn)此問(wèn)題。
參考:《解決Android中Movie導(dǎo)入播放GIF圖片文件異常IOException.reset》
我也真是運(yùn)氣,點(diǎn)進(jìn)去一個(gè)“不相關(guān)”的問(wèn)題,都能找到解決的方法。
所以提醒大家找問(wèn)題的時(shí)候,不要局限于你自己的那幾個(gè)關(guān)鍵字哦!
~
2015.8.28 更新!使用強(qiáng)大的android-gif-drawable開(kāi)源庫(kù),效率比Movie還高!
命運(yùn)多舛,又出現(xiàn)新問(wèn)題了,7.21更新的方法在4.0。4.2,5.0的機(jī)器上測(cè)試都沒(méi)問(wèn)題,卻發(fā)如今Android 4.4出現(xiàn)調(diào)用movie.draw(canvas,0,0)崩潰的情況。我也崩潰了。
于是后來(lái)我用了第三方調(diào)用jni的android-gif-drawable開(kāi)源庫(kù)。之前不用它就是怕麻煩,用過(guò)之后發(fā)現(xiàn)不像想象中那樣復(fù)雜。如今記錄一下使用過(guò)程。
首先打開(kāi)《android開(kāi)源庫(kù)android-gif-drawable的使用》,過(guò)一下前面8點(diǎn),講了怎么樣把 jni 拷到自己項(xiàng)目中。
后面的就是一些 API 了。
簡(jiǎn)單使用,先新建一個(gè)GifDrawable,然后把GifDrawable設(shè)置到 GifImageView / GifImageButton / GifTextView 中。就OK了!
GifImageView gif = (GifImageView) findViewById(R.id.hisGifView);GifDrawable gifFromResource = new GifDrawable( getResources(), R.drawable.run );gif.setImageDrawable(gifFromResource);值得一提的是,文章其中用的是1.0.8版本號(hào),如今盡管已經(jīng)更新到了1.1.9版本號(hào)了。可是我用的仍然是1.0.8的,由于1.1.9的so直接導(dǎo)入執(zhí)行報(bào)錯(cuò),而我還不知道怎樣解決。
1.0.8版本號(hào)缺點(diǎn)是不能在Android 5.0 + 的手機(jī)上正常執(zhí)行。
8.28更新-Demo源代碼下載:https://github.com/Xieyupeng520/MyGifView_V1.3
假設(shè)你有不論什么問(wèn)題。歡迎留言告訴我!
總結(jié)
以上是生活随笔為你收集整理的【Android实战】记录自学自己定义GifView过程,能同一时候支持gif和其它图片!【有用篇】...的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 7.Redis常用命令:ZSet
- 下一篇: C++ multimap 的插入,遍历,