レイアウトがいつ描かれたかはどのようにしてわかりますか?


201

画面にスクロール可能なビットマップを描画するカスタムビューがあります。それを初期化するには、親レイアウトオブジェクトのサイズをピクセル単位で渡す必要があります。しかし、onCreate関数とonResume関数の実行中は、レイアウトはまだ描画されていないため、layout.getMeasuredHeight()は0を返します。

回避策として、1秒待ってから測定するハンドラーを追加しました。これは機能しますが、ずさんなため、レイアウトが描画される前に最終的にどのくらいの時間をトリミングできるかわかりません。

知りたいのは、レイアウトがいつ描画されたかをどのように検出できるかです。イベントまたはコールバックはありますか?

回答:


391

レイアウトにツリーオブザーバーを追加できます。これにより、正しい幅と高さが返されます。onCreate()子ビューのレイアウトが完了する前に呼び出されます。したがって、幅と高さはまだ計算されていません。高さと幅を取得するには、これをonCreate()メソッドに配置します。

    final LinearLayout layout = (LinearLayout) findViewById(R.id.YOUR_VIEW_ID);
    ViewTreeObserver vto = layout.getViewTreeObserver(); 
    vto.addOnGlobalLayoutListener (new OnGlobalLayoutListener() { 
        @Override 
        public void onGlobalLayout() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
                    layout.getViewTreeObserver()
                            .removeOnGlobalLayoutListener(this);
                } else {
                    layout.getViewTreeObserver()
                            .removeGlobalOnLayoutListener(this);
                }
            int width  = layout.getMeasuredWidth();
            int height = layout.getMeasuredHeight(); 

        } 
    });

28
どうもありがとうございました!しかし、これが一般的な問題であるため、これを行う簡単な方法がないのはなぜでしょうか。
felixd 2014年

1
すごい。OnLayoutChangeListenerを試しましたが、うまくいきませんでした。しかし、OnGlobalLayoutListenerは機能しました。どうもありがとうございます!
YoYoMyo 2015年

8
removeGlobalOnLayoutListenerはAPIレベル16で廃止されました。代わりにremoveOnGlobalLayoutListenerを使用してください。
tounaobun '20年

3
ビューが表示されない場合、onGlobalLayoutが呼び出されますが、後で表示されたときにgetViewが呼び出されますが、onGlobalLayoutは呼び出されません。
スティーブンマコーミック

1
このための別の簡単な解決策は、を使用するview.post(Runnable)ことです。
dvdciri 2017年

74

本当に簡単な方法はpost()、レイアウトを呼び出すだけです。これにより、ビューが既に作成された後、次のステップでコードが実行されます。

YOUR_LAYOUT.post( new Runnable() {
    @Override
    public void run() {
        int width  = YOUR_LAYOUT.getMeasuredWidth();
        int height = YOUR_LAYOUT.getMeasuredHeight(); 
    }
});

4
ビューが既に作成された後、postが実際にコードを実行するかどうかはわかりません。postは、作成したRunnableをRunQueueに追加するだけなので、UIスレッドで実行された前のキューの後に実行されるため、保証はありません。
HendraWD 2016年

4
反対に、post()は、ビューの作成後に発生する次のUIステップの更新後に実行されます。したがって、ビューが作成された後に実行されることが保証されます。
Elliptica

私はこのコードを数回テストしましたが、UIスレッドでのみ最適に機能します。バックグラウンドスレッドでは動作しない場合があります。
CoolMind

3
@HendraWijayaDjiono、多分この記事はお答えします:stackoverflow.com/questions/21937224/...
CoolMind

1
スクロールする必要があるビュー(スクロールビュー)に投稿を使用すると、最初の回答を使用しても毎回機能しないという問題が発生し、ビューの準備ができるとすぐに目的の位置に正しくスクロールできましたscrollToメソッド呼び出し。
Danuofr

21

非推奨のコードと警告を回避するために、以下を使用できます。

view.getViewTreeObserver().addOnGlobalLayoutListener(
        new ViewTreeObserver.OnGlobalLayoutListener() {
            @SuppressWarnings("deprecation")
            @Override
            public void onGlobalLayout() {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
                    view.getViewTreeObserver()
                            .removeOnGlobalLayoutListener(this);
                } else {
                    view.getViewTreeObserver()
                            .removeGlobalOnLayoutListener(this);
                }
                yourFunctionHere();
            }
        });

