Visualizza getWidth () e getHeight () restituisce 0


513

Sto creando tutti gli elementi nel mio progetto Android in modo dinamico. Sto cercando di ottenere la larghezza e l'altezza di un pulsante in modo da poter ruotare quel pulsante. Sto solo cercando di imparare a lavorare con la lingua Android. Tuttavia, restituisce 0.

Ho fatto alcune ricerche e ho visto che deve essere fatto altrove rispetto al onCreate()metodo. Se qualcuno può darmi un esempio di come farlo, sarebbe fantastico.

Ecco il mio codice attuale:

package com.animation;

import android.app.Activity;
import android.os.Bundle;
import android.view.animation.Animation;
import android.view.animation.LinearInterpolator;
import android.view.animation.RotateAnimation;
import android.widget.Button;
import android.widget.LinearLayout;

public class AnimateScreen extends Activity {


//Called when the activity is first created.
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    LinearLayout ll = new LinearLayout(this);

    LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
    layoutParams.setMargins(30, 20, 30, 0);

    Button bt = new Button(this);
    bt.setText(String.valueOf(bt.getWidth()));

    RotateAnimation ra = new RotateAnimation(0,360,bt.getWidth() / 2,bt.getHeight() / 2);
    ra.setDuration(3000L);
    ra.setRepeatMode(Animation.RESTART);
    ra.setRepeatCount(Animation.INFINITE);
    ra.setInterpolator(new LinearInterpolator());

    bt.startAnimation(ra);

    ll.addView(bt,layoutParams);

    setContentView(ll);
}

Qualsiasi aiuto è apprezzato.

Risposte:


197

Stai chiamando getWidth()troppo presto. L'interfaccia utente non è stata ancora dimensionata e strutturata sullo schermo.

Dubito che tu voglia fare quello che stai facendo, comunque: i widget animati non cambiano le loro aree cliccabili, e quindi il pulsante risponderà comunque ai clic nell'orientamento originale indipendentemente da come ha ruotato.

Detto questo, è possibile utilizzare una risorsa dimensione per definire la dimensione del pulsante, quindi fare riferimento a tale risorsa dimensione dal file di layout e dal codice sorgente, per evitare questo problema.


82
ngreenwood6, qual è la tua altra soluzione?
Andrew,

12
@Andrew - se vuoi che negreenwood6 riceva una notifica del tuo follow-up, devi iniziare il tuo messaggio come ti ho fatto (penso che le prime tre lettere siano sufficienti) - CommonsWare viene avvisato automaticamente, dal momento che ha scritto questa risposta, ma ngreen no a meno che tu non li indirizzi.
Peter Ajtai,

11
@Override public void onWindowFocusChanged (hasFocus booleano) {// TODO Metodo generato automaticamente stub super.onWindowFocusChanged (hasFocus); // Qui puoi ottenere le dimensioni! }
Sana,

5
@ ngreenwood6 Qual è stata la tua soluzione?
Nima Vaziri,

5
Usa questo ascoltatore per ottenere le dimensioni, quando lo schermo è pronto. view.getViewTreeObserver (). addOnGlobalLayoutListener (nuovo ViewTreeObserver.OnGlobalLayoutListener () {}
Sinan Dizdarević

816

Il problema di base è che devi attendere la fase di disegno per le misurazioni effettive (specialmente con valori dinamici come wrap_contento match_parent), ma di solito questa fase non è stata completata onResume(). Quindi hai bisogno di una soluzione alternativa per aspettare questa fase. Esistono diverse possibili soluzioni a questo:


1. Ascolta gli eventi di disegno / layout: ViewTreeObserver

Un ViewTreeObserver viene attivato per diversi eventi di disegno. Di solito OnGlobalLayoutListenerè quello che vuoi per ottenere la misurazione, quindi il codice nel listener verrà chiamato dopo la fase di layout, quindi le misure sono pronte:

view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                view.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                view.getHeight(); //height is ready
            }
        });

Nota: il listener verrà immediatamente rimosso perché altrimenti si attiverà su ogni evento di layout. Se devi supportare app SDK Lvl <16 utilizzalo per annullare la registrazione del listener:

public void removeGlobalOnLayoutListener (ViewTreeObserver.OnGlobalLayoutListener victim)


