詳解glide中crossfade引發(fā)的默認(rèn)圖變形


最近因?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ǔ)丁代碼:

https://github.com/TWiStErRob/glide-support/tree/master/src/glide3/java/com/bumptech/glide/supportapp/stackoverflow/_32235413_crossfade_placeholder

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末浮梢,一起剝皮案震驚了整個(gè)濱河市跛十,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌秕硝,老刑警劉巖芥映,帶你破解...
    沈念sama閱讀 207,248評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡奈偏,警方通過查閱死者的電腦和手機(jī)坞嘀,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來惊来,“玉大人丽涩,你說我怎么就攤上這事〔靡希” “怎么了矢渊?”我有些...
    開封第一講書人閱讀 153,443評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)枉证。 經(jīng)常有香客問我矮男,道長(zhǎng),這世上最難降的妖魔是什么室谚? 我笑而不...
    開封第一講書人閱讀 55,475評(píng)論 1 279
  • 正文 為了忘掉前任昂灵,我火速辦了婚禮,結(jié)果婚禮上舞萄,老公的妹妹穿的比我還像新娘眨补。我一直安慰自己,他們只是感情好倒脓,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,458評(píng)論 5 374
  • 文/花漫 我一把揭開白布撑螺。 她就那樣靜靜地躺著,像睡著了一般崎弃。 火紅的嫁衣襯著肌膚如雪甘晤。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,185評(píng)論 1 284
  • 那天饲做,我揣著相機(jī)與錄音线婚,去河邊找鬼。 笑死盆均,一個(gè)胖子當(dāng)著我的面吹牛塞弊,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播泪姨,決...
    沈念sama閱讀 38,451評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼游沿,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了肮砾?” 一聲冷哼從身側(cè)響起诀黍,我...
    開封第一講書人閱讀 37,112評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎仗处,沒想到半個(gè)月后眯勾,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體枣宫,經(jīng)...
    沈念sama閱讀 43,609評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,083評(píng)論 2 325
  • 正文 我和宋清朗相戀三年吃环,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了镶柱。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,163評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡模叙,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出鞋屈,到底是詐尸還是另有隱情范咨,我是刑警寧澤,帶...
    沈念sama閱讀 33,803評(píng)論 4 323
  • 正文 年R本政府宣布厂庇,位于F島的核電站渠啊,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏权旷。R本人自食惡果不足惜替蛉,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,357評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望拄氯。 院中可真熱鬧躲查,春花似錦、人聲如沸译柏。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)鄙麦。三九已至典唇,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間胯府,已是汗流浹背介衔。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評(píng)論 1 261
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留骂因,地道東北人炎咖。 一個(gè)月前我還...
    沈念sama閱讀 45,636評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像寒波,于是被迫代替她去往敵國(guó)和親塘装。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,925評(píng)論 2 344

推薦閱讀更多精彩內(nèi)容