1. 程式人生 > >解讀ImageView的wrap_content和adjustViewBounds的工作原理

解讀ImageView的wrap_content和adjustViewBounds的工作原理

ImageView是android開發過程中經常會使用的一種元件,由於android螢幕碎片化的問題,有時候我們無法設定一個具體的寬高。比如說width是match_parent的,這時候我們還想讓圖片在寬度完全填充並能正常顯示,我們直接會想到將height設定為wrap_content。但是用過的同學都知道ImageView的實際區域要大於圖片區域,如圖:


可以看到在圖片的上下留了很大的空白空間,有人可能想設定fitXY來解決這個問題。結果是ImageView區域未變,但是圖片變形了,如圖:


上面這種情況出現在圖片實際尺寸要大於螢幕尺寸(或為ImageView設定的尺寸)。那如果圖片比較小,情況會改善麼?

我們同樣觀察設定fitXY前後的情況,圖片如下:

可以看到當圖片比較小的時候,會左右留出空白,而設定fitXY後則ImageView區域依然未改變,所以圖片變形了。

1、wrap_content

那麼設定了wrap_content的ImageView的區域為什麼無法貼合圖片內容的大小呢?
我們知道View的onMeasure函式是計算一個view的大小的,那麼讓我們來看看ImageView的onMeasure函式,程式碼如下:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    resolveUri();
    int w;
    int h;


    // Desired aspect ratio of the view's contents (not including padding)
    float desiredAspect = 0.0f;


    // We are allowed to change the view's width
    boolean resizeWidth = false;


    // We are allowed to change the view's height
    boolean resizeHeight = false;


    final int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
    final int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);


    if (mDrawable == null) {
        // If no drawable, its intrinsic size is 0.
        mDrawableWidth = -1;
        mDrawableHeight = -1;
        w = h = 0;
    } else {
        w = mDrawableWidth;
        h = mDrawableHeight;
        if (w <= 0) w = 1;
        if (h <= 0) h = 1;


        // We are supposed to adjust view bounds to match the aspect
        // ratio of our drawable. See if that is possible.
        if (mAdjustViewBounds) {
            resizeWidth = widthSpecMode != MeasureSpec.EXACTLY;
            resizeHeight = heightSpecMode != MeasureSpec.EXACTLY;


            desiredAspect = (float) w / (float) h;
        }
    }


    final int pleft = mPaddingLeft;
    final int pright = mPaddingRight;
    final int ptop = mPaddingTop;
    final int pbottom = mPaddingBottom;


    int widthSize;
    int heightSize;


    if (resizeWidth || resizeHeight) {


        // Get the max possible width given our constraints
        widthSize = resolveAdjustedSize(w + pleft + pright, mMaxWidth, widthMeasureSpec);


        // Get the max possible height given our constraints
        heightSize = resolveAdjustedSize(h + ptop + pbottom, mMaxHeight, heightMeasureSpec);


        if (desiredAspect != 0.0f) {
            // See what our actual aspect ratio is
            final float actualAspect = (float)(widthSize - pleft - pright) /
                                    (heightSize - ptop - pbottom);


            if (Math.abs(actualAspect - desiredAspect) > 0.0000001) {


                boolean done = false;


                // Try adjusting width to be proportional to height
                if (resizeWidth) {
                    int newWidth = (int)(desiredAspect * (heightSize - ptop - pbottom)) +
                            pleft + pright;


                    // Allow the width to outgrow its original estimate if height is fixed.
                    if (!resizeHeight && !sCompatAdjustViewBounds) {
                        widthSize = resolveAdjustedSize(newWidth, mMaxWidth, widthMeasureSpec);
                    }


                    if (newWidth <= widthSize) {
                        widthSize = newWidth;
                        done = true;
                    }
                }


                // Try adjusting height to be proportional to width
                if (!done && resizeHeight) {
                    int newHeight = (int)((widthSize - pleft - pright) / desiredAspect) +
                            ptop + pbottom;


                    // Allow the height to outgrow its original estimate if width is fixed.
                    if (!resizeWidth && !sCompatAdjustViewBounds) {
                        heightSize = resolveAdjustedSize(newHeight, mMaxHeight,
                                heightMeasureSpec);
                    }


                    if (newHeight <= heightSize) {
                        heightSize = newHeight;
                    }
                }
            }
        }
    } else {
        ...


        widthSize = resolveSizeAndState(w, widthMeasureSpec, 0);
        heightSize = resolveSizeAndState(h, heightMeasureSpec, 0);
    }


    setMeasuredDimension(widthSize, heightSize);
}

