1. 程式人生 > >TextView 的新特性,Autosizing 到底是如何實現的? | 源碼分析

TextView 的新特性,Autosizing 到底是如何實現的? | 源碼分析

osi 了解 roi https 行數 eset ott 特性 largest

技術分享圖片

一、前言

Hi,大家好,我是承香墨影!

前兩天聊了一下 Autosizing 的使用,反映還不錯。畢竟是這種能解決實際問題的新 Api,確實在需要的時候,用起來會很順手。

簡單回顧一下,Autosizing 是在 Support v26 中新支持的功能,可以根據文本的內容和 TextView 的大小,自動適應齊內部文本的字體大小,來達到完全顯示的效果。而這個功能,最低能兼容到 Api Level 14,可以說是一個誠意滿滿的新 Api。

技術分享圖片

還不了解 Autosizing 的朋友,可以看看之前的文章《文字太多?控件太小?試試 TextView 的新特性 Autosizeing 吧!》,裏面有使用它的詳細介紹。

我想,在沒有 Autosizing 的時候,應該已經有人以這樣的思路在實現功能了。那麽,今天就來從源碼的角度分析一下,Autosizing 的原理如何,看看它是如何工作的。

二、帶著問題看源碼

分析源碼也是講究方式方法的,我主推的一個思路,就是帶著問題看源碼。

很多大型項目,其實本身都是很復雜的,並且涵蓋的功能點也非常的多,如果想要一次就把它完整的閱讀屢清楚,還是很吃力的。

所以我建議在閱讀開源項目之前,你先閱讀文檔,嘗試使用一下它,看看它能做什麽,再自己思考一下,如果你是作者,你會如何去實現這些功能的,最後帶著這些問題去閱讀源碼,以問題為出發點,看看那些大牛寫的優秀的開源庫,到底有什麽值得我們借鑒的地方。

總歸一句話:閱讀源碼是為了更好的編寫源碼!

當我看到到 Autosizing 這個新特性的時候,我有一些好奇的地方在於:

  1. Android 8.0 的 TextView 和 Support 包中,Autosizing 的實現,有什麽區別?
  2. Autosizing 是會在什麽時機,去觸發根據文本的內容,計算出一個適合的字體大小。
  3. Autosizing 是如何計算合適的字體大小的。
  4. 脫離 Autosizing,源碼中的功能,有什麽能借鑒的使用場景。

大概就是這些問題吧,接下來我們看看 Autosizing 是如何實現的。

三、Autosizing 源碼

3.1 實現的區別

對於 Android 8.0 中和 Support v26 中,具體對於 Autosizing 的實現,有什麽區別這一點,大致閱讀一下兩邊的源碼,你會發現大致上沒區別。

它們之間,和 Autosizing 相關的源碼所在的源碼文件也不一樣:

  • Android 8.0 主要在 TextView 中。
  • Support v26 主要在 AppCompatTextViewAutoSizeHelper。

隨手比對一下它們的 setAutoSizeTextTypeWithDefaults() 方法,這個方法用來標記是否對 TextView 開啟 Autosizing。

技術分享圖片

左邊是 Android 8.0 的 TextView ,右邊是 AppCompatTextViewAutoSizeHelper。

可以看到,整個代碼的結構都是一致的,只是部分引用的類不一樣而已,但是表達的意思是一致的。

之所以說它們之前大致是一樣的,是因為有一些 Api 是 private 的或者被標記為 @hide 了,這樣,在外部是無法訪問到的。對此 AppCompatTextViewAutoSizeHelper 的做法是用反射的形式去調用它。

例如,實際去修改 TextView 尺寸的方法 autoSizeText() ,看下面它們的區別。

技術分享圖片

左邊是 Android 8.0 的 TextView ,右邊是 AppCompatTextViewAutoSizeHelper。

兩邊都需要獲取 mHorizontallyScrolling 的值,TextView 內部當然可以直接調用了,而 AppCompatTextViewAutoSizeHelper 的做法是,使用 invokeAndReturnWithDefault() 方法,通過反射區獲取這個值。

技術分享圖片

所以,我們可以得出結論,兩邊的實現思路,大體上是沒有區別的,只是有一些小細節,會不一樣,但是我們不需要太在意這些。

既然,兩邊的區別不大,之後我們就以 Support v26 中,關於 Autosizing 的源碼實現來進行分析。

3.2 觸發 Autosizing 的時機

首先這個時機讓我自己來設計,也非常好理解。

本質上 Autosizing 就是為了讓 TextView 中的文本,能完全顯示,在這個過程中會去調整 文本 的字體大小。

那這樣,觸發它的時機,其實就很容易猜到了:

  1. 在 文本內容 變動的時候。
  2. 在 TextView 大小變動的時候。

Support v26 中,之所以能保證兼容,本質上它會自動將 TextView 這樣的控件,替換成 AppCompatTextView 來達到兼容的效果,這個過程中,開發者只需要使用 AppCompatActivity 就可以了,其它的不需要開發者來參與。這樣,其實我們只需要關註 AppCompatTextView 中的實現邏輯就好了。

前面提到,操作 Autosizing 的具體源碼,在 AppCompatTextViewAutoSizeHelper 中。 而 AppCompatTextView 並不直接操作它。

首先 AppCompatTextVIew 會持有 AppCompatTextHelper 這個幫助類,而這個幫助類,又去持有 AppCompatTextViewAutoSizeHelper,最終所有的邏輯都傳遞到 AppCompatTextViewAutoSizeHelper 中去處理。

所以操作的流程大概是這樣的:

技術分享圖片

而 Autosizing 真實去測量並修改字體大小的邏輯,都在 autoSizeText() 方法中,我們只需要關心它在何時被調用,就能知道具體觸發 Autosizing 的時機了。

第一個觸發點,它會在 AppCompatTextView 的 onTextChanged() 方法中,直接調用 autoSizeText() 方法。

技術分享圖片

第二個觸發點,會監聽 AppCompatTextView 的 onLayout() 方法,在其中調用 AppCompatTextHelper 的 onLayout() 方法。

技術分享圖片

好了,兩個時機都找到了,也驗證了我們之前的猜想。

3.3 Autosizing 如何計算大小

前面提到 Autosizing 實際上去修改 TextView 字體的方法,在 AppCompatTextViewAutoSizeHelper 的 autoSizeText() 方法中,這裏我們先來看看這個方法的實現。

技術分享圖片

這一段邏輯,就是 Autosizing 中,很重要的一個邏輯。先來看看它大體上的流程。

  1. 使用它會使用 isAutoSizeEnabled() 方法,判斷當前是否開啟 Autosizing 。
  2. 判斷 mNeedsAutoSizeText 是否為 true,此處判斷是主要是看是否存在可變動的尺寸。
  3. 計算 TextView 本身的顯示區域大小,存放在 TEMP_RECTF 中。
  4. 使用 findLargestTextSizeWhichFits() 獲取到一個合適當前文本長度的最大尺寸值。
  5. 如果和當前 TextView 的 textSize 不一致,則使用 setTextSizeInternal() 將其設置回去。

大體步驟就是這樣,接下來我們從細節出發看看它的具體實現。

首先是 isAutoSizeEnable() 方法,它去判斷當前是否開啟了 Autosizing,其實就是判斷 mAutoSizeTextType 屬性是否為 none。

技術分享圖片

mNeedsAutoSizeText 這個判斷,本質上其實是為了判斷 mAutoSizeTextSizesInPx 這個存放尺寸的數組裏,是否有值,這個尺寸數組,在後面的 findLargestTextSizeWhichFits() 方法中會用到。

技術分享圖片

mAutoSizeTextSizesInPx 其實就是一個存放當前 TextView 預估能使用的尺寸數組,是被提前計算出來的,它會在對 Autosizing 受影響的相關的屬性做出修改的時候,重新計算。例如:粒度(Granularity)、預設尺寸(PresetSizes)等變動,都會觸發重新計算 mAutoSizeTextSizesInPx 的值。

TEMP_RECTF 就沒有什麽好說的了,無非就是從 TextView 的寬高和 Padding 等屬性,計算出一個能用於顯示 文本 區域大小。接下來就會去調用 findLargestTextSizeWhichFits() 方法,找到一個當前 文本 內容,最合適的字體大小。

技術分享圖片

這裏邏輯也很清晰,就是使用一個循環,通過 suggestedSizeFitsInSpace() 方法判斷取出來的尺寸是否合適。這裏為了提高效率,使用了二分算法,去避免全部遍歷 mAutoSizeTextSizesInPx 數組,從而提高效率。

接下來就是 suggestedSizeFitsInSpace() 方法,它會根據 TextView 的內容區域和 文本,判斷當前給定的尺寸,是否能放的下這些內容。

技術分享圖片

這裏首先使用了一個 TextPaint 對象 mTempTextPaint 來存放 TextView 的一些參數,然後根據 mTempTextPaint 去創建一個使用 StaticLayout 對象,來嘗試對文本進行布局。

StaticLayout 是一個為不可編輯的文本布局的類,這意味著一旦布局完成,文本內容就不可以改變。

最終,就能確定,傳遞進行的字體大小,是否能完全顯示在這個區域內。

經過這一通計算,findLargestTextSizeWhichFits() 方法,最終將計算出來的一個合適的字體尺寸,返回回去,再通過 setTextSizeInternal() 設置到 TextView,來達到修改字體大小的目的。

四、源碼中能借鑒的功能

現在來看,Autosizing 計算某段文本,在一個 固定的 TextView 中,將展示的單行寬度和行數這個功能,這些算是 Autosizing 中,比較有借鑒意義的功能了。

其它的我暫時沒有想到,你覺得還有什麽可以借鑒的點呢?在留言中告訴我。

今天在承香墨影公眾號的後臺,回復『成長』,我會送你一些特別的內容。

我另外還維護了一個技術交流的微信群,有興趣可以在公眾號後臺回復:"加群"

推薦閱讀:

  • 站在Android開發的角度,聊聊Airbnb的Lottie
  • 這些工具,讓你寫博客的時候,只需要專註寫作!
  • 找了一天找不到 Bug ? 試試 Git 的二分法吧!!!
  • 如何更精準的在 Github 上搜索開源庫?你需要這些技巧!
  • Android 開發,遇上 Emoji 頭疼嗎?

技術分享圖片

TextView 的新特性,Autosizing 到底是如何實現的? | 源碼分析