Wie können Sie feststellen, wann ein Layout gezeichnet wurde?

201

Ich habe eine benutzerdefinierte Ansicht, die eine scrollbare Bitmap auf den Bildschirm zeichnet. Um es zu initialisieren, muss ich die Größe des übergeordneten Layoutobjekts in Pixel übergeben. Während der Funktionen onCreate und onResume wurde das Layout jedoch noch nicht gezeichnet, sodass layout.getMeasuredHeight () 0 zurückgibt.

Als Problemumgehung habe ich einen Handler hinzugefügt, der eine Sekunde wartet und dann misst. Das funktioniert, aber es ist schlampig, und ich habe keine Ahnung, wie viel ich die Zeit verkürzen kann, bevor ich am Ende bin, bevor das Layout gezeichnet wird.

Ich möchte wissen, wie ich erkennen kann, wann ein Layout gezeichnet wird. Gibt es ein Ereignis oder einen Rückruf?

Plastikstör
quelle

Antworten:

391

Sie können dem Layout einen Baumbeobachter hinzufügen. Dies sollte die richtige Breite und Höhe zurückgeben. onCreate()wird aufgerufen, bevor das Layout der untergeordneten Ansichten fertig ist. Die Breite und Höhe ist also noch nicht berechnet. Um die Höhe und Breite zu erhalten, setzen Sie dies auf die onCreate()Methode:

    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(); 

        } 
    });
blessenm
quelle
28
Vielen Dank! Aber ich frage mich, warum es dafür keine einfachere Methode gibt, weil es ein häufiges Problem ist.
Felixd
1
Tolle. Ich habe OnLayoutChangeListener ausprobiert und es hat nicht funktioniert. Aber OnGlobalLayoutListener hat funktioniert. Ich danke dir sehr!
YoYoMyo
8
removeGlobalOnLayoutListener ist in API-Level 16 veraltet. Verwenden Sie stattdessen removeOnGlobalLayoutListener.
Tunaobun
3
Ein "Gotcha", das ich sehe, ist, wenn Ihre Ansicht nicht sichtbar ist, wird das onGlobalLayout aufgerufen, aber später, wenn es sichtbar ist, wird das getView aufgerufen, aber nicht das onGlobalLayout ... ugh.
Stephen McCormick
1
Eine andere schnelle Lösung hierfür ist die Verwendung view.post(Runnable), es ist sauberer und es macht das gleiche.
dvdciri
74

Eine wirklich einfache Möglichkeit besteht darin, einfach post()Ihr Layout aufzurufen . Dadurch wird Ihr Code im nächsten Schritt ausgeführt, nachdem Ihre Ansicht bereits erstellt wurde.

YOUR_LAYOUT.post( new Runnable() {
    @Override
    public void run() {
        int width  = YOUR_LAYOUT.getMeasuredWidth();
        int height = YOUR_LAYOUT.getMeasuredHeight(); 
    }
});
Elliptica
quelle
4
Ich weiß nicht, ob der Post Ihren Code tatsächlich ausführen wird, nachdem die Ansicht bereits erstellt wurde. Es gibt keine Garantie, da post nur die von Ihnen erstellte Runnable zur RunQueue hinzufügt, die nach der vorherigen Warteschlange ausgeführt wird, die auf dem UI-Thread ausgeführt wurde.
HendraWD
4
Im Gegenteil, post () wird nach der nächsten Aktualisierung des UI-Schritts ausgeführt, die nach dem Erstellen der Ansicht erfolgt. Daher ist garantiert, dass es ausgeführt wird, nachdem die Ansicht erstellt wurde.
Elliptica
Ich habe diesen Code mehrmals getestet, er funktioniert am besten nur im UI-Thread. In einem Hintergrund-Thread funktioniert es manchmal nicht.
CoolMind
3
@HendraWijayaDjiono, vielleicht wird dieser Beitrag antworten: stackoverflow.com/questions/21937224/…
CoolMind
1
Ich stieß auf ein Problem, bei dem die Verwendung der ersten Antwort nicht jedes Mal funktionierte. Durch die Verwendung von Post in der Ansicht, die ich zum Scrollen benötigte (eine Scroll-Ansicht), konnte ich an die gewünschte Position scrollen, sobald die Ansicht bereit war scrollTo-Methodenaufrufe.
Danuofr
21