我們一步步來看,首先看這個函式一開始呼叫了resolveUri這個函式,這個函式程式碼如下:
private void resolveUri() {
    ...
    if (mResource != 0) {
        try {
            d = mContext.getDrawable(mResource);
        } catch (Exception e) {
            ...
        }
    } else if (mUri != null) {
        d = getDrawableFromUri(mUri);
        ...
    } else {
        return;
    }


    updateDrawable(d);
}


private void updateDrawable(Drawable d) {
    ...
    if (d != null) {
        ...
        mDrawableWidth = d.getIntrinsicWidth();
        mDrawableHeight = d.getIntrinsicHeight();
        ...
    } else {
        mDrawableWidth = mDrawableHeight = -1;
    }
}

通過上面程式碼可以看到resolveUri函式會先得到drawable物件(從resource或uri中),然後通過updateDrawable將
mDrawableWidth和mDrawableHeight這兩個變數設定為drawable的寬高。

我們回到onMeasure函式繼續往下看,第一個if-else,因為我們討論的是有圖片的情況,所以mDrawable一定不為null,那麼走進了else語句,
將剛才的mDrawableWidth和mDrawableHeight兩個變數的值賦給了w和h這兩個區域性變數。

同時這裡如果mAdjustViewBounds為ture,則改變resizeWidth和resizeHeight。而他們的預設值是false。這裡我們先討論mAdjustViewBounds為false的情況。

再繼續,第二個if-else,判斷是否resize,由於mAdjustViewBounds為false,所以resizeWidth和resizeHeight都為false,走進else語句塊。
在else中則用resolveSizeAndState這個函式來計算寬高,程式碼如下:
public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) {
    final int specMode = MeasureSpec.getMode(measureSpec);
    final int specSize = MeasureSpec.getSize(measureSpec);
    final int result;
    switch (specMode) {
        case MeasureSpec.AT_MOST:
            if (specSize < size) {
                result = specSize | MEASURED_STATE_TOO_SMALL;
            } else {
                result = size;
            }
            break;
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
        case MeasureSpec.UNSPECIFIED:
        default:
            result = size;
    }
    return result | (childMeasuredState & MEASURED_STATE_MASK);
}

首先從measureSpec中獲取mode和size。

這裡簡單解釋一下measureSpec,它是一個int值,前兩位(32和31位)儲存標誌位,即specMode;後面則儲存著size即限制大小。而measureSpec是父View傳給子View的,也就是說父View會根據自身的情況限制子View的大小。
這裡還涉及到specMode的三種模式:
①UNSPECIFIED:父View沒有對子View施加任何約束。它可以是任何它想要的大小。 
②EXACTLY:父View已經確定了子View的確切尺寸。子View將被限制在給定的界限內,而忽略其本身的大小。 
③AT_MOST:子View的大小不能超過指定的大小
這部分也值得一說,以後專門開一篇新文章來仔細講講。


基於上面的解釋,我們回頭在來看resolveAdjustedSize的程式碼就比較好理解了。
在從measureSpec中的到了mode和size後,根據mode不同會有不同的處理。
這裡我們有一個隱藏的前提暴露出來了,就是ImageView不能超出它的父view的顯示區域。即mode只能為EXACTLY或AT_MOST。因為view的寬度是match_parent,所以mode是EXACTLY,直接是父view的寬度,我們就不再考慮了。
那麼重點來看高度,因為是wrap_content,所以mode應該是AT_MOST,則最終的高度是desiredSize、specSize和maxSize的最小值,desiredSize是前面獲取的圖片的高度,specSize是父view限制的大小。而最終高度則取他們兩個的最小值。

