前言
在 Android 開發(fā)中队魏,如果我們不確定圖片的寬高,又想讓 ImageView
以固定的寬度或高度顯示万搔,且圖片寬高比保持不變胡桨,我們很容易想到 adjustViewBounds
這個(gè)屬性,配合固定的 ImageView
寬度或高度瞬雹,即可實(shí)現(xiàn)寬/高固定昧谊,另一邊自適應(yīng)。
起因
有一個(gè)即時(shí)通信產(chǎn)品酗捌,Emoji 表情通過服務(wù)端接口下發(fā)資源揽浙,支持動(dòng)態(tài)增刪,同時(shí)本地有一份兜底數(shù)據(jù)意敛,用于網(wǎng)絡(luò)不可用時(shí)的兜底展示馅巷。有一天產(chǎn)品在后臺(tái)新增了幾個(gè)表情,測(cè)試發(fā)現(xiàn)個(gè)別手機(jī)上新上的表情顯示比較小草姻,而本地兜底的表情則顯示正常钓猬。
這里的表情面板使用 RecyclerView
實(shí)現(xiàn),Emoji Item 使用 RelativeLayout
作為容器撩独,Emoji 圖片使用 ImageView
固定高度敞曹,通過 adjustViewBounds
使寬度根據(jù)圖片寬高比自適應(yīng)展示。
復(fù)現(xiàn)
排查過程比較曲折综膀,這里直接嘗試復(fù)現(xiàn)澳迫。
新建一個(gè)布局文件,加入一個(gè) ImageView
剧劝,高度固定150dp橄登,大于圖片高度,設(shè)置 adjustViewBounds
為 true
嗯讥此,沒什么問題拢锹,圖片被等比例拉伸,ImageView
的寬度也自適應(yīng)了萄喳,符合對(duì) adjustViewBounds
的預(yù)期卒稳。
接下來,在 ImageView
外層嵌套一層 RelativeLayout
WTF! 意想不到的事情發(fā)生了他巨,圖片沒有按照預(yù)期的寬高比進(jìn)行拉伸充坑,僅僅展示了原本的尺寸减江,就好像 adjustViewBounds
從來都不存在一樣!
原因
先看一下 ImageView
的測(cè)量方法捻爷,為了方便閱讀辈灼,代碼做了簡(jiǎn)化。
ImageView
的測(cè)量并不復(fù)雜役衡,大致可以分為以下幾步:
- 判斷是否設(shè)置
adjustViewBounds
茵休,如果設(shè)置,繼續(xù)往下手蝎,否則使用常規(guī)測(cè)量方法(即使用Drawable
寬高榕莺、最小寬高和background
寬高的最大值) - 根據(jù)測(cè)量模式判斷是否可以調(diào)整寬度或高度,如果可以棵介,繼續(xù)往下钉鸯,否則使用常規(guī)測(cè)量方法
- 取得
Drawable
寬高和 View 最大寬高的最小值,得到實(shí)際寬高比邮辽,判斷實(shí)際寬高比和Drawable
是否相同唠雕,如果相同,則使用當(dāng)前寬高吨述,測(cè)量結(jié)束岩睁,否則繼續(xù) - 根據(jù)
Drawable
寬高比調(diào)整實(shí)際寬高,使用調(diào)整后的寬高作為測(cè)量結(jié)果揣云,測(cè)量結(jié)束
并沒有看出什么端倪捕儒,看來問題可能不在 ImageView
這里,那就只剩 RelativeLayout
這個(gè)“嫌疑人”了邓夕。
繼續(xù)查看 RelativeLayout
的測(cè)量方法刘莹。
RelativeLayout
的測(cè)量方法就復(fù)雜多了,這里摘出關(guān)鍵代碼
可以看出焚刚,RelativeLayout
會(huì)對(duì)子 View 測(cè)量?jī)纱蔚阃洌谝淮螠y(cè)量水平方向,確定子 View 的寬度矿咕,第二次抢肛,使用上次測(cè)量的寬度,再次測(cè)量痴腌,得到子 View 實(shí)際的尺寸雌团。
這里沒看出什么問題,繼續(xù)看 measureChildHorizontal
方法
真相終于浮出眼前士聪!
注意看14-18行,在確定高度測(cè)量模式時(shí)猛蔽,如果高度是 MATCH_PARENT
剥悟,則使用 EXACTLY
模式灵寺,否則使用 AT_MOST
模式。
由于我們 ImageView
是固定高度区岗,即固定值略板,因此會(huì)使用 AT_MOST
模式進(jìn)行測(cè)量,而寬度也是
AT_MOST
慈缔,回憶一下上面 ImageView
的測(cè)量方法叮称,如果寬高都是 AT_MOST
,則實(shí)際大小和 Drawable
一致藐鹤,即第一次測(cè)量的寬度是 Drawable
的實(shí)際寬度瓤檐。
那高度怎么不是 Drawable
的高度呢?因?yàn)?RelativeLayout
第一次測(cè)量只是確定了 ImageView
的寬度娱节,第二次又根據(jù) ImageView
的實(shí)際高度進(jìn)行測(cè)量挠蛉,因此便出現(xiàn)了上圖的情況,即 adjustViewBounds
失效了肄满。
既然 RelativeLayout
是先測(cè)量寬度谴古,那我把寬度固定,高度改為 WRAP_CONTENT
稠歉,是不是就沒問題了掰担,沒錯(cuò),我們修改代碼測(cè)試一下
到這里怒炸,我們基本理清了 RelativeLayout
的測(cè)量方式對(duì) ImageView
的 adjustViewBounds
屬性的影響带饱,最后我們回到業(yè)務(wù)上,為什么只有新上的表情(通過接口獲群崦摹)顯示偏小纠炮,而內(nèi)置圖片確能正常顯示呢?
原因是內(nèi)置圖片的尺寸放在對(duì)應(yīng) dpi 目錄下灯蝴,會(huì)根據(jù)設(shè)備 dpi 進(jìn)行縮放恢口,因此即使 adjustViewBounds
失效,也能拉伸到對(duì)應(yīng)尺寸穷躁,因此顯示正常耕肩,而通過接口下載的圖片,density
默認(rèn)和設(shè)備一致问潭,因此在 density
比較高的設(shè)備上猿诸,無法自動(dòng)拉伸到預(yù)期的尺寸。
解決
弄清楚了問題的原因就很容易解決了狡忙,這里 Emoji 容器中只有一個(gè) ImageView
保持居中梳虽,不依賴 RelativeLayout
的特性,因此直接替換為 FrameLayout
即可灾茁。
總結(jié)
本文主要通過一個(gè)業(yè)務(wù) bug窜觉,通過源碼解讀谷炸,發(fā)現(xiàn) ImageView
與 RelativeLayout
組合使用,在特定場(chǎng)景下 adjustViewBounds
屬性會(huì)“失效”的問題禀挫,最終通過替換為 FrameLayout
來解決旬陡。
不清楚這個(gè)問題是 Google 是有意為之,還是一個(gè) Bug语婴,目前官方文檔上暫時(shí)未發(fā)現(xiàn)有相關(guān)說明描孟,有了解的大佬可以幫小弟解解惑。