源碼分析Android 中ImageView的設置src與background繪制流程

本文原文發表于vcmo博客 by Jie

眾所周知ImageView可以通過src和Background兩種方式設置顯示資源。和大家一起通過源碼來了解兩種屬性的繪制流程有何不同。

熱身案例

先上兩段小代碼:

    <ImageView
        android:layout_width="200dp"
        android:layout_height="300dp"
        android:background="@mipmap/ic_launcher"/>
    <ImageView
        android:layout_width="200dp"
        android:layout_height="300dp"
        android:src="@mipmap/ic_launcher"/>


21-49-56_363701.jpg

android:background="@mipmap/ic_launcher"



21-33-45.jpg

android:src="@mipmap/ic_launcher"


例子中,可以看到ImageView通過設置Background會將圖片資源拉伸至控件的寬高。通過src設置并不會有這樣的現象。這是為什么呢?下面就同大家一起在源碼中找到答案。
ImageView 是View的直接子類,我們先從ImageView的源碼入手。

src與background兩種屬性設置的方法入手

setImageResource()方法入手src屬性

在ImageView的源碼中我們很容易能找到設置ImageResource的方法:


21-49-56.jpg

方法很短

  1. 首先將兩個全局變量的值賦給了局部變量。
  2. 調用了updateDrawable(null),更新Drawable,并將null作為參數。對,是用來初始化的。
  3. 將我們傳入的resId賦值給全局變量mResource
  4. 調用resolveUri();名稱的意思是解析資源
  5. 根據新的寬高是否等于舊的值,判斷是否請求重新計算布局。
  6. invalidate(),重繪

通過上面方法,找到了resolveUri()方法,看看里面做了什么。

    private void resolveUri() {
    
    ···
    
        Drawable d = null;

        if (mResource != 0) {
            try {
                d = mContext.getDrawable(mResource);
            } catch (Exception e) {
                Log.w("ImageView", "Unable to find resource: " + mResource, e);
                // Don't try again.
                mUri = null;
            }
        }
        ···
        updateDrawable(d);
}

代碼不短,我們只關注相關的部分。可以看到在這個方法里面

  • 通過mContext.getDrawable(mResource)獲取到了mResource解析來的Drawable對象實例。
  • mResource就是上面方法第三步賦值我們傳入的resId來的。
  • 拿到Drawable對象d,在方法最后通過updateDrawable(d)方法更新Drawable資源。(沒錯這個方法在上面也用到了,傳入的參數是null初始化)。

接著查看updateDrawable()方法:

    private void updateDrawable(Drawable d) {
        ···
        mDrawable = d;

        if (d != null) {
            d.setCallback(this);
            d.setLayoutDirection(getLayoutDirection());
            if (d.isStateful()) {
                d.setState(getDrawableState());
            }
            d.setVisible(getVisibility() == VISIBLE, true);
            d.setLevel(mLevel);
            mDrawableWidth = d.getIntrinsicWidth();
            mDrawableHeight = d.getIntrinsicHeight();
            applyImageTint();
            applyColorMod();

            configureBounds();
        }
}

同樣我們看重點:

  1. 將d賦值全局變量mDrawable.
  2. 通過d.getIntrinsicWidth()拿到drawable的width賦值全局變量mDrawableWidth
  3. 通過d.getIntrinsicHeight()拿到drawable的height賦值全局變來那個mDrawableHeight
  4. 調用configureBounds()方法配置邊界。

OK,去看configureBounds()方法:


    private void configureBounds() {
        ···
        int dwidth = mDrawableWidth;
        int dheight = mDrawableHeight;

        int vwidth = getWidth() - mPaddingLeft - mPaddingRight;
        int vheight = getHeight() - mPaddingTop - mPaddingBottom;
        
        if (dwidth <= 0 || dheight <= 0 || ScaleType.FIT_XY == mScaleType) {
            mDrawable.setBounds(0, 0, vwidth, vheight);
            mDrawMatrix = null;
        } else {
            mDrawable.setBounds(0, 0, dwidth, dheight);
            ···
        }
    }