Um veralteten Code und Warnungen zu vermeiden, können Sie Folgendes verwenden:

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();
            }
        });
murena
quelle
1
Um diesen Code verwenden zu können, muss 'view' als 'final' deklariert werden.
BeccaP
2
Verwenden Sie stattdessen diese Bedingung Build.VERSION.SDK_INT <Build.VERSION_CODES.JELLY_BEAN
HendraWD
4

Besser mit Kotlin-Erweiterungsfunktionen

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)
            }
        }
    })
}
Sergio
quelle
Sehr schöne und elegante Lösung, die wiederverwendet werden kann.
Ionut Negru
3

Eine andere Antwort lautet:
Überprüfen Sie die Ansichtsabmessungen unter onWindowFocusChanged.

Joe Plante
quelle
3

Eine Alternative zu den üblichen Methoden besteht darin, sich in die Zeichnung der Ansicht einzuhängen.

OnPreDrawListenerwird beim Anzeigen einer Ansicht mehrmals aufgerufen, sodass es keine bestimmte Iteration gibt, bei der Ihre Ansicht eine gültige gemessene Breite oder Höhe hat. Dies erfordert, dass Sie kontinuierlich überprüfen ( view.getMeasuredWidth() <= 0) oder ein Limit für die Häufigkeit festlegen, mit der Sie nach measuredWidthmehr als Null suchen.

Es besteht auch die Möglichkeit, dass die Ansicht niemals gezeichnet wird, was auf andere Probleme mit Ihrem Code hinweisen kann.

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
    }
});
jwehrle
quelle
Die Frage ist , How can you tell when a layout has been drawn?und Sie werden , ein Verfahren , wo man ein Anruf OnPreDrawListenerund don't stop interrogating that view until it has provided you with a reasonable answerso die Einwände gegen Ihre Lösung sollte klar sein.
Verlassener Wagen
Was Sie wissen müssen, ist nicht, wann es gezeichnet wurde, sondern wann es gemessen wurde. Mein Code beantwortet die Frage, die wirklich gestellt werden muss.
jwehrle
Gibt es ein Problem mit dieser Lösung? Kann es das Problem nicht lösen?
jwehrle
1
Die Ansicht wird erst gemessen, wenn sie angelegt ist. Daher ist diese Alternative übermäßig und führt eine redundante Überprüfung durch. Sie geben zu, die Frage nicht angesprochen zu haben, aber was Sie denken, sollte gefragt werden. Der ursprüngliche Kommentar machte beide Punkte, aber es gibt auch einige Probleme mit dem Code selbst, die als Bearbeitung eingereicht wurden.
Verlassener Wagen
Das ist eine schöne Bearbeitung. Danke dir. Ich denke, was ich frage ist: Denkst du, dass diese Antwort gelöscht werden sollte oder nicht? Ich bin auf das Problem des OP gestoßen. Dies löste das Problem. Ich habe es in gutem Glauben angeboten. War das ein Fehler? Ist das etwas, was mit StackOverflow nicht gemacht werden sollte?
jwehrle
2

Überprüfen Sie dies einfach, indem Sie die Post-Methode in Ihrem Layout oder Ihrer Ansicht aufrufen

 view.post( new Runnable() {
     @Override
     public void run() {
        // your layout is now drawn completely , use it here.
      }
});
Shivam Yadav
quelle
Dies ist nicht wahr und könnte mögliche Probleme verbergen. poststellt nur die ausführbare Datei in die Warteschlange, um sie auszuführen, stellt jedoch nicht sicher, dass die Ansicht gemessen wird, noch weniger gezeichnet.
Ionut Negru
@IonutNegru vielleicht lesen Sie diese stackoverflow.com/a/21938380
Bruno Bieri
1

Wenn onMeasuredie Ansicht aufgerufen wird, erhält sie die gemessene Breite / Höhe. Danach können Sie anrufen layout.getMeasuredHeight().

Pavan
quelle
4
Müssten Sie nicht Ihre eigene Ansicht erstellen, um zu wissen, wann onMeasure ausgelöst wird?
Geeks On Hugs
Ja, Sie müssen benutzerdefinierte Ansichten verwenden, um auf Maßnahmen zuzugreifen.
Trevor Hart