1
このコードを使用するには、「view」を「final」として宣言する必要があります。
BeccaP 2015年

2
代わりにこの条件を使用するBuild.VERSION.SDK_INT <Build.VERSION_CODES.JELLY_BEAN
HendraWD

4

kotlin拡張関数でより良い

inline fun View.waitForLayout(crossinline yourAction: () -> Unit) {
    val vto = viewTreeObserver
    vto.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
        override fun onGlobalLayout() {
            when {
                vto.isAlive -> {
                    vto.removeOnGlobalLayoutListener(this)
                    yourAction()
                }
                else -> viewTreeObserver.removeOnGlobalLayoutListener(this)
            }
        }
    })
}

再利用できる非常に素晴らしくエレガントなソリューション。
Ionut Negru


3

通常の方法に代わる方法は、ビューの描画にフックすることです。

OnPreDrawListenerビューを表示するときに何度も呼び出されるため、ビューに有効な測定された幅または高さがある特定の反復はありません。これには、継続的に確認(view.getMeasuredWidth() <= 0)するかmeasuredWidth、ゼロより大きいかどうかを確認する回数に制限を設定する必要があります。

また、ビューが描画されない可能性もあり、コードに関する他の問題を示している可能性があります。

final View view = [ACQUIRE REFERENCE]; // Must be declared final for inner class
ViewTreeObserver viewTreeObserver = view.getViewTreeObserver();
viewTreeObserver.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
    @Override
    public boolean onPreDraw() {
        if (view.getMeasuredWidth() > 0) {     
            view.getViewTreeObserver().removeOnPreDrawListener(this);
            int width = view.getMeasuredWidth();
            int height = view.getMeasuredHeight();
            //Do something with width and height here!
        }
        return true; // Continue with the draw pass, as not to stop it
    }
});

質問はHow can you tell when a layout has been drawn?、あなたが呼び出すメソッドを提供しているOnPreDrawListenerし、don't stop interrogating that view until it has provided you with a reasonable answerので、あなたのソリューションへの異議は明らかです。
放棄されたカート

あなたが知る必要があるのは、それが描かれたときではなく、測定されたときです。私のコードは、本当に尋ねる必要がある質問に答えます。
jwehrle

このソリューションに問題はありますか?直面している問題を解決できませんか?
jwehrle

1
ビューはレイアウトされるまで測定されないため、この代替案は過剰であり、冗長チェックを実行します。あなたは質問に対処していないことを認めますが、あなたが何を感じるべきかが問われるべきです。元のコメントはこれらの両方のポイントを作成しましたが、編集として送信されたコード自体にもいくつかの問題があります。
放棄されたカート

それは素晴らしい編集です。ありがとうございました。私が求めているのは、この回答を削除する必要があると思いますか?OPの問題が発生しました。これで問題は解決しました。誠意をもって提供しました。それは間違いでしたか?StackOverflowで行うべきではないことですか?
jwehrle

2

レイアウトまたはビューでpostメソッドを呼び出して確認するだけです

 view.post( new Runnable() {
     @Override
     public void run() {
        // your layout is now drawn completely , use it here.
      }
});

これは真実ではなく、起こり得る問題を隠す可能性があります。 postランナブルを後で実行するためにキューに入れるだけで、ビューが測定されることは保証されません。
Ionut Negru

@IonutNegru多分これを読んでくださいstackoverflow.com/a/21938380
Bruno

@BrunoBieri、ビューの測定値を変更する非同期関数を使用してこの動作をテストし、キューされたタスクが各測定後に実行され、期待値を受け取るかどうかを確認できます。
Ionut Negru

1

ときにonMeasure呼び出されたビューは、その測定した幅/高さを取得します。この後、を呼び出すことができますlayout.getMeasuredHeight()


4
onMeasureがいつ起動するかを知るために、独自のカスタムビューを作成する必要はありませんか?
抱擁のオタク2012年

はい、メジャーにアクセスするにはカスタムビューを使用する必要があります。
Trevor Hart
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.