巧用Handler獲取View控件信息

眾所周知,在Android實(shí)際開發(fā)中,對于某些復(fù)雜多變的情況,控件的位置擺放、大小控制并非是xml類型的layout文件完全可以搞定的。此時,我們通常會使用Java代碼來通過動態(tài)計(jì)算,將指定的控件擺放在相應(yīng)的位置,并限定其大小。同樣地,也需要獲取某個控件的大小。
對于獲取控件寬、高的方法,大家可以自行谷歌或者百度,大抵無非一下三種方法:

  1. 給相應(yīng)的View控件添加ViewTreeObserver回調(diào)
  2. Override onWindowFocusChange方法;
  3. 在需要測量時(而不是onCreate或onResume中),使用MeasureSpec內(nèi)部類獲取寬高。

對于上述第三種情況,我們暫且不論。對于前二者而言,有沒有更簡單的實(shí)現(xiàn)呢?

為何獲取寬高要如此?

對于初學(xué)者,可能會有這樣的疑問:為什么我們不能在onCreate()或者onResume()中直接使用上述第三種方案獲取寬高呢?
結(jié)論是:那樣的話,獲取來的值很可能皆為0,即使實(shí)際的寬高不是0。那么這是為何呢?
這其實(shí)是由Android的UI繪制流程決定的。大家不妨試著做一下實(shí)驗(yàn),即使是在onResume()方法后,它的意義也僅僅是指Activity進(jìn)入了可見的狀態(tài),這并不意味著界面繪制的結(jié)束。我們可以用一個簡單的帶有寬高值得View來做實(shí)驗(yàn),觀察Activity中各回調(diào)方法的調(diào)用順序,得到的結(jié)果將是這樣的:

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

因此,如果我們在onResume()中嘗試獲取View寬高的話,很大概率是會失敗的。

巧用Handler獲取View控件信息

這里我們開門見山地先放上代碼片:

    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()方法中調(diào)用,均可獲取到正確的寬高。

    @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中的運(yùn)行結(jié)果:

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

為何如此就可獲取到正確的值了呢?

其中的玄機(jī)在于,我們在View.post()中所寫的語句并沒有立即執(zhí)行,而在其真正執(zhí)行的時候,View的寬高已經(jīng)被測量完成了,那時我們再去獲取寬高時,就會很容易地獲取到正確的值了。
通過斷點(diǎn)Debug,可以輕松地發(fā)現(xiàn),在Activity啟動過程的調(diào)用棧中,存在ActivityThread類被執(zhí)行了,具體按照:

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

的執(zhí)行順序。
在我們獲取寬高的語句執(zhí)行前,主線程的Handler正在執(zhí)行TraversalRunnable(見上述方法具體實(shí)現(xiàn)),而performMeasure也被包含其中。又因?yàn)槲覀儷@取寬高的語句要排隊(duì),處于等待狀態(tài),直到主線程Handler輪到執(zhí)行我們的語句,而此時View的寬高的測量已經(jīng)結(jié)束。

完整示例代碼:https://github.com/wh1990xiao2005/FetchViewSizeDemo

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容