引
最近因?yàn)榘鏅?quán)問題颅湘,要把fresco替換成glide(3.5)第岖。
可是在執(zhí)行crossfade后荤懂,本來正常的默認(rèn)圖(place holder)發(fā)生了拉伸形變吃靠。
Glide.with(context)
.load(url)
.fitCenter()
.placeholder(R.drawable.glide_placeholder)
.crossFade(2000)
.into(imageView);
百思不得其解的畴,于是看了一遍源碼渊抄,找到了原因。
crossFade流程
crassFade使用了一個(gè)工廠類苗傅,如下:
public DrawableRequestBuilder crossFade(int duration) {
????super.animate(new DrawableCrossFadeFactory(duration));
????return this;
}
該工廠類的構(gòu)造類中有個(gè)參數(shù)抒线,參數(shù)使用了一個(gè)默認(rèn)的Animation工廠,如下:
public DrawableCrossFadeFactory(int duration) {
????this(new ViewAnimationFactory(new DefaultAnimationFactory()), duration);
}
默認(rèn)工廠類生成Animation的build方法渣慕,主要是構(gòu)建了一個(gè)AlphaAnimation嘶炭,就是最終呈現(xiàn)出來的淡入淡出效果,如下:
private static class DefaultAnimationFactory implements ViewAnimation.AnimationFactory {
????@Override
? ? public Animation build() {
????????AlphaAnimation animation = new AlphaAnimation(0f, 1f);
? ? ? ? animation.setDuration(DEFAULT_DURATION_MS /2);
? ? ? ? return animation;
? ? }
}
再看下DrawableCrossFadeFactory是如何生成GlideAnimation的逊桦;它使用上面的默認(rèn)工廠構(gòu)造了一個(gè)defaultAnimation眨猎,然后又用了一個(gè)DrawableCrossFadeViewAnimation將defaultAnimation包裝起來生成新的GlideAnimation(這里使用了裝飾器模式),如下:
@Override
public GlideAnimation build(boolean isFromMemoryCache, boolean isFirstResource) {
????if (isFromMemoryCache) {
????????return NoAnimation.get();
? ? }
????if (animation ==null) {
????????GlideAnimation defaultAnimation = animationFactory.build(false, isFirstResource);
? ? ????animation = new DrawableCrossFadeViewAnimation(defaultAnimation, duration);
? ? }
????return animation;
}
繼續(xù)看下DrawableCrossFadeViewAnimation是如何執(zhí)行動(dòng)畫的强经;
它先判斷adapter當(dāng)前有沒有Drawable存在睡陪,如果沒有的話,就使用之前構(gòu)造好的默認(rèn)動(dòng)畫匿情,就是前面提到的包含AlphaAnimation的動(dòng)畫執(zhí)行器兰迫。
如果有的話,就將已經(jīng)存在和當(dāng)前需要?jiǎng)赢嫷膬蓚€(gè)Drawable作為參數(shù)炬称,構(gòu)造出一個(gè)TransitionDrawable汁果,然后將這個(gè)TransitionDrawable設(shè)置為要顯示的Drawable;這里的previous顯然是有的玲躯,因?yàn)槭褂肎lide時(shí)設(shè)置了placeholder据德,這里的previous拿到的就是place holder的Drawable。
代碼如下:
@Override
public boolean animate(T current, ViewAdapter adapter) {
????Drawable previous = adapter.getCurrentDrawable();
? ? if (previous !=null) {
????????TransitionDrawable transitionDrawable = new TransitionDrawable(new Drawable[] { previous, current });
? ? ????transitionDrawable.setCrossFadeEnabled(true);
? ? ? ? transitionDrawable.startTransition(duration);
? ? ? ? adapter.setDrawable(transitionDrawable);
????????return true;
? ? } else {
????????defaultAnimation.animate(current, adapter);
????????return false;
? ? }
}
目前為止沒看出什么問題跷车,我們繼續(xù)看這個(gè)TransitionDrawable棘利,讀讀它的源碼。
TransitionDrawable源碼
源碼地址:
http://androidxref.com/8.0.0_r4/xref/frameworks/base/graphics/java/android/graphics/drawable/
上面提到的兩個(gè)參數(shù)(previous, current)朽缴,最后是以Drawable[]形式構(gòu)造TransitionDrawable的善玫,如下:
它調(diào)用了重載方法,而這個(gè)重載方法只是調(diào)用了父類LayerDrawable的構(gòu)造方法密强。
在LayerDrawable的構(gòu)造方法中蝌焚,傳入的layers參數(shù)裹唆,被循環(huán)遍歷,每個(gè)Drawable元素構(gòu)造出了一個(gè)ChildDrawable對(duì)象只洒,這個(gè)對(duì)象的mDrawable屬性記錄了最開始傳入的Drawable參數(shù)许帐;這些ChildDrawable形成一個(gè)數(shù)組,保存在狀態(tài)變量的mChildren屬性毕谴。
看看DrawableLayer是怎么繪制的成畦,它遍歷上面的ChildDrawable列表循帐,對(duì)每個(gè)Drawable對(duì)象進(jìn)行繪制;這里不對(duì)Drawable設(shè)置區(qū)域范圍瘪匿,所以遇到的默認(rèn)圖形變問題肯定不在這里棋弥。
那么我們繼續(xù)看下DrawableLayer是如何進(jìn)行邊界更新的顽染,如下:
最終調(diào)用到updateLayerBoundsInternal方法中粉寞,如下:
它總體還是對(duì)ChildDrawable列表進(jìn)行了遍歷;對(duì)每個(gè)ChildDrawable的處理,先是獲取到Drawable對(duì)象,然后拿到對(duì)應(yīng)的inset信息,這個(gè)inset信息是Drawable的邊界信息摇锋。(開始嗅到問題的味道了...)
接著先是在重新設(shè)置了臨時(shí)變量container,這是一個(gè)區(qū)域?qū)ο驲ect,設(shè)置的方法是在給定參數(shù)bounds(外部賦予LayerDrawable對(duì)象的區(qū)域)的基礎(chǔ)上县昂,做inset偏移审洞;
然后獲取到d的原始尺寸和記錄的尺寸芒澜,從這些信息中獲取到一個(gè)gravity值;
然后就是最關(guān)鍵的汁针,通過gravity辉词,記錄尺寸信息來計(jì)算出最終的區(qū)域,給Drawable設(shè)定區(qū)域。
這里的幾個(gè)信息點(diǎn): inset,原始尺寸(intrinsicW, intrinsicH)凸丸,記錄尺寸(r.mWidth, r.mHeight)拷邢,gravity。
如果記錄尺寸無效(< 0)甲雅,那么會(huì)使用原始尺寸解孙;通過這個(gè)尺寸和gravity(布局方式),來重新調(diào)整前面計(jì)算過一次的區(qū)域(inset)抛人,最終形成一個(gè)區(qū)域弛姜。
默認(rèn)圖發(fā)生了形變,意味著這個(gè)區(qū)域的尺寸不再是(intrinsicW, intrinsicH)妖枚,按照這段的代碼邏輯廷臼,原因很可能是:記錄尺寸(r.mWidth, r.mHeight)被設(shè)置了,或者gravity不對(duì)绝页,又或者inset影響了荠商。
下面代碼是重構(gòu)gravity的;如果width(這里傳入?yún)?shù)是記錄尺寸)無效续誉,那么gravity會(huì)填充整個(gè)橫向區(qū)域莱没,height則是豎向區(qū)域;這段邏輯好可怕酷鸦,如果設(shè)置的記錄尺寸(和Drawable原始尺寸)有效饰躲,那么就用記錄尺寸,否則就填充整個(gè)視圖臼隔?
繼續(xù)看看記錄尺寸的屬性都有哪些地方修改:構(gòu)造函數(shù)里默認(rèn)無效(-1)嘹裂,別處解析attr時(shí)會(huì)設(shè)置,可是測(cè)試代碼里沒有用設(shè)置該屬性摔握。
還有一處就是對(duì)外接口了:
這個(gè)接口必須API 23以上的才支持寄狼;而且上面看到的調(diào)用流程中,沒有調(diào)用該api的地方氨淌。
到這里為止泊愧,默認(rèn)圖的形變?cè)蚧究梢远ㄕ摿耍?/p>
placeholder在crossfade過程中,和load好的圖片同處于一個(gè)TransitionDrawable里盛正;
它沒有被設(shè)置任何外部尺寸信息删咱,gravity也沒有初始化,所以在計(jì)算尺寸時(shí)gravity被加入了填充信息(FILL_XXX)蛮艰,導(dǎo)致它的區(qū)域是和inset過的區(qū)域一致的腋腮;
而它又沒有被設(shè)置任何inset信息(邊界信息),自然和整個(gè)視圖的尺寸保持了一致壤蚜,當(dāng)它原本小于視圖尺寸的情況下自然而然就被拉伸了即寡。
那么怎么解決這個(gè)問題了?看來我們的救命稻草袜刷,只能著手于inset信息了:
這個(gè)API聪富,不用擔(dān)心像上面提到的setLayerSize,setLayerGravity等新的api問題了。
還有一種方式著蟹,就是把Drawable本身的邊界信息改變墩蔓,也是一樣的效果。
glide官方給出的方案就是這樣的萧豆,我們來看看吧奸披。
官方的解決方案
下面的代碼是一個(gè)ViewAdapter子類PaddingViewAdapter;
它以原有adapter和給定的尺寸為參數(shù)做成一個(gè)包裝類涮雷,類似代理模式阵面;在獲取當(dāng)前Drawable的時(shí)候,它先是把這個(gè)Drawable做了InsetDrawable的包裝洪鸭,這個(gè)包裝對(duì)象的尺寸能將Drawable居中顯示在給定的尺寸中样刷。
import android.graphics.drawable.*;
import android.os.Build.*;
import android.view.View;
import com.bumptech.glide.request.animation.GlideAnimation.ViewAdapter;
class PaddingViewAdapter implements ViewAdapter{
????private final ViewAdapterre alAdapter;
????private final int targetWidth;
????private final int targetHeight;
????public PaddingViewAdapter(ViewAdapter adapter,int targetWidth,int targetHeight) {
????????this.realAdapter = adapter;
????????this.targetWidth = targetWidth;
????????this.targetHeight = targetHeight;
????}
????@Override
????public View getView() {
????????return realAdapter.getView();
????}
????@Override
????public Drawable getCurrentDrawable() {
????????Drawable drawable = realAdapter.getCurrentDrawable();
????????if (drawable != null) ?{
????????????int padX = Math.max(0, targetWidth-drawable.getIntrinsicWidth())/2;
????????????int padY=Math.max(0, targetHeight-drawable.getIntrinsicHeight())/2;
????????????if(padX>0||padY>0) {
????????????????drawable=new InsetDrawable(drawable, padX, padY, padX, padY);
????????????}
????????}
? ? ? ?return drawable;
????}
????@Override
????public void setDrawable(Drawable drawable) {
????????if(VERSION.SDK_INT>=VERSION_CODES.M && drawable instanceof TransitionDrawable) {
????????????//For some reason padding is taken into account differently on M than before in LayerDrawable
????????????//PaddingMode was introduced in 21 and gravity in 23, I think NO_GRAVITY default may play
????????????//a role in this, but didn't have time to dig deeper than this.
????????????((TransitionDrawable)drawable).setPaddingMode(TransitionDrawable.PADDING_MODE_STACK);
????????}
????????realAdapter.setDrawable(drawable);
????}
}
下面的代碼是一個(gè)GlideAnimation子類PaddingAnimation,也是一個(gè)代理類览爵;
它在執(zhí)行動(dòng)畫的時(shí)候置鼻,首先拿到了當(dāng)前要做動(dòng)畫對(duì)象的尺寸,然后使用上面的代理類PaddingViewAdapter蜓竹,針對(duì)這個(gè)尺寸對(duì)Drawable做預(yù)處理箕母;
我們回憶一下最初看到的crossFade流程,是不是就是通過adapter.getCurrentDrawable()拿到previous的梅肤?那么placeholder通過這個(gè)代理類司蔬,就被預(yù)先處理成了帶正確inset的Drawable,這樣就不會(huì)形變了姨蝴。
import android.graphics.drawable.Drawable;
import com.bumptech.glide.request.animation.GlideAnimation;
class PaddingAnimation implements GlideAnimation {
????private final GlideAnimation realAnimation;
????public PaddingAnimation(GlideAnimation animation) {
????????this.realAnimation=animation;
????}
????@Override
????public boolean animate(T current, final View Adapteradapter) {
????????int width = current.getIntrinsicWidth();
????????int height = current.getIntrinsicHeight();
????????return realAnimation.animate(current, newPaddingViewAdapter(adapter, width, height));
????}
}
下面的代碼是更改后的代碼俊啼,使用后默認(rèn)圖不再發(fā)生形變了;
這里只是把into(imageView)左医,更改為into(new GlideDrawableImageViewTarget(imageView)授帕,同時(shí)在onResourceReady的重寫中使用了代理類PaddingAnimation。
Glide.with(context)
.load(url)
.fitCenter()
.placeholder(R.drawable.glide_placeholder)
.crossFade(2000)
.into(new GlideDrawableImageViewTarget(imageView) {
????@Override
????public void onResourceReady(GlideDrawable resource, GlideAnimation animation) { ????????super.onResourceReady(resource, new PaddingAnimation<>(animation));
????}
})
參考
問題討論:
https://stackoverflow.com/questions/32235413/glide-load-drawable-but-dont-scale-placeholder
官方補(bǔ)丁代碼: