![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmbw5SZkVWMidjZyYTY5MDZ5QGO4QzY0QWYkBjZ4EzM0cDOi9CX0JXZ252bj91Ztl2Lc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
衆所周知,在Android實際開發中,對于某些複雜多變的情況,控件的位置擺放、大小控制并非是xml類型的layout檔案完全可以搞定的。此時,我們通常會使用Java代碼來通過動态計算,将指定的控件擺放在相應的位置,并限定其大小。同樣地,也需要擷取某個控件的大小。 對于擷取控件寬、高的方法,大家可以自行谷歌或者百度,大抵無非一下三種方法:
- 給相應的View控件 添加ViewTreeObserver回調 ;
- Override onWindowFocusChange方法 ;
- 在需要測量時(而不是onCreate或onResume中), 使用MeasureSpec内部類 擷取寬高。
對于上述第三種情況,我們暫且不論。對于前二者而言,有沒有更簡單的實作呢?
為何擷取寬高要如此?
對于初學者,可能會有這樣的疑問:為什麼我們不能在onCreate()或者onResume()中直接使用上述第三種方案擷取寬高呢? 結論是:那樣的話,擷取來的值很可能皆為0,即使實際的寬高不是0。那麼這是為何呢? 這其實是由Android的UI繪制流程決定的。大家不妨試着做一下實驗,即使是在onResume()方法後,它的意義也僅僅是指Activity進入了可見的
狀态,這
并不意味着界面繪制的結束。我們可以用一個簡單的帶有寬高值得View來做實驗,觀察Activity中各回調方法的調用順序,得到的結果将是這樣的:
Activity.oncreate() → Activity.onResume() → View.onMeasure() → View.onLayout() → onGlobalLayoutListener() → Activity.onWidnowFocusChanged() → ... → View.onDraw() -> ...
是以,如果我們在onResume()中嘗試擷取View寬高的話,很大機率是會失敗的。
巧用Handler擷取View控件資訊
這裡我們開門見山地先放上代碼片:
private
上述代碼作為通用的方法将擷取任意View的寬高做了封裝,其妙處就在‘view.post’處。 将其置于onCreate()、onResume()方法中調用,均可擷取到正确的寬高。
@Override
Logcat中的運作結果:
2019-01-14 22:33:13.874 18355-18355/com.example.wenhan.helloandroid I/MainActivity: Width: 225, height: 57 2019-01-14 22:33:13.874 18355-18355/com.example.wenhan.helloandroid I/MainActivity: Width: 225, height: 57
為何如此就可擷取到正确的值了呢?
其中的玄機在于,我們在View.post()中所寫的語句并沒有立即執行,而在其真正執行的時候,View的寬高已經被測量完成了,那時我們再去擷取寬高時,就會很容易地擷取到正确的值了。 通過斷點Debug,可以輕松地發現,在Activity啟動過程的調用棧中,存在ActivityThread類被執行了,具體按照:
main() -> handleResumeActivity() -> addView() -> setView() -> requestLayout() -> scheduleTraversals() -> 執行mTraversalRunnable異步線程 -> doTraversal() -> performTraversals() -> ... -> performMeasure() -> ...
的執行順序。 在我們擷取寬高的語句執行前,主線程的Handler正在執行TraversalRunnable(見上述方法具體實作),而performMeasure也被包含其中。又因為我們擷取寬高的語句要排隊,處于等待狀态,直到主線程Handler輪到執行我們的語句,而此時View的寬高的測量已經結束。
完整示例代碼:
wh1990xiao2005/FetchViewSizeDemogithub.com