前言
之前兩篇文章介紹了如何讓ImageSpan中的drawable如何去刷新TextView,那么如何創(chuàng)建一個(gè)gif的drawable呢?這篇文章主要介紹利用 android-gif-drawable和glide產(chǎn)生GifDrawable的方法
android-gif-drawable
android-gif-drawable
使用jni方法去渲染gif圖片,效率不錯(cuò),使用也很簡(jiǎn)單,不過不支持遠(yuǎn)程圖片的加載
使用
GifDrawable
類的構(gòu)造方法可以接受File,Uri,InputStream,byte[],ResourcesId...等多種類型的參數(shù),直接new就可以創(chuàng)建一個(gè)可以動(dòng)的Drawable
利用GifDrawable實(shí)現(xiàn)TextView支持gif
GifDrawable gifDrawable = new GifDrawable(getResources(), R.drawable.a);
Spannable spannable = new SpannableString("[笑臉]");
ImageSpan imageSpan = new EqualHeightSpan(gifDrawable);
spannable.setSpan(imageSpan, 0, spannable.length(),Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
//利用之前介紹的靜態(tài)方法使spEditText中的gif動(dòng)起來
GifTextUtil.setText(spEditText,spannable);
- 這里傳入了一個(gè)資源的id,當(dāng)然它是一張gif圖片
-
EqualHeightSpan
是自己寫的一個(gè)讓圖片保持和文字等高的ImageSpan
- 最后使用
GifTextUtil.setText
讓spEditText
的gif動(dòng)起來
遇到的坑
按道理上面幾行代碼就可以做到讓gif動(dòng)起來了,結(jié)果卻讓我大失所望,spEditText
的invalidate()
確實(shí)被調(diào)用了,但是圖片卻不能正常刷新
折騰兩個(gè)晚上,逐漸將問題鎖定在android-gif-drawable
身上,果然最終在https://github.com/koral--/android-gif-drawable/issues/368找到了解決方案
- 對(duì)于
EditText
,需要setLayerType(View.LAYER_TYPE_SOFTWARE, null)
才能正常刷新 -
TextView
不需要特殊處理
談?wù)勔粋€(gè)奇怪的現(xiàn)象
之前文章中提到過GifTextUtil.setText
只能讓一個(gè)drawable刷新一個(gè)TextView
為了定位上面的問題,自己寫了一個(gè)TextView和EditText
- 使用同一個(gè)GifDrawable
- 都沒有
setLayerType(View.LAYER_TYPE_SOFTWARE, null)
現(xiàn)象是
- 當(dāng)刷新的是EditText時(shí)gif顯示不正常 ,這個(gè)好理解
- 但是當(dāng)刷新的是TextView時(shí)竟然兩個(gè)View中的gif都顯示的是正常的
也就是說在使用硬件加速的情況下只要有一個(gè)TextView中GifDrawable刷新了,會(huì)連帶著其他的TextView中的相同GifDrawable一起顯示新的圖像
-
GifTextUtil.setText()
方法在使用GifDrawable
的情況下其實(shí)是可以復(fù)用的,因?yàn)橹灰粋€(gè)刷新了其他的就會(huì)一起刷新 - 雖然貌似可行但是并不建議
GifTextUtil.setText()
用在drawable復(fù)用的環(huán)境,因?yàn)檫@里面的原理我是沒想通,不能保證所有設(shè)備都正常
Glide
使用android-gif-drawable
可以支持大部分本地gif資源的加載,但是在項(xiàng)目中往往會(huì)使用遠(yuǎn)程資源,這里使用Glide
去解決
Glide
是Google推薦使用的圖片加載庫,功能強(qiáng)大,API簡(jiǎn)單而且擴(kuò)展性還強(qiáng),強(qiáng)烈推薦大家使用,當(dāng)然如果是要做圖文混排的話Glide用起來稍微麻煩了一點(diǎn)
關(guān)鍵
- Glide加載圖片是個(gè)異步的過程,而且placeHolder和真正的圖片會(huì)加載兩次
- 文本的設(shè)置應(yīng)該是個(gè)同步過程,先取到drawable,生成ImageSpan再產(chǎn)生一個(gè)Spannable,最后設(shè)置到TextView中
- 所以利用Glide實(shí)現(xiàn)圖文混排的關(guān)鍵就在于如何把Glide的異步API轉(zhuǎn)換成可以同步產(chǎn)生drawable
代碼分析
主要代碼
GlidePreDrawable preDrawable = new GlidePreDrawable();
GlideApp.with(this)
.load("http://5b0988e595225.cdn.sohucs.com/images/20170919/1ce5d4c52c24432e9304ef942b764d37.gif")
.placeholder(gifDrawable)
.into(new DrawableTarget(preDrawable));
-
GlidePreDrawable
持有真正的圖片的mDrawable
,并代理mDrawable
的相關(guān)方法,相當(dāng)于先扔了一個(gè)占位置的到TextView中,等正主到了再刷新一下,操作GlidePreDrawable
就相當(dāng)于操作mDrawable
,以此完成同步獲得drawable的任務(wù) -
DrawableTarget
用來更新GlidePreDrawable中包裹的真正的drawable - 拿到一個(gè)drawable,后面的代碼就和上一節(jié)的方法一樣了,不再贅述
下面主要看下GlidePreDrawable
和DrawableTarget
干了啥
DrawableTarget
這個(gè)比較簡(jiǎn)單,無非是在placeHolder加載完,圖片加載出錯(cuò),圖片加載完等情況下去更新preDrawable
中的mDrawable
Glide里面也有個(gè)類叫GifDrawable
,完全限定名是com.bumptech.glide.load.resource.gif.GifDrawable
android-gif-drawable
的是pl.droidsonroids.gif.GifDrawable
注意區(qū)分
public class DrawableTarget extends SimpleTarget<Drawable> {
private static final String TAG = "GifTarget";
private GlidePreDrawable preDrawable;
@Override
public void onResourceReady(@NonNull Drawable resource,
@Nullable Transition<? super Drawable> transition) {
Log.i(TAG, "onResourceReady: " + resource);
preDrawable.setDrawable(resource);
if (resource instanceof GifDrawable) {
((GifDrawable) resource).setLoopCount(GifDrawable.LOOP_FOREVER);
((GifDrawable) resource).start();
}
preDrawable.invalidateSelf();
}
public DrawableTarget(PreDrawable preDrawable) {
this.preDrawable = preDrawable;
}
@Override
public void onLoadCleared(@Nullable Drawable placeholder) {
Log.i(TAG, "onLoadCleared: " + placeholder);
if (preDrawable.getDrawable() != null) {
return;
}
preDrawable.setDrawable(placeholder);
if (placeholder instanceof GifDrawable) {
((GifDrawable) placeholder).setLoopCount(GifDrawable.LOOP_FOREVER);
((GifDrawable) placeholder).start();
}
preDrawable.invalidateSelf();
}
@Override
public void onLoadStarted(@Nullable Drawable placeholder) {
Log.i(TAG, "onLoadCleared: " + placeholder);
preDrawable.setDrawable(placeholder);
if (placeholder instanceof GifDrawable) {
((GifDrawable) placeholder).setLoopCount(GifDrawable.LOOP_FOREVER);
((GifDrawable) placeholder).start();
}
preDrawable.invalidateSelf();
}
@Override
public void onLoadFailed(@Nullable Drawable errorDrawable) {
Log.i(TAG, "onLoadFailed: " + errorDrawable);
preDrawable.setDrawable(errorDrawable);
if (errorDrawable instanceof GifDrawable) {
((GifDrawable) errorDrawable).setLoopCount(GifDrawable.LOOP_FOREVER);
((GifDrawable) errorDrawable).start();
}
preDrawable.invalidateSelf();
}
}
GlidePreDrawable
說GlidePreDrawable
之前先說下Measurable
這個(gè)接口,這個(gè)接口主要是為了EqualHeightSpan
中確定drawable占據(jù)的空間,并且避免重復(fù)調(diào)用
public interface Measurable {
int getWidth();//獲取真正的drawable的寬度
int getHeight();//獲取真正的drawable的高度
boolean canMeasure();//當(dāng)可以獲取到寬高是返回true,否則返回false
boolean needResize();//只有當(dāng)drawable被設(shè)置時(shí)needResize返回true, onBoundsChange之后設(shè)為false,保證設(shè)置drawable邊界的方法不被多次調(diào)用
}
public class GlidePreDrawable extends Drawable implements Drawable.Callback, Measurable {
private static final String TAG = "PreDrawable";
private Drawable mDrawable;
private boolean needResize;
@Override
public void draw(Canvas canvas) {
if (mDrawable != null) {
mDrawable.draw(canvas);
}
}
@Override
public void setAlpha(int alpha) {
if (mDrawable != null) {
mDrawable.setAlpha(alpha);
}
}
@Override
public void setColorFilter(ColorFilter cf) {
if (mDrawable != null) {
mDrawable.setColorFilter(cf);
}
}
@Override
public int getOpacity() {
if (mDrawable != null) {
return mDrawable.getOpacity();
}
return PixelFormat.UNKNOWN;
}
public void setDrawable(Drawable drawable) {
if (this.mDrawable != null) {
this.mDrawable.setCallback(null);
}
drawable.setCallback(this);
this.mDrawable = drawable;
needResize = true;
if (getCallback() != null) {
getCallback().invalidateDrawable(this);
}
}
@Override
protected void onBoundsChange(Rect bounds) {
super.onBoundsChange(bounds);
needResize = false;
}
@Override
public void invalidateDrawable(Drawable who) {
if (getCallback() != null) {
getCallback().invalidateDrawable(this);
}
}
@Override
public void scheduleDrawable(Drawable who, Runnable what, long when) {
if (getCallback() != null) {
getCallback().scheduleDrawable(who, what, when);
}
}
@Override
public void unscheduleDrawable(Drawable who, Runnable what) {
if (getCallback() != null) {
getCallback().unscheduleDrawable(who, what);
}
}
@Override
public void setBounds(@NonNull Rect bounds) {
super.setBounds(bounds);
if (mDrawable != null) {
mDrawable.setBounds(bounds);
}
}
@Override
public void setBounds(int left, int top, int right, int bottom) {
super.setBounds(left, top, right, bottom);
if (mDrawable != null) {
mDrawable.setBounds(left, top, right, bottom);
}
}
@Override
public int getWidth() {
if (mDrawable != null) {
return mDrawable.getIntrinsicWidth();
}
return 0;
}
@Override
public int getHeight() {
if (mDrawable != null) {
return mDrawable.getIntrinsicHeight();
}
return 0;
}
@Override
public boolean canMeasure() {
return mDrawable != null;
}
@Override
public boolean needResize() {
return mDrawable != null && needResize;
}
public Drawable getDrawable() {
return mDrawable;
}
}
-
GlidePreDrawable
實(shí)現(xiàn)Drawable.Callback
接口,持有mDrawable
,將自己作為mDrawable
的Callback,使得自己可以監(jiān)聽到mDrawable
的刷新 -
draw
方法直接調(diào)用mDrawable
,其他方法也一樣,就是mDrawable
的一個(gè)代理方法
總結(jié)
- 使用android-gif-drawable加載本地gif,注意對(duì)EditText設(shè)置
View.LAYER_TYPE_SOFTWARE
- 使用Glide實(shí)現(xiàn)圖文混排,需要先創(chuàng)建一個(gè)真正的drawable的代理使異步API能夠同步產(chǎn)生drawable設(shè)置給TextView
完整代碼
項(xiàng)目地址https://github.com/sunhapper/SpEditTool
因?yàn)椴幌朐趲熘幸雽?duì)Glide
和android-gif-drawable
的依賴,所以本文相關(guān)內(nèi)容都在demo module中
歡迎star,提PR、issue
索引
一行代碼讓TextView中ImageSpan支持Gif(一)
第一篇給出解決方案并分析整體思路
一行代碼讓TextView中ImageSpan支持Gif(二)
第二篇對(duì)實(shí)現(xiàn)中的細(xì)節(jié)和踩過的坑進(jìn)行說明
一行代碼讓TextView中ImageSpan支持Gif(三)
第三篇介紹如何使用android-gif-drawable和Glide實(shí)現(xiàn)遠(yuǎn)程gif圖片在TextView中的圖文混排
一行代碼讓TextView中ImageSpan支持Gif(四)
第四篇介紹在RecyclerView等需要drawable復(fù)用的場(chǎng)景下的gif動(dòng)圖顯示