View單位轉換的秘密(系統源碼分析)

上一篇Android之重新推導設備尺寸對屏幕尺寸的概念進行了推算。如果你對設備尺寸的掌握程度還比較模糊,不妨看一看,方便我們繼續搞事拉。

在自定義View上做適配,多數情況下可以根據權重和獲取整個控件的寬高然后根據百分比去設置某個特殊的屬性需要用到的值。but,這些都太常見了。也不是一個我們今天要講的重點。

不知道大家有沒有糾結一個問題,在自定義View里,經常要定義Paint用來繪制。但是setStrokeWidth(float width)里的入參接受的到底是以px? dp? 哪個為單位。這里有本質上的區別。比如你寫了一個控件,自己的開發機器沒問題,結果換一個設備就尺寸就不準了。

我先賣個關子,不告訴你答案╭(╯^╰)╮。為此我們用官方TextVIew源碼和TypedArray源碼作為分析。

這是一條溫柔的提示:來么,寶貝兒!打開我們的AndroidStudio快速輕擊Shift兩下,然后輸入TextView,我們一起看流星吧。(里面真的有很多小星星都是注釋)

目錄

  • TextView中的textSize源碼分析
  • TypedArray源碼分析

TextView中的textSize源碼分析

textSize默認大小

我們可以看到默認情況下,TextViewtextSize是15。

重繪方法

而后我發現了這樣一個方法,用于重新設置FontSize,這里邏輯很清晰,判斷Paint的Size和剛剛重設是否一致,不一致就重設Paint的參數,并重新繪制View。

最后調用的是Native方法,java層已經看不到了。


setTextSize發現TypedValue

不過這些不是重點,我們還發現了另外一個方法。TextVIew設置字體有重載方法。我們重點去看兩個參數的。

這里說明了Size的單位大小是由unit來決定的,最后它調用了SetRawTextSize方法,而這個方法我們前面分析了是用來重繪的。我們來看Unit是個什么值。

通過TypedValue類發現實際所有的單位都是定義在這里的。而我們的setTextSize需要的單位也在這里。

TypedValue.applyDimension

重頭戲來了,如果我們用了兩個參數構造方法進行設置字體大小,那么。
TypedValue.applyDimension( unit, size, r.getDisplayMetrics())做了什
么,他是如何轉換單位的。我跟進去看。


我們來看最典型的一條case語句case COMPLEX_UNIT_DIP: return value * metrics.density;
這里的density就是我們上個章節里講到的密度,也就是說如果你用dp作為單位,他就已當前的size乘以密度返回。
好家伙,恍然大悟把?實際上這里把你傳入的任意單位最后都轉換成了像素來處理的,最后設置給了Paint

那么這里就說明了一個問題,Paint對象接受的參數最后都是px像素來使用的,不要漏掉了這一點喔。

這里我推薦直接使用TypedValue.applyDimension()來處理。這是android已經具備的Api,不需要自己再從零寫了。可能有小伙伴用過類似dp2Px()這種類庫。我個人不是很推薦往項目里堆疊繁雜的庫進來,一來是會變得越來越臃腫,而是如果單純的依賴不方便去修改源碼,出了問題,你要么選擇copy出代碼出來進行修改,要么反射一下。但這都不是很優雅。盡可能使用系統已有的。

參考代碼:

 TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 20, Resources.getSystem().getDisplayMetrics());


TypedArray源碼分析

什么是TypedArray

如果有常寫自定義View屬性的小伙伴一定對TypedArray不陌生,我們常用來在Java文件中取自定義View上的屬性。這些屬性可以是布爾、dp、整型、字符串甚至是一個布局文件的id等。

現在我們思考一個問題,平時在一個控件里描述一個長度用了dp為單位,他是如何適配在不同設備上的?

帶著這個疑惑,我們來看源碼如何解析的。首先我們不管是原生控件也好,還是自定義控件也好,我們都需要在構造方法中去取出XML布局文件中的屬性。

獲取TypedArray

這里獲取TypedArray對象,其中R.styleable.xxx是該控件擁有的屬性。

TypedArray typedArray = context.obtainStyledAttributes(attrs,R.styleable.xxx);  

接著我們來看一眼TypedArray都有那些方法。

TypedArray的使用

實際上,我們的控件從XML中去解析就是通過該TypedArray的API來操作的。見TextView獲取textSize源碼如下。


我們來看一眼,getDimensionPixelSize方法內部的邏輯。


注意關鍵的一行TypedValue.complexToDimensionPixelSize( data[index+AssetManager.STYLE_DATA], mMetrics);, 我們跟進去看。


是不是似曾相識?熟悉吧,沒錯,該方法最終還是調用了TypedValue.applyDimension()進行了單位換算。我們上一章節講過這個。我就不再展開里面的邏輯了。到這里真相大白。實際上在xml寫的dp、pt、px等,最終都要來這里一趟,將其轉換為px。再畫出來。所以又一次證明了,我們的畫圖操作都是基于px為單位的。

流程總結

  • 1.xml定義一個屬性
  • 2.在構造方法里用TypedArray來get這個屬性。
  • 3.調用getDimensionPixelSize()方法獲取該屬性上的具體值,并轉換單位為像素。
  • 4.繪制出來

我們發現它并不復雜,但很多時候往往是沒有去閱讀過源碼,在工作中不能流暢的解決這些問題。
有小伙伴反應說他的自定義View通過java代碼設置尺寸就是不能適配,但是通過屬性就可以。這里其實就是忘記調用TypedValue.applyDimension( unit, size, r.getDisplayMetrics())方法。
再比如面試過程中再有面試官問如何做適配的時候,你就不會再是背誦面試題上的答案了吧?

題外話,我反對那些所謂的面試寶典類,不是說這些知識的對錯性,而是很多新人在學習的過程中往往是囫圇吞棗,出發的目的如果是為了面試通過背誦這些東西,而不理解其中的概念,甚至想都沒有想過為什么是這樣。這樣的結果絕對不是面試官想看到的。也不是我們應該做的。我們慢一點不要緊,多花點時間,不能途一時之快,那些偷過的懶,都是要還的。


如何下次找到我?

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容