這樣我們就有一個結論,在我們設定的前提下,ImageView的寬度是父View的限制寬度,而高度是圖片高度與父View限制高度的較小值。兩者並無關聯,所以並不會按照圖片的比例計算自己的寬高。所以在這種情況下,wrap_content無法達到讓ImageView按圖片的比例顯示,這樣就會出現文章開頭的情況。


2、adjustViewBounds

上面我們發現為ImageView設定了wrap_content無法達到效果。但是ImageView還有一個adjustViewBounds引數,當設定了這個引數,如下:
android:adjustViewBounds="true"
ImageView就可以按照圖片的比例來顯示了。

這是怎麼實現的?
接下來我們回過頭看看之前的mAdjustViewBounds,我們上面討論的是它為false的情況。當我們設定了adjustViewBounds,它就為ture了,這時就執行if語句中的程式碼:
resizeWidth = widthSpecMode != MeasureSpec.EXACTLY;
resizeHeight = heightSpecMode != MeasureSpec.EXACTLY;
desiredAspect = (float) w / (float) h;
當ImageView的寬高沒有都是設定為固定值或match_parent時,resizeWidth和resizeHeight一定有一個為ture。
而desiredAspect則是寬高比。

繼續看onMeasure中接下來的程式碼,在第二個if-else時,由於resizeWidth和resizeHeight一定有一個為ture,所以走進if語句塊。
首先呼叫了resolveAdjustedSize這個函式來計算寬高。我們先來看看resolveAdjustedSize的程式碼:
private int resolveAdjustedSize(int desiredSize, int maxSize,
                               int measureSpec) {
    int result = desiredSize;
    final int specMode = MeasureSpec.getMode(measureSpec);
    final int specSize =  MeasureSpec.getSize(measureSpec);
    switch (specMode) {
        case MeasureSpec.UNSPECIFIED:
            /* Parent says we can be as big as we want. Just don't be larger
               than max size imposed on ourselves.
            */
            result = Math.min(desiredSize, maxSize);
            break;
        case MeasureSpec.AT_MOST:
            // Parent says we can be as big as we want, up to specSize.
            // Don't be larger than specSize, and don't be larger than
            // the max size imposed on ourselves.
            result = Math.min(Math.min(desiredSize, specSize), maxSize);
            break;
        case MeasureSpec.EXACTLY:
            // No choice. Do what we are told.
            result = specSize;
            break;
    }
    return result;
}

與上面resolveSizeAndState方法很類似,當mode為AT_MOST時,
result = Math.min(Math.min(desiredSize, specSize), maxSize);
是取圖片size、specSize和maxSize的最小值。

到目前為止沒什麼不同,下面才是重點,繼續看onMeasure下面的程式碼:
final float actualAspect = (float)(widthSize - pleft - pright) /
                        (heightSize - ptop - pbottom);
if (Math.abs(actualAspect - desiredAspect) > 0.0000001) {
    boolean done = false;


    // Try adjusting width to be proportional to height
    if (resizeWidth) {
        int newWidth = (int)(desiredAspect * (heightSize - ptop - pbottom)) +
                pleft + pright;


        // Allow the width to outgrow its original estimate if height is fixed.
        if (!resizeHeight && !sCompatAdjustViewBounds) {
            widthSize = resolveAdjustedSize(newWidth, mMaxWidth, widthMeasureSpec);
        }


        if (newWidth <= widthSize) {
            widthSize = newWidth;
            done = true;
        }
    }


    // Try adjusting height to be proportional to width
    if (!done && resizeHeight) {
        int newHeight = (int)((widthSize - pleft - pright) / desiredAspect) +
                ptop + pbottom;


        // Allow the height to outgrow its original estimate if width is fixed.
        if (!resizeWidth && !sCompatAdjustViewBounds) {
            heightSize = resolveAdjustedSize(newHeight, mMaxHeight,
                    heightMeasureSpec);
        }


        if (newHeight <= heightSize) {
            heightSize = newHeight;
        }
    }
}

