Come puoi sapere quando è stato disegnato un layout?


201

Ho una vista personalizzata che disegna una bitmap scorrevole sullo schermo. Per inizializzarlo, devo passare la dimensione in pixel dell'oggetto layout padre. Ma durante le funzioni onCreate e onResume, il layout non è stato ancora disegnato, quindi layout.getMeasuredHeight () restituisce 0.

Per ovviare al problema, ho aggiunto un gestore per attendere un secondo e poi misurare. Funziona, ma è sciatto, e non ho idea di quanto posso tagliare il tempo prima di finire prima che il layout venga disegnato.

Quello che voglio sapere è, come posso rilevare quando viene disegnato un layout? C'è un evento o una richiamata?

Risposte:


391

È possibile aggiungere un osservatore dell'albero al layout. Questo dovrebbe restituire la larghezza e l'altezza corrette. onCreate()viene chiamato prima che venga eseguito il layout delle viste figlio. Quindi la larghezza e l'altezza non sono ancora calcolate. Per ottenere l'altezza e la larghezza, inseriscilo nel onCreate()metodo:

    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
Grazie mille! Ma mi chiedo perché non esiste un metodo più semplice per farlo perché è un problema comune.
felixd

1
Sorprendente. Ho provato OnLayoutChangeListener e non ha funzionato. Ma OnGlobalLayoutListener ha funzionato. Grazie mille!
YoYoMyo

8
removeGlobalOnLayoutListener è obsoleto nel livello API 16. Utilizzare invece removeOnGlobalLayoutListener.
tounaobun,

3
Un "gotcha" che sto vedendo è se la tua vista non è visibile, viene chiamato onGlobalLayout, ma in seguito quando viene chiamato getView, ma non onGlobalLayout ... ugh.
Stephen McCormick,

1
Un'altra soluzione rapida per questo è usare view.post(Runnable), è più pulito e fa la stessa cosa.
dvdciri,

74

Un modo davvero semplice è semplicemente chiamare post()il tuo layout. Questo eseguirà il tuo codice il passaggio successivo, dopo che la tua vista è già stata creata.

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

4
Non so se post eseguirà effettivamente il tuo codice dopo che la vista è già stata creata. Non esiste alcuna garanzia, poiché post aggiungerà semplicemente Runnable che si crea a RunQueue, da eseguire dopo la coda precedente eseguita sul thread dell'interfaccia utente.
HendraWD,

4
Al contrario, post () viene eseguito dopo il successivo aggiornamento del passaggio dell'interfaccia utente, che si verifica dopo la creazione della vista. Pertanto, si garantisce che verrà eseguito dopo la visualizzazione.
Elliptica,

Ho provato questo codice più volte, funziona meglio solo nel thread dell'interfaccia utente. In un thread in background a volte non funziona.
CoolMind

3
@HendraWijayaDjiono, forse questo post risponderà: stackoverflow.com/questions/21937224/...
CoolMind

1
Mi sono imbattuto in un problema in cui l'utilizzo della prima risposta non funzionava ogni volta, utilizzando post nella vista che dovevo scorrere (una vista di scorrimento), mi permetteva di scorrere correttamente nella posizione desiderata non appena la vista era pronta per essere presa scroll Per le chiamate al metodo.
Danuofr,

21

Per evitare codici obsoleti e avvisi è possibile utilizzare:

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
Per utilizzare questo codice, 'view' deve essere dichiarato come 'final'.
BeccaP

2
usa questa condizione invece Build.VERSION.SDK_INT <Build.VERSION_CODES.JELLY_BEAN
HendraWD

4

Meglio con le funzioni di estensione di 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)
            }
        }
    })
}

Soluzione molto bella ed elegante che può essere riutilizzata.
Ionut Negru

3

Un'altra risposta è:
prova a controllare le dimensioni di visualizzazione in onWindowFocusChanged.


3

Un'alternativa ai metodi abituali è agganciarsi al disegno della vista.

OnPreDrawListenerviene chiamato più volte durante la visualizzazione di una vista, quindi non esiste un'iterazione specifica in cui la vista abbia una larghezza o altezza misurate valide. Ciò richiede la verifica continua ( view.getMeasuredWidth() <= 0) o l'impostazione di un limite al numero di volte in cui si verifica un valore measuredWidthmaggiore di zero.

È anche possibile che la vista non venga mai disegnata, il che potrebbe indicare altri problemi con il codice.

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

La domanda è How can you tell when a layout has been drawn?e stai fornendo un metodo in cui chiami un OnPreDrawListenere don't stop interrogating that view until it has provided you with a reasonable answerquindi le obiezioni alla tua soluzione dovrebbero essere ovvie.
Carrello abbandonato

Quello che devi sapere non è quando è stato disegnato ma quando è stato misurato. Il mio codice risponde alla domanda che deve davvero essere posta.
jwehrle,

C'è qualche problema con questa soluzione? Non riesce a risolvere il problema affrontato?
jwehrle,

1
La vista non viene misurata fino a quando non viene definita, quindi questa alternativa è eccessiva ed esegue un controllo ridondante. Ammetti di non rispondere alla domanda, ma di ciò che ritieni dovrebbe essere posto. Il commento originale ha fatto entrambi questi punti, ma ci sono anche alcuni problemi con il codice stesso che sono stati inviati come modifica.
Carrello abbandonato

Questa è una bella modifica. Grazie. Immagino che quello che chiedo sia: pensi che questa risposta debba essere cancellata o no? Ho riscontrato il problema del PO. Questo ha risolto il problema. L'ho offerto in buona fede. È stato un errore? È qualcosa che non dovrebbe essere fatto su StackOverflow?
jwehrle

2

Basta controllarlo chiamando il metodo post sul layout o sulla vista

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

Questo non è vero e potrebbe nascondere possibili problemi. postmetterà semplicemente in coda il runnable per l'esecuzione, non assicura che la vista sia misurata, ancor meno disegnata.
Ionut Negru

@IonutNegru forse leggere questo stackoverflow.com/a/21938380
Bruno Bieri

@BrunoBieri, è possibile testare questo comportamento con funzioni asincrone che modificano le misurazioni della vista e vedere se l'attività in coda viene eseguita dopo ogni misura e riceve i valori previsti.
Ionut Negru,

1

Quando onMeasureviene chiamato, la vista ottiene la sua larghezza / altezza misurate. Dopo questo, puoi chiamare layout.getMeasuredHeight().


4
Non dovresti creare la tua visualizzazione personalizzata per sapere quando onMeasure spara?
Geeks On Hugs,

Sì, devi utilizzare viste personalizzate per accedere su misura.
Trevor Hart,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.