代碼較多,但是并不復雜,我們只看與我們討論相關的部分(中間面省略的部分是與ScaleType屬相相關的代碼,下次單獨拿出來分析)

  1. 計算出view的內部寬高并賦值局部變量。
  2. 判斷drawable的寬高是否沒有意義,或者是設置ScaleType為FIX_XY,就通過mDrawable.setBounds()方法將mDrawable的邊界范圍設置為View的內部大小。
  3. 否則,將mDrawable的邊界范圍設置為drawable資源的寬高。(所以src默認不會拉伸圖片)

最后就是在ondraw()方法中將mDrawable繪制出來。到此整個的src屬性的設置到繪制的流程已經清楚。


接著我們來看Background屬性是如何作用于View的。

setBackgroundResource()方法入手Background屬性

在ImageView的源碼中我們通過搜索發現并沒有Background相關的設置方法。在它的父類—View中,可以找到我們想要的代碼。

    public void setBackgroundResource(@DrawableRes int resid) {
        if (resid != 0 && resid == mBackgroundResource) {
            return;
        }
        Drawable d = null;
        if (resid != 0) {
            d = mContext.getDrawable(resid);
        }
        setBackground(d);

        mBackgroundResource = resid;
    }

代碼簡短,同樣先根據resid拿到drawable對象,然后調用了setBackground()方法。最終調用了setBackgroundDrawable()方法。在這個方法最終將drawable賦值給了一個全局變量mBackground,在這個方法中并沒有發現設置drawable的bounds相關的方法,那么我們接著往后走,到繪制層。View的onDraw()方法是空的,我們知道onDraw()方法其實是由View的draw()方法調用的,在draw()方法中是進行View的基本繪制,onDraw()是擴展視圖自己繪制的方法。


直奔draw()方法

00-53-01.jpg

很開心,google的工程師給我們寫了很清晰的注釋。第一步就是繪制background,只調用了一個方法drawBackground()。乘勝追擊。

    private void drawBackground(Canvas canvas) {
        final Drawable background = mBackground;
        if (background == null) {
            return;
        }

        setBackgroundBounds();
        ···
}

看到了,調用了一個setBackGroundBounds()方法,追

    void setBackgroundBounds() {
        if (mBackgroundSizeChanged && mBackground != null) {
            mBackground.setBounds(0, 0,  mRight - mLeft, mBottom - mTop);
            mBackgroundSizeChanged = false;
            rebuildOutline();
        }
    }

簡短精悍的代碼,mBackground.setBounds(0, 0, mRight - mLeft, mBottom - mTop),設置了mBackground這個drawable的邊界。就是View的寬高。

  • 此處是直接使用mRight - mleft 和 mBottom - mTop,也就是說忽略padding的存在。
  • 在src屬性中,使用到容器的寬高是這樣獲得的
        int vwidth = getWidth() - mPaddingLeft - mPaddingRight;
        int vheight = getHeight() - mPaddingTop - mPaddingBottom;

padding是起作用的。通過兩個簡單的例子看看效果。

01-09-06.jpg
01-08-32.jpg


01-10-21.jpg
01-07-19.jpg

總結

通過源碼的閱讀,清楚了ImageView 與 Background的區別與聯系以及繪制的流程:

  1. 都是Drawable資源的繪制,所以他們內容是可以互通的。
  2. background是View的屬性,在View層繪制(所以Background稱為背景,因為它在自定義View的onDraw()方法被調用前就已經被調用繪制了),所有的控件都具有該屬性,src是ImageView中定義的屬性,在ImageView的onDraw方法中才被繪制。只有ImageView即其子類才有此屬性。
  3. background是縮放填充式的繪制,src可以通過ScaleType設置不同的縮放效果。
  4. padding屬性對background無效,但對ImageView的src是有效的。

第一次寫技術類的文章,如有錯誤還望多多指正。歡迎分享交流。
郵箱: liuj4563@163.com
原創博文 轉載請注明出處

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,739評論 6 534
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,634評論 3 419
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,653評論 0 377
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,063評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,835評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,235評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,315評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,459評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,000評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,819評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,004評論 1 370
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,560評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,257評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,676評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,937評論 1 288
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,717評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,003評論 2 374

推薦閱讀更多精彩內容