當計算後的寬高比與圖片寬高比不同時,會根據之前resizeWidth和resizeHeight,用固定的那個值和圖片寬高比取計算另外一個值。
這樣ImageView的寬高比例就完全符合了圖片的實際寬高比,不會出現文章前面的留白的情況了。


3、其他元件

本文討論的是ImageView保持固定的寬高比,那麼其他元件也可以麼?

相關推薦

(轉)計算機原理學習(1)-- 馮諾依曼體系CPU工作原理

原文:https://blog.csdn.net/cc_net/article/details/10419645 對於我們80後來說,最早接觸計算機應該是在95年左右,那個時候最流行的一個詞語是多媒體。 依舊記得當時在同學家看同學輸入幾個DOS命令就成功的打開了一個遊戲,當時實在是佩服的五體投地。因為對我來

乾貨!!c++newdelete工作原理 以及 針對連結串列節點過載operator new operator delete 實現連結串列節點使用記憶體池申請釋放空間

第一部分: new和delete的實現原理 開始談之前我們應該瞭解另一個概念“operator new”和“operator delete”: new操作符呼叫一個函式來完畢必需的記憶體分配,你可以重寫或過載這個函式來改變它的行為。new操

HTTP SessionCookie工作原理

session的工作原理 術語session在我的經驗裡,session這個詞被濫用的程度大概僅次於transaction,更加有趣的是transaction與session在某些語境下的含義是相同的。 session,中文經常翻譯為會話,其本來的含義是指有始有終的一系列動作/訊息,比如打電話時從拿起電話撥號

記憶體模型(堆工作原理,String詳解)

   JVM主要管理兩種型別記憶體:堆和非堆。 1.堆是執行時資料區域,所有類例項和陣列的記憶體均從此處分配,這些物件通過new、newarray、 anewarray和multianewarray等

Servlet、Filter Listener 工作原理

Servlet工作原理 一、Servlet生命週期分為三個階段:      1、初始化階段,呼叫init()方法      2、響應客戶請求階段,呼叫service()方法      3、終止階段,呼叫destroy()方法 二、Servlet初始化階段,在下列時刻Servlet容器裝載Servlet:

計算機原理學習(1)-- 馮諾依曼體系CPU工作原理

前言 對於我們80後來說,最早接觸計算機應該是在95年左右,那個時候最流行的一個詞語是多媒體。 依舊記得當時在同學家看同學輸入幾個DOS命令就成功的打開了一個遊戲,當時實在是佩服的五體投地。因為對我來說,螢幕上的東西簡直就是天書。有了計算機我們生活發生了巨大的變化,打遊

HSRPVRRP工作原理

2.1.2 HSRP工作原理    多數IP主機有一個以單一路由器作為預設閘道器的IP地址。當使用HSRP時,IP主機的預設閘道器將以HSRP組的虛擬IP地址替代具體物理路由器介面的IP地址。HSRP通過為網路中的主機提供冗餘的IP通訊路由來實現網路的高可用性。1.     HSRP組中路由器的兩種角色   

路由交換機工作原理

打包 否則 溢出 流量 .網絡 限制 入口 repeater 連接服務器 路由器與交換機的工作原理   計算機網絡往往由許多種不同類型的網絡互連連接而成。如果幾個計算機網絡只是在物理上連接在一起,它們之間並不能進行通信,那麽這種“互連&rdquo

「MoreThanJava」一文了解二進位制CPU工作原理