2. Aggiungi un file eseguibile alla coda di layout: View.post ()

Non molto noto e la mia soluzione preferita. Fondamentalmente basta usare il metodo post di View con il tuo runnable. Questo in pratica mette in coda il codice dopo la misura, il layout, ecc. Della vista, come affermato da Romain Guy :

La coda eventi dell'interfaccia utente elaborerà gli eventi in ordine. Dopo aver richiamato setContentView (), la coda degli eventi conterrà un messaggio che richiede un inoltro, quindi tutto ciò che pubblichi nella coda avverrà dopo il passaggio del layout

Esempio:

final View view=//smth;
...
view.post(new Runnable() {
            @Override
            public void run() {
                view.getHeight(); //height is ready
            }
        });

Il vantaggio rispetto a ViewTreeObserver:

  • il tuo codice viene eseguito una sola volta e non è necessario disabilitare Observer dopo l'esecuzione, il che può essere una seccatura
  • sintassi meno dettagliata

Riferimenti:


3. Sovrascrivi il metodo onLayout di Views

Ciò è pratico solo in determinate situazioni in cui la logica può essere incapsulata nella vista stessa, altrimenti si tratta di una sintassi piuttosto dettagliata e ingombrante.

view = new View(this) {
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        view.getHeight(); //height is ready
    }
};

Ricorda inoltre che onLayout verrà chiamato molte volte, quindi considera attentamente ciò che fai nel metodo o disabilita il codice dopo la prima volta


4. Verificare se è stata eseguita la fase di layout

Se si dispone di codice che viene eseguito più volte durante la creazione dell'interfaccia utente, è possibile utilizzare il seguente metodo lib v4 di supporto:

View viewYouNeedHeightFrom = ...
...
if(ViewCompat.isLaidOut(viewYouNeedHeightFrom)) {
   viewYouNeedHeightFrom.getHeight();
}

Restituisce vero se la vista ha attraversato almeno un layout dall'ultima volta in cui è stata collegata o scollegata da una finestra.


Ulteriori: ottenere misurazioni definite staticamente

Se è sufficiente ottenere l'altezza / larghezza definite staticamente, puoi farlo con:

Ma attenzione, questo potrebbe essere diverso dalla larghezza / altezza effettive dopo il disegno. Il javadoc descrive perfettamente la differenza:

La dimensione di una vista è espressa con una larghezza e un'altezza. Una vista in realtà possiede due coppie di valori di larghezza e altezza.

La prima coppia è nota come larghezza misurata e altezza misurata. Queste dimensioni definiscono la dimensione massima di una vista all'interno del padre (per ulteriori dettagli, vedere Layout). Le dimensioni misurate possono essere ottenute chiamando getMeasuredWidth () e getMeasuredHeight ().

La seconda coppia è semplicemente conosciuta come larghezza e altezza, o talvolta larghezza e altezza del disegno. Queste dimensioni definiscono la dimensione effettiva della vista sullo schermo, al momento del disegno e dopo il layout. Questi valori possono, ma non devono, essere diversi dalla larghezza e dall'altezza misurate. La larghezza e l'altezza possono essere ottenute chiamando getWidth () e getHeight ().


1
Sto usando view.post in un frammento. Ho bisogno di misurare la vista la cui altezza del genitore è 0dp e ha un peso impostato. Tutto ciò che provo restituisce un'altezza pari a zero (ho anche provato l'ascoltatore di layout globale). Quando inserisco il codice view.post in onViewCreated con un ritardo di 125, funziona, ma non senza il ritardo. Idee?
Psest328,

6
peccato che tu possa votare una sola volta una risposta, spiegazione davvero buona. Ho avuto un problema con una rotazione della vista se ho chiuso la mia app e riavviata dalle app recenti. Se l'ho fatto scorrere da lì e ho fatto un nuovo riavvio, tutto andava bene. Il view.post () mi ha salvato la giornata!
Matthias,

1
Grazie. Di solito uso View.post () (secondo metodo), ma se viene chiamato da un thread in background a volte non funziona. Quindi, non dimenticare di scrivere runOnUiThread(new Runnable() {...}. Attualmente io uso una variante del 1 ° metodo: addOnLayoutChangeListener. Non dimenticare di rimuoverlo quindi all'interno: removeOnLayoutChangeListener. Funziona anche da thread in background.
CoolMind

2
Sfortunatamente per me 'View.post ()' non funziona sempre. Ho 2 progetti molto simili. In un progetto funziona, dall'altro non funziona. :(. In entrambe le situazioni chiamo il metodo dal metodo 'onCreate ()' da un'attività. Qualche suggerimento sul perché?
Adrian Buciuman,

1
Trovo il problema. In un progetto la vista ha android:visibility="gone", e nell'altro progetto la proprietà di visibilità era invisible. Se la visibilità è gonela larghezza sarà sempre 0.
Adrian Buciuman,

262

Possiamo usare

@Override
 public void onWindowFocusChanged(boolean hasFocus) {
  super.onWindowFocusChanged(hasFocus);
  //Here you can get the size!
 }

10
quindi come possiamo lavorare in Fragments? funziona solo in Activity?
Olkunmustafa,

8
Questo dovrebbe fare la cosa ma non dovrebbe essere la risposta. Un utente vuole ottenere le dimensioni in qualsiasi momento anziché essere disegnato sulla finestra. Inoltre, questo metodo viene chiamato più volte o ogni volta che si verifica una modifica (Visualizza nascondi, scomparso, aggiunta di nuove visualizzazioni ecc.) Nella finestra. Quindi
usalo

1
Questo è un trucco e sembra che usare ViewTreeObserver sarebbe meglio.
i_am_jorf

Non intendo sembrare sgarbato, ma lasciare un commento generato automaticamente (specialmente in 2 righe di codice) non dà molta fiducia nel fatto che tu sappia effettivamente cosa stai facendo.
Natix,

Sfortunatamente, non ha funzionato per me. Vista di provaTreeObserver ha funzionato per me.
Narendra Singh,

109

Ho usato questa soluzione, che penso sia migliore di onWindowFocusChanged (). Se si apre un DialogFragment, quindi si ruota il telefono, onWindowFocusChanged verrà chiamato solo quando l'utente chiude la finestra di dialogo):

    yourView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {

        @Override
        public void onGlobalLayout() {
            // Ensure you call it only once :
            yourView.getViewTreeObserver().removeGlobalOnLayoutListener(this);

            // Here you can get the size :)
        }
    });

Modifica: poiché removeGlobalOnLayoutListener è obsoleto, ora dovresti fare:

@SuppressLint("NewApi")
@SuppressWarnings("deprecation")
@Override
public void onGlobalLayout() {

    // Ensure you call it only once :
    if(android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
        yourView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
    }
    else {
        yourView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
    }

    // Here you can get the size :)
}

4
questa è di gran lunga la soluzione migliore! perché funziona anche su soluzioni dinamiche in cui la larghezza e l'altezza non sono note e calcolate. cioè in una disposizione relativa basata sulle posizioni / dimensioni di altri elementi. Molto bene!
George Pligoropoulos,

2
Ciò richiede API 16 o superiore (Jelly bean)
tomsv

7
NON RICHIEDE API 16! L'unico problema è metodo removeGlobalOnLayoutListener deprecato da API 16, ma c'è soluzione semplice compatibile con tutti i livelli api - stackoverflow.com/questions/16189525/...
Gingo

Se devi rimuovere il listener in modo che non si chiami più volte e riscontri problemi con diverse API, ti consiglio di impostare un falso booleano in testa alla classe e poi, dopo aver catturato i valori, impostarlo su vero in modo che non cattura di nuovo i valori.
Mansour Fahad,

Se rimuovi l'ascoltatore dopo la prima corsa potresti avere dimensioni errate: 02-14 10: 18: 53.786 15063-15063 / PuzzleFragment: altezza: 1647 larghezza: 996 02-14 10: 18: 53.985 15063-15063 / PuzzleFragment: altezza : 1500 larghezza: 996
Leos Literak

76

Come afferma Ian in questo thread di sviluppatori Android :

Ad ogni modo, l'affare è che il layout del contenuto di una finestra avviene dopo che tutti gli elementi sono stati costruiti e aggiunti alle loro viste principali. Deve essere così, perché fino a quando non sai quali componenti contiene una Vista, cosa contengono e così via, non c'è modo sensato di poterlo disporre.

In conclusione, se si chiama getWidth () ecc. In un costruttore, verrà restituito zero. La procedura consiste nel creare tutti gli elementi della vista nel costruttore, quindi attendere che venga chiamato il metodo onSizeChanged () della vista, ovvero quando si rilevano per la prima volta le dimensioni reali, quindi è quando si impostano le dimensioni degli elementi della GUI.

Ricorda anche che a volte onSizeChanged () viene chiamato con i parametri zero: controlla questo caso e ritorna immediatamente (in modo da non ottenere una divisione per zero durante il calcolo del layout, ecc.). Qualche tempo dopo verrà chiamato con i valori reali.


8
onSizeChanged ha funzionato per me. onWindowFocusedChanged non viene chiamato nella mia vista personalizzata.
aaronmarino,

sembra una buona soluzione, ma cosa devo sempre creare la mia vista personalizzata per sovrascrivere il metodo onSizeChanged?
M.ElSaka,

Bottom line, if you call getWidth() etc. in a constructor, it will return zero.quello è un bingo. La radice dei miei problemi.
MikeNereson,

Suggerisco questa soluzione per i frammenti, perché alcune altre soluzioni restituiscono 0 nel mio caso.
WindRider,

66

Se è necessario ottenere la larghezza di alcuni widget prima che vengano visualizzati sullo schermo, è possibile utilizzare getMeasuredWidth () o getMeasuredHeight ().

myImage.measure(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
int width = myImage.getMeasuredWidth();
int height = myImage.getMeasuredHeight();

5
Per qualche motivo, questa è stata l'unica risposta che ha funzionato per me - grazie!
Farbod Salamat-Zadeh,

Lo stesso per me, la soluzione più semplice, che ha funzionato per me
Tima,

Lavora come un fascino!
Morales Batovski,

1
@CommonsWare in che modo questa soluzione fornisce l'altezza e la larghezza prima che l'osservatore dell'albero di visualizzazione sul callback dei layout globali sia utile per una spiegazione.
Mightian il

22

Preferirei usare OnPreDrawListener()invece di addOnGlobalLayoutListener(), poiché viene chiamato un po 'prima rispetto ad altri ascoltatori.

    view.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener()
    {
        @Override
        public boolean onPreDraw()
        {
            if (view.getViewTreeObserver().isAlive())
                view.getViewTreeObserver().removeOnPreDrawListener(this);

            // put your code here
            return true;
        }
    });

3
Si noti che i return falsemezzi per annullare il passo disegno corrente . Per procedere, return trueinvece.
Provò il

9

Un'estensione Kotlin da osservare sul layout globale ed eseguire una determinata attività quando l'altezza è pronta in modo dinamico.

Uso:

view.height { Log.i("Info", "Here is your height:" + it) }

Implementazione:

fun <T : View> T.height(function: (Int) -> Unit) {
    if (height == 0)
        viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
            override fun onGlobalLayout() {
                viewTreeObserver.removeOnGlobalLayoutListener(this)
                function(height)
            }
        })
    else function(height)
}

7

AndroidX ha diverse funzioni di estensione che ti aiutano in questo tipo di lavoro, all'interno androidx.core.view

Devi usare Kotlin per questo.

Quello che si adatta meglio qui è doOnLayout:

Esegue l'azione indicata quando questa vista è definita. Se la vista è stata disposta e non ha richiesto un layout, l'azione verrà eseguita immediatamente altrimenti, l'azione verrà eseguita dopo la successiva visualizzazione della vista.

L'azione verrà invocata una sola volta sul layout successivo e quindi rimossa.

Nel tuo esempio:

bt.doOnLayout {
    val ra = RotateAnimation(0,360,it.width / 2,it.height / 2)
    // more code
}

Dipendenza: androidx.core:core-ktx:1.0.0



1
queste estensioni sono davvero importanti e utili
Ahmed na

5

Una riga se stai usando RxJava e RxBindings . Approccio simile senza il boilerplate. Questo risolve anche l'hacking per sopprimere gli avvisi come nella risposta di Tim Autin .

RxView.layoutChanges(yourView).take(1)
      .subscribe(aVoid -> {
           // width and height have been calculated here
      });

Questo è. Non è necessario annullare l'iscrizione, anche se non è mai stato chiamato.


4

Succede perché la vista richiede più tempo per essere gonfiata. Quindi, invece di chiamare view.widthe view.heightsul thread principale, dovresti view.post { ... }assicurarti che il tuo viewsia già stato gonfiato. A Kotlin:

view.post{width}
view.post{height}

In Java puoi anche chiamare getWidth()e getHeight()metodi in a Runnablee passare il metodo Runnableto view.post().

view.post(new Runnable() {
        @Override
        public void run() {
            view.getWidth(); 
            view.getHeight();
        }
    });

Questa non è una risposta corretta poiché la vista potrebbe non essere misurata e disposta quando viene eseguito il codice pubblicato. Sarebbe un caso limite, ma .postnon garantisce la visione da definire.
Demone

1
Questa risposta è stata grandiosa e ha funzionato per me. Grazie mille Ehsan
MMG

3

L'altezza e la larghezza sono zero perché la vista non è stata creata al momento della richiesta di altezza e larghezza. Una soluzione più semplice è

view.post(new Runnable() {
        @Override
        public void run() {
            view.getHeight(); //height is ready
            view.getWidth(); //width is ready
        }
    });

Questo metodo è buono rispetto ad altri metodi in quanto è breve e nitido.


3

Forse questo aiuta qualcuno:

Creare una funzione di estensione per la classe View

nome file: ViewExt.kt

fun View.afterLayout(what: () -> Unit) {
    if(isLaidOut) {
        what.invoke()
    } else {
        viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
            override fun onGlobalLayout() {
                viewTreeObserver.removeOnGlobalLayoutListener(this)
                what.invoke()
            }
        })
    }
}

Questo può quindi essere utilizzato su qualsiasi vista con:

view.afterLayout {
    do something with view.height
}

2

La risposta con postnon è corretta, perché la dimensione potrebbe non essere ricalcolata.
Un'altra cosa importante è che la vista e tutti i suoi antenati devono essere visibili . Per questo uso una proprietà View.isShown.

Ecco la mia funzione kotlin, che può essere posizionata da qualche parte in utils:

fun View.onInitialized(onInit: () -> Unit) {
    viewTreeObserver.addOnGlobalLayoutListener(object : OnGlobalLayoutListener {
        override fun onGlobalLayout() {
            if (isShown) {
                viewTreeObserver.removeOnGlobalLayoutListener(this)
                onInit()
            }
        }
    })
}

E l'uso è:

myView.onInitialized {
    Log.d(TAG, "width is: " + myView.width)
}

1

Se stai usando Kotlin

  leftPanel.viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {

            override fun onGlobalLayout() {

                if(android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
                    leftPanel.viewTreeObserver.removeOnGlobalLayoutListener(this)
                }
                else {
                    leftPanel.viewTreeObserver.removeGlobalOnLayoutListener(this)
                }

                // Here you can get the size :)
                leftThreshold = leftPanel.width
            }
        })

0

Le visualizzazioni andate restituiscono 0 come altezza se l'app in background. Questo mio codice (1oo% funziona)

fun View.postWithTreeObserver(postJob: (View, Int, Int) -> Unit) {
    viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
        override fun onGlobalLayout() {
            val widthSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
            val heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
            measure(widthSpec, heightSpec)
            postJob(this@postWithTreeObserver, measuredWidth, measuredHeight)
            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
                @Suppress("DEPRECATION")
                viewTreeObserver.removeGlobalOnLayoutListener(this)
            } else {
                viewTreeObserver.removeOnGlobalLayoutListener(this)
            }
        }
    })
}

0

Dobbiamo aspettare che venga disegnata la vista. A tale scopo utilizzare OnPreDrawListener. Esempio di Kotlin:

val preDrawListener = object : ViewTreeObserver.OnPreDrawListener {

                override fun onPreDraw(): Boolean {
                    view.viewTreeObserver.removeOnPreDrawListener(this)

                    // code which requires view size parameters

                    return true
                }
            }

            view.viewTreeObserver.addOnPreDrawListener(preDrawListener)
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.