日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > Android >内容正文

Android

【Android实战】记录自学自己定义GifView过程,能同一时候支持gif和其它图片!【有用篇】...

發(fā)布時間:2025/6/15 Android 29 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【Android实战】记录自学自己定义GifView过程,能同一时候支持gif和其它图片!【有用篇】... 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

之前寫了一篇博客。《【Android實戰(zhàn)】記錄自學自己定義GifView過程,具體解釋屬性那些事!

【學習篇】》
關(guān)于自己定義GifView的,具體解說了學習過程及遇到的一些類的解釋,然后完畢了一個項目,能通過在xml增加自己定義 view (MyGifView)中增加自己定義屬性(my:gif_src = “@drawable/coffee”)。達到播放gif圖片的效果。


可是。有幾個問題

1.gif_src 屬性僅僅支持 gif 圖,并不支持其它類型的圖片

2.僅僅支持默認的引用圖片,不能另外設置


問題一

gif_src 屬性僅僅支持 gif 圖,并不支持其它類型的圖片。

解決思路:
ImageView本身有個屬性 src 是定義好的。已經(jīng)能夠用它播放靜態(tài)圖片。假設再能通過它播放動態(tài)圖片,不就解決這個問題啦?!

于是查看 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);}//... }

有沒有非常眼熟?!

對,之前自己定義屬性的時候用過!這里只是把屬性路徑改了。之前我們用的是自己定義的路徑 R.styleable.GifView 。

于是乎。我也想著,要是能在繼承類(MyGifView)里面復制上段代碼,然后再用movie轉(zhuǎn)化,轉(zhuǎn)換成功說明是 gif。就用之前的方法播放。轉(zhuǎn)換失敗說明是其它格式的圖片,就交給 ImageView 自己處理!

真是好辦法!

然而。根本不能這么用:

不能導入internal的包!

可是思路是對的!

通過參考《 Android PowerImageView實現(xiàn)。能夠播放動畫的強大ImageView》

得知了能夠用反射!
中心代碼:

/** * 通過Java反射,獲取到src指定圖片資源所相應的id。

* * @param a 屬性組 * @param context * @return 返回布局文件里指定圖片資源所相應的id,沒有指定不論什么圖片資源就返回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屬性相應值

如今僅僅須要改這一句就好啦!

//int resId = typedArray.getResourceId(R.styleable.GifView_gif_src, 0); //gif_src屬性相應值int resId = getResourceId(typedArray, context); //src屬性相應值

然后后面都不用改啦!

(可是轉(zhuǎn)換成 InputStream 的時候。還是要加一句推斷 if (resId != 0)再進行轉(zhuǎn)換)

if (resId != 0) {InputStream iStream = getResources().openRawResource(resId); //此方法能通過資源文件id查找到資源文件并轉(zhuǎn)化為輸入流mMovie = Movie.decodeStream(iStream); //輸入流轉(zhuǎn)化為Movie (mMovie 為全局變量,類型 Movie) }

好了問題解決。如今能夠在xml用src屬性指向.gif文件,并且進行正常播放了!

問題二

僅僅支持默認的引用圖片,不能另外設置

解決思路:
從外面設置無非就是外面調(diào)用setImageResource(int resId),setImageDrawable(Drawable drawable),setImageBitmap(Bitmap bm)等這些方法去改變 ImageView 屬性 src 所相應的值!

那么。重寫這些方法。把資源改成我們的 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(); } /*** 設置movie* @param iStream 輸入流*/ public void setMovie(InputStream iStream) {mMovie = Movie.decodeStream(iStream);if (mMovie == null) { //說明不是gif。直接退出return;}//設置圖片寬高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)了一個bug…

就是如今必須在xml里面的自己定義MyGifView增加默認的 src 引用 或者 backgroud 附初始值,不然會報錯崩潰,假設不想增加默認圖片。能夠把background設置為透明 #00000000

報錯的原因,大概是沒設置src屬性時。調(diào)用反射int resId = getResourceId(typedArray, context);得到的 resId 也并不為0 (具體得到的是什么我也還不知)。然后進入 if 語句執(zhí)行InputStream iStream = getResources().openRawResource(resId);轉(zhuǎn)換流的時候報了空指針,導致程序崩潰。

依照設置默認src或者backgroud的方法能夠臨時解決。假設廣大網(wǎng)友知道是什么原因,有什么更好的辦法解決它,懇求告知一下!

接著重寫setImageDrawable(Drawable drawable)和setImageBitmap(Bitmap bm)

=-=-=-=-=-=-=-=-= 為了完畢以下的實現(xiàn),另花費了非常久時間,思緒可能和前面不大連貫了 =-=-=-=-=-=-=-=-=

我本來以為會如setImageResource(int resId)一樣順利,但事實上按原來的思路并不能夠轉(zhuǎn)換成gif,而是轉(zhuǎn)換成了png/jepg!最后還是借助了三方包才勉強完畢任務。

接下來我按順序解說下我實現(xiàn)的過程。

首先講下原來的想法以及為什么后來推翻了。

按著setImageResource(int resId)的實現(xiàn)思路。setImageDrawable(Drawable drawable)應該也就是把 drawable 先轉(zhuǎn)換成 input sream,然后再轉(zhuǎn)換成movie,假設成功就說明是gif,不成功說明是其它格式則調(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); //通過工具類將drawable轉(zhuǎn)換成input streamsetMovie(iStream);if (mMovie == null) { //說明不是gifsuper.setImageDrawable(drawable);}}invalidate(); }@Override public void setImageBitmap(Bitmap bm) {this.setImageDrawable(new BitmapDrawable(getContext().getResources(), bm)); }

這個時候我們在Activity增加語句調(diào)用setImageDrawable()方法。在執(zhí)行,出現(xiàn)的是靜態(tài)圖片!

說明代碼是有問題的,焦點在drawable轉(zhuǎn)換成inputstream的地方

InputStream iStream = FomatUtils.Drawable2InputStream(drawable); //通過工具類將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; } }

注意第三個轉(zhuǎn)換函數(shù)Bitmap2InputStream()方法內(nèi)的第二句

bm.compress(Bitmap.CompressFormat.PNG, quality, baos);

非常明顯這里將Bitmap壓縮為了PNG格式。也就是說GIF被壓縮成了PNG!

這個時候非常容易想到,把PNG改成GIF不就能夠了嘛!~

可是點擊進入 CompressFormat 類后發(fā)現(xiàn),僅僅支持三種格式

public enum CompressFormat {JPEG (0),PNG (1),WEBP (2); //Sdk-14后開始支持 }

而這三種都不能實現(xiàn)壓縮成gif格式的流。

這個時候我就突發(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通過 byte[] 轉(zhuǎn)換的話。不管bitmap還是drawable都要經(jīng)過下一步驟

/*** bitmap -> byte[]*/public byte[] Bitmap2Bytes(Bitmap bm) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); bm.compress(Bitmap.CompressFormat.PNG, 100, baos); return baos.toByteArray(); }

能夠看到還是要壓縮。所以這種方法放棄。

2.decodeFile通過文件轉(zhuǎn)化的方法好像行之有效。并且Image自帶方法setImageURL(Uri uri),那么重寫它看看!

@Override public void setImageURI(Uri uri) {mMovie = Movie.decodeFile(uri.toString());if (mMovie == null) {super.setImageURI(uri);} }

懷著激動的心情執(zhí)行。

結(jié)果 —— 并沒有顯示不論什么東西。

—(2015.7.21更新。找到方法破解該問題!

可略過以下一大段直接跳到最后看更新內(nèi)容!)—


希望破滅了~

問度娘要安慰~

檢索后。發(fā)現(xiàn)大部分情況都是將GIF轉(zhuǎn)換成了PNG/JEPG就不了了之了,去 stackoverflow 發(fā)現(xiàn)有人問卻也沒有人給出行之有效的辦法,去Github找各種第三方。發(fā)現(xiàn)非常多也僅僅是和我【學習篇】實現(xiàn)一樣,并不能解決實際問題(我如今項目確實要用到,從sd卡讀取圖片并顯示,支持gif和其它格式圖片)。

搜索了一下午。也有了一些思路,大概例如以下:

方案1.通過先把 gif 圖片轉(zhuǎn)換為若干幀的 bitmap 保存起來,然后等要使用的時候,再合成 gif 。并利用線程播放。
(參看:《Android 載入.gif格式圖片》。

這種方法聽上去就比較麻煩,看了代碼量又好大我沒時 (zi) 間 (xi) 看……)

方案2.自己寫一個壓縮類,實現(xiàn)壓縮 gif 格式圖片。


(參看:《java圖片壓縮處理 支持gif》和《最終搞定多張JPG圖片轉(zhuǎn)成GIF動畫這個難題,解決方法例如以下。》。

這種方法聽上去簡單好用,可是代碼量好大我沒仔 (shi) 細 (jian) 看……)

方案3.通過第三方寫好的拿來用。依據(jù)需求更改源代碼。


(參看:《android開源庫android-gif-drawable的使用》和“GifView項目源代碼”。這兩個好像看評論第一個更好,用 JNI 攻克了內(nèi)存泄露的問題。

可是。我用的是第二個……第二個自帶 javadoc。自學沒有問題。)

—(2015.8.28更新,還是把我逼到用jni的地步,已經(jīng)會用。不難。比movie效率高!

往后跳過看。)—

懶人看過來:

我是個聰明人。也就是俗稱的懶人~我一定是朝著“怎么能高速解決這個問題”這個方向走的。我如今攻克了,但并非完美地解決的方法,鼓舞大家都是勤快人。能自己去琢磨~也能夠參考我的做法。

首先下載“GifView項目源代碼”。文件夾結(jié)構(gòu)是這個樣子的

能夠看到有個Activity!~那么說明能夠直接執(zhí)行,我們看一下。

哇,感覺好強大的樣子。

可是翻閱Activity的實現(xiàn)發(fā)現(xiàn),它都是調(diào)用項目res資源的gif,這個我們已經(jīng)實現(xiàn)了,看看它還有沒有其它的設置圖片方法?

然后我們看看doc,哇好全的樣子(自學就靠它了!)

在GifView的API中。我們發(fā)現(xiàn)了三個方法:

經(jīng)試驗。setGifImage(String filePath)能夠播放本地(指SD卡)gif,可是也僅僅是支持 .gif 格式的,假設路徑目標是其它格式(比方.png),程序就會掛掉。

感覺又回到了原點……忙了一天了。毫無成果。非常挫敗。

這時候我懶人思想冒了出來:既然是讀取文件路徑,那就說明能夠預先推斷后綴名是否是gif。假設是的話就調(diào)用該方法,不是調(diào)用默認方法不就能夠了。

另外,我發(fā)現(xiàn)。GifView并沒有重寫父類的“onDraw()”,”onMeasure()”。“setImageResource(int)”等方法,而我的 MyGifView 剛好寫了,于是結(jié)合一下。用 MyGifView 繼承 GifView!在MyGifView進行改動!

@Override public void setImageURI(Uri uri) {String path = uri.toString();if (isGif(path)) { //依據(jù)路徑名推斷后綴是否為gifsetGifImage(path); //調(diào)用父類GifView的方法} else {this.pauseGifAnimation(); //暫停之前動畫,不然設置別的圖片的時候,原來的gif還在播放動畫super.setImageURI(uri); //調(diào)用原始父類ImageView的方法} } //以下兩個方法能夠?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('.'); //取得最后一個.的位置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"));

這個時候執(zhí)行,發(fā)現(xiàn)gif和普通圖片都能正常顯示。可是卡頓非常嚴重!

用GifView的設置方法卻不會。

原因我也找到了,是由于我的MyGifView重寫了setImageDrawable(Drawable drawable)和setImageBitmap(Bitmap bitmap),依據(jù)打印發(fā)現(xiàn)播放的時候,這兩個方法頻繁被調(diào)用,可能GifView播放動畫的時候用到了這兩個方法,反正我如今也用不到這兩個方法,于是。我就干脆屏蔽掉了。

屏蔽掉之后果然不卡頓了。

最后有人要問了,那我的圖片不在本地怎么辦呀?我從網(wǎng)上下下來的圖片怎么辦呀?

我的懶人思想:
方案1. 你下下來先保存嘛……
方案2. 假設你能夠得到byte[]數(shù)據(jù)的話,能夠試試Movie的處理byte[]的辦法,也能夠自己把byte[]轉(zhuǎn)換成InputStream。然后調(diào)用setMovie(InputStream),還能夠試試用GifView處理byte[]的方法。

總而言之,能繞過bitmap轉(zhuǎn)input stream就好辦!

原來這篇博客寫了八千字了……該結(jié)貼了。


源代碼地址:https://github.com/Xieyupeng520/MyGifView_V1.1


2015.7.21 更新。解決 Movie.decodeFile 不起作用的問題

本來用上面的第三方攻克了也挺好。可是我發(fā)現(xiàn)幾個問題,第三方的代碼播放幀數(shù)較多的 gif 圖片卡頓非常嚴重!并且 gif 放大后清晰度也失真嚴重。

非常幸運的是。我在網(wǎng)上找解決的方法的時候,找到了個非常easy的方法,還是用Movie。并且用 Movie 通過流的方式播放 gif 的效果是最好的。和原圖無差。

這種方法我之前也用過。就是用 Movie 的 decodeFile 方法,以下是 GifView 里面重寫父類的 setImageURI 方法。

@Override public void setImageURI(Uri uri) {mMovie = Movie.decodeFile(uri.toString());if (mMovie == null) {super.setImageURI(uri);} }

這個之前試過是不行的。

看我找到的解決的方法。

方法一:

參考了“貼吧五樓”和博客 《android 播放網(wǎng)絡或本地gif格式的動態(tài)圖片》后。得到的解決的方法是:

將FileInputStream轉(zhuǎn)化為btye[]數(shù)組。然后調(diào)用Movie.decodeByteArray(byte[] array,0,array.length); 去完畢。

寫成代碼是這個樣子:

@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);}//設置圖片寬高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); //親測也可不寫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(); }

方法二:

假設你連方法一都嫌麻煩的話,那方法二真的是代碼少到死!


直接上代碼!

@Override public void setImageURI(Uri uri) {//就以下兩句是新加的,其它都是原來的InputStream is = new BufferedInputStream(new FileInputStream(uri.toString()));is.mark(16 * 1024);//調(diào)用之前封裝好的setMovie(InputStream is)方法setMovie(is);if (mMovie == null) {super.setImageURI(uri);} } /*** 設置movie* @param iStream 輸入流*/ public void setMovie(InputStream iStream) {mMovie = Movie.decodeStream(iStream);if (mMovie == null) { //說明不是gif。直接退出return;}//設置圖片寬高Bitmap bitmap = BitmapFactory.decodeStream(iStream);if (bitmap == null) {return;}mWidth = bitmap.getWidth();mHeight = bitmap.getHeight();bitmap.recycle(); }

只是有個問題是,Bitmap bitmap = BitmapFactory.decodeStream(iStream);這一句得到的bitmap為null,用方法一不會出現(xiàn)此問題。

參考:《解決Android中Movie導入播放GIF圖片文件異常IOException.reset》

我也真是運氣,點進去一個“不相關(guān)”的問題,都能找到解決的方法。

所以提醒大家找問題的時候,不要局限于你自己的那幾個關(guān)鍵字哦!

~


2015.8.28 更新!使用強大的android-gif-drawable開源庫,效率比Movie還高!

命運多舛,又出現(xiàn)新問題了,7.21更新的方法在4.0。4.2,5.0的機器上測試都沒問題,卻發(fā)如今Android 4.4出現(xiàn)調(diào)用movie.draw(canvas,0,0)崩潰的情況。我也崩潰了。

于是后來我用了第三方調(diào)用jni的android-gif-drawable開源庫。之前不用它就是怕麻煩,用過之后發(fā)現(xiàn)不像想象中那樣復雜。如今記錄一下使用過程。

首先打開《android開源庫android-gif-drawable的使用》,過一下前面8點,講了怎么樣把 jni 拷到自己項目中。

后面的就是一些 API 了。

簡單使用,先新建一個GifDrawable,然后把GifDrawable設置到 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版本號,如今盡管已經(jīng)更新到了1.1.9版本號了。可是我用的仍然是1.0.8的,由于1.1.9的so直接導入執(zhí)行報錯,而我還不知道怎樣解決。

1.0.8版本號缺點是不能在Android 5.0 + 的手機上正常執(zhí)行。


8.28更新-Demo源代碼下載:https://github.com/Xieyupeng520/MyGifView_V1.3



假設你有不論什么問題。歡迎留言告訴我!

總結(jié)

以上是生活随笔為你收集整理的【Android实战】记录自学自己定义GifView过程,能同一时候支持gif和其它图片!【有用篇】...的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。