![](https://imgkr.cn-bj.ufileos.com/def6144e-d6a2-4f06-9d6c-7f2c0acf6cc9.png) - **「MoreThanJava」** 宣揚的是 **「學習,不止 CODE」**,本系列 Java 基礎教程是自己在結合各方面的知識之後,對 Jav

解讀ImageView的wrap_contentadjustViewBounds工作原理

ImageView是android開發過程中經常會使用的一種元件,由於android螢幕碎片化的問題,有時候我們無法設定一個具體的寬高。比如說width是match_parent的,這時候我們還想讓圖片在寬度完全填充並能正常顯示,我們直接會想到將height設定為wrap_content。但是用過的同學都知道

Android 基於Netty的消息推送方案之概念工作原理(二)

img b2c 決定 watermark net nios 通道 感覺 art 上一篇文章中我講述了關於消息推送的方案以及一個基於Netty實現的一個簡單的Hello World。為了更好的理解Hello World中的代碼,今天我來解說一下關於Netty中一些概念和工

springMVC 的工作原理機制、配置

spring mvc+my batis kafka dubbo+zookeerper restful redis分布式緩存 工作原理下面的是springMVC的工作原理圖:1、客戶端發出一個http請求給web服務器,web服務器對http請求進行解析,如果匹配DispatcherServle

深入解析瀏覽器的幕後工作原理(三) 呈現樹 DOM 樹的關系

文本 一行 出現 src 格式 關於 放置 顯示 關系 呈現樹和 DOM 樹的關系   呈現器是和 DOM 元素相對應的,但並非一一對應。非可視化的 DOM 元素不會插入呈現樹中,例如“head”元素。如果元素的 display 屬性值為“none”,那麽也不會顯示在呈現

lvskeeplived的工作原理詳解

lvs+keeplived的工作原理一、lvs的工作原理 使用集群的技術和liunx的操作系統實現一個高性能、高可用的服務器。可伸縮性、可靠性、很好的管理性。 特點:可伸縮網絡服務的幾種結構,它們都需要一個前端的負載調度器(或者多個進行主從備份)。我們先分析實現虛擬網絡服務的主要技術,指出IP負載均衡技術

Struts2工作原理執行流程圖

過濾器 map filters play servle 同時 cati 通過 spa 在struts2的應用中,從用戶請求到服務器返回相應響應給用戶端的過程中,包含了許多組件如:Controller、ActionProxy、ActionMapping、Configurati

(轉)Java 詳解 JVM 工作原理流程

移植 獲得 代碼 適配 調用 tac 階段 main方法 等待 作為一名Java使用者,掌握JVM的體系結構也是必須的。說起Java,人們首先想到的是Java編程語言,然而事實上,Java是一種技術,它由四方面組成:Java編程語言、Java類文件格式、Java虛擬機和Ja

Servlet生命周期工作原理

所有 equal web容器 protoc xml文件 body ror 動態網頁 servlet容器 Servlet生命周期分為三個階段:   1,初始化階段 調用init()方法   2,響應客戶請求階段  調用service()方法   3,終止階段  調用dest

CSS布局模型 之 浮動模型(浮動的工作原理清除浮動技巧?)

浮動 浮動模型 工作原理 浮動的工作原理浮動是讓某元素脫離文檔流,在浮動框之前和之後的非定位元素會當它不存在一樣,可能沿著它的另一側垂直流動,但都為其騰出空間,塊級元素也不例外(被浮動元素占據了部分行空間的塊級元素,仍然被看作是占據了一整行,只不過是被浮動元素占據的那部分空間無法利用罷了)。浮動的

strust2的核心工作原理

如何工作 java語言 creates 幫助 multipart bject null -a throws 在學習strust2之前,我們要明白使用struts2的目的是什麽?它能給我們帶來什麽樣的好處? 設計目標   Strust設計的第一目標就是使MVC模式應用於we

負載均衡器部署方式工作原理

硬件負載均衡 f5設備概述負載均衡(Load Balance)由於目前現有網絡的各個核心部分隨著業務量的提高,訪問量和數據流量的快速增長,其處理能力和計算強度也相應地增大,使得單一的服務器設備根本無法承擔。在此情況下,如果扔掉現有設備去做大量的硬件升級,這樣將造成現有資源的浪費,而且如果再面臨下一次業務量的提