巧用Handler獲取View控件信息

衆所周知,在Android實際開發中,對於某些複雜多變的狀況,控件的位置擺放、大小控制並不是是xml類型的layout文件徹底能夠搞定的。此時,咱們一般會使用Java代碼來經過動態計算,將指定的控件擺放在相應的位置,並限定其大小。一樣地,也須要獲取某個控件的大小。 對於獲取控件寬、高的方法,你們能夠自行谷歌或者百度,大抵無非一下三種方法:android

  1. 給相應的View控件添加ViewTreeObserver回調
  2. Override onWindowFocusChange方法
  3. 在須要測量時(而不是onCreate或onResume中),使用MeasureSpec內部類獲取寬高。

對於上述第三種狀況,咱們暫且不論。對於前兩者而言,有沒有更簡單的實現呢?git

爲什麼獲取寬高要如此?

對於初學者,可能會有這樣的疑問:爲何咱們不能在onCreate()或者onResume()中直接使用上述第三種方案獲取寬高呢?
結論是:那樣的話,獲取來的值極可能皆爲0,即便實際的寬高不是0。那麼這是爲什麼呢?
這實際上是由Android的UI繪製流程決定的。你們不妨試着作一下實驗,即便是在onResume()方法後,它的意義也僅僅是指Activity進入了可見的狀態,這並不意味着界面繪製的結束。咱們能夠用一個簡單的帶有寬高值得View來作實驗,觀察Activity中各回調方法的調用順序,獲得的結果將是這樣的:github

Activity.oncreate() → Activity.onResume() → View.onMeasure() → View.onLayout() → onGlobalLayoutListener() → Activity.onWidnowFocusChanged() → ... → View.onDraw() -> ...bash

所以,若是咱們在onResume()中嘗試獲取View寬高的話,很大機率是會失敗的。異步

巧用Handler獲取View控件信息

這裏咱們開門見山地先放上代碼片:ide

private int[] measureView(final View view) {
        final int[] returnData = new int[2];
        view.post(new Runnable() {
            @Override
            public void run() {
                returnData[0] = view.getWidth();
                returnData[1] = view.getHeight();
                Log.i(TAG, "Width: " + returnData[0] + ", height: " + returnData[1]);
            }
        });
        return returnData;
    }
複製代碼

上述代碼做爲通用的方法將獲取任意View的寬高作了封裝,其妙處就在‘view.post’處。 將其置於onCreate()、onResume()方法中調用,都可獲取到正確的寬高。post

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.i(TAG, "onCreate start!");
        setContentView(R.layout.activity_main);
        testTv = (TextView) findViewById(R.id.testTv);
        measureView(testTv);
    }

    @Override
    protected void onResume() {
        super.onResume();
        Log.i(TAG, "onResume start!");
        measureView(testTv);
    }
複製代碼

Logcat中的運行結果:ui

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: 57spa

爲什麼如此就可獲取到正確的值了呢?

其中的玄機在於,咱們在View.post()中所寫的語句並無當即執行,而在其真正執行的時候,View的寬高已經被測量完成了,那時咱們再去獲取寬高時,就會很容易地獲取到正確的值了。 經過斷點Debug,能夠輕鬆地發現,在Activity啓動過程的調用棧中,存在ActivityThread類被執行了,具體按照:線程

main() -> handleResumeActivity() -> addView() -> setView() -> requestLayout() -> scheduleTraversals() -> 執行mTraversalRunnable異步線程 -> doTraversal() -> performTraversals() -> ... -> performMeasure() -> ...

的執行順序。
在咱們獲取寬高的語句執行前,主線程的Handler正在執行TraversalRunnable(見上述方法具體實現),而performMeasure也被包含其中。又由於咱們獲取寬高的語句要排隊,處於等待狀態,直到主線程Handler輪到執行咱們的語句,而此時View的寬高的測量已經結束。

完整示例代碼:github.com/wh1990xiao2…

相關文章
相關標籤/搜索