La vista getWidth () y getHeight () devuelve 0

513

Estoy creando todos los elementos en mi proyecto de Android de forma dinámica. Estoy tratando de obtener el ancho y la altura de un botón para poder rotarlo. Solo estoy tratando de aprender a trabajar con el lenguaje Android. Sin embargo, devuelve 0.

Investigué un poco y vi que debe hacerse en otro lugar que no sea el onCreate()método. Si alguien me puede dar un ejemplo de cómo hacerlo, sería genial.

Aquí está mi código actual:

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

Cualquier ayuda es apreciada.

ngreenwood6
fuente

Respuestas:

197

Estás llamando getWidth()demasiado temprano. La interfaz de usuario aún no se ha dimensionado y presentado en la pantalla.

De todos modos, dudo que quiera hacer lo que está haciendo: los widgets que se animan no cambian sus áreas en las que se puede hacer clic, por lo que el botón seguirá respondiendo a los clics en la orientación original, independientemente de cómo haya girado.

Dicho esto, puede usar un recurso de dimensión para definir el tamaño del botón, luego hacer referencia a ese recurso de dimensión desde su archivo de diseño y su código fuente, para evitar este problema.

CommonsWare
fuente
82
ngreenwood6, ¿cuál es su otra solución?
Andrew
12
@ Andrew: si desea que se notifique a negreenwood6 sobre su seguimiento, debe comenzar su mensaje como le hice a usted (creo que las primeras tres letras son suficientes) - CommonsWare recibe una notificación automáticamente, ya que escribió esta respuesta, pero ngreen no a menos que te dirijas a ellos.
Peter Ajtai el
11
@Override public void onWindowFocusChanged (boolean hasFocus) {// TODO Stub de método generado automáticamente super.onWindowFocusChanged (hasFocus); // ¡Aquí puedes obtener el tamaño! }
Sana
55
@ ngreenwood6 Entonces, ¿cuál fue su solución?
Nima Vaziri
55
Use este oyente para obtener el tamaño, cuando esté lista su pantalla. view.getViewTreeObserver (). addOnGlobalLayoutListener (nuevo ViewTreeObserver.OnGlobalLayoutListener () {}
Sinan Dizdarević
816

El problema básico es que debe esperar la fase de dibujo para las mediciones reales (especialmente con valores dinámicos como wrap_contento match_parent), pero por lo general esta fase no se ha completado onResume(). Por lo tanto, necesita una solución alternativa para esperar esta fase. Hay diferentes soluciones posibles para esto:


1. Escuche los eventos de Draw / Layout: ViewTreeObserver

Se dispara un ViewTreeObserver para diferentes eventos de dibujo. Por lo general, esto OnGlobalLayoutListeneres lo que desea para obtener la medición, por lo que el código en el oyente se llamará después de la fase de diseño, para que las mediciones estén listas:

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

Nota: El oyente se eliminará inmediatamente porque, de lo contrario, se activará en cada evento de diseño. Si tiene que admitir aplicaciones SDK Lvl <16, use esto para cancelar el registro del oyente:

public void removeGlobalOnLayoutListener (ViewTreeObserver.OnGlobalLayoutListener victim)


2. Agregue un ejecutable a la cola de diseño: View.post ()

No muy conocido y mi solución favorita. Básicamente, solo use el método de publicación de View con su propio ejecutable. Básicamente, esto pone en cola su código después de la medida, el diseño, etc. de la vista según lo declarado por Romain Guy :

La cola de eventos de la IU procesará los eventos en orden. Después de invocar setContentView (), la cola de eventos contendrá un mensaje pidiendo una retransmisión, por lo que todo lo que publique en la cola sucederá después del pase de diseño

Ejemplo:

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

La ventaja sobre ViewTreeObserver:

  • su código solo se ejecuta una vez y no tiene que deshabilitar el Observador después de la ejecución, lo que puede ser una molestia
  • sintaxis menos detallada

Referencias


3. Sobrescribir el método onLayout de Views

Esto solo es práctico en ciertas situaciones cuando la lógica se puede encapsular en la vista misma, de lo contrario, esta es una sintaxis bastante detallada y engorrosa.

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

También tenga en cuenta que onLayout se llamará muchas veces, así que tenga en cuenta lo que hace en el método o desactive su código después de la primera vez


4. Compruebe si ha pasado por la fase de diseño

Si tiene código que se ejecuta varias veces mientras crea la interfaz de usuario, puede usar el siguiente método de soporte v4 lib:

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

Devuelve verdadero si la vista ha pasado por al menos un diseño desde la última vez que se adjuntó o desconectó de una ventana.


Adicional: obtener medidas estáticamente definidas

Si es suficiente obtener la altura / anchura estáticamente definidas, puede hacer esto con:

Pero tenga en cuenta que esto podría ser diferente al ancho / alto real después del dibujo. El javadoc describe la diferencia perfectamente:

El tamaño de una vista se expresa con un ancho y un alto. Una vista en realidad posee dos pares de valores de ancho y alto.

El primer par se conoce como ancho medido y altura medida. Estas dimensiones definen qué tan grande quiere ser una vista dentro de su elemento primario (consulte Diseño para obtener más detalles). Las dimensiones medidas se pueden obtener llamando a getMeasuredWidth () y getMeasuredHeight ().

El segundo par se conoce simplemente como ancho y alto, o a veces ancho y alto de dibujo. Estas dimensiones definen el tamaño real de la vista en pantalla, en el momento del dibujo y después del diseño. Estos valores pueden, pero no necesariamente, ser diferentes del ancho y la altura medidos. El ancho y el alto se pueden obtener llamando a getWidth () y getHeight ().

Patrick Favre
fuente
1
Estoy usando view.post en un fragmento. Necesito medir la vista cuya altura de los padres es 0dp y tiene un peso establecido. Todo lo que intento devuelve una altura de cero (también probé el oyente de diseño global). Cuando pongo el código view.post en onViewCreated con un retraso de 125, funciona, pero no sin el retraso. Ideas?
Psest328
66
Lástima que solo puedas votar una respuesta una vez, una muy buena explicación. Tuve un problema con la rotación de vistas si salía de mi aplicación y la reiniciaba desde las aplicaciones recientes. Si lo saqué de allí e hice un nuevo reinicio, todo estaba bien. El view.post () me salvó el día!
Matthias
1
Gracias. Usualmente uso View.post () (segundo método), pero si se llama desde un hilo de fondo, a veces no funciona. Entonces, no te olvides de escribir runOnUiThread(new Runnable() {...}. Actualmente utilizo una variación de la primera método: addOnLayoutChangeListener. No olvides eliminarlo luego dentro: removeOnLayoutChangeListener. Funciona desde el hilo de fondo también.
CoolMind
2
Desafortunadamente para mí 'View.post ()' no funciona todo el tiempo. Tengo 2 proyectos que son muy similares. En un proyecto funciona, en el otro no funciona. :(. En ambas situaciones, llamo al método desde el método 'onCreate ()' desde una Actividad. ¿Alguna sugerencia por qué?
Adrian Buciuman
1
Encuentro el problema. En un proyecto, la vista tiene android:visibility="gone", y en el otro proyecto, la propiedad de visibilidad era invisible. Si la visibilidad es goneel ancho siempre será 0.
Adrian Buciuman
262

Nosotros podemos usar

@Override
 public void onWindowFocusChanged(boolean hasFocus) {
  super.onWindowFocusChanged(hasFocus);
  //Here you can get the size!
 }
Sana
fuente
10
Entonces, ¿cómo podemos trabajar en Fragments? esto funciona solo en Activity?
Olkunmustafa
8
Esto debería hacer la cosa pero no debería ser la respuesta. Un usuario desea obtener dimensiones en cualquier momento en lugar de dibujar en la ventana. Además, este método se llama varias veces o cada vez que hay un cambio (Ver ocultar, desaparecer, agregar nuevas vistas, etc.) en la ventana. Así que
úsalo con
1
Este es un truco y parece que usar ViewTreeObserver sería mejor.
i_am_jorf
No quiero parecer grosero, pero dejar un comentario generado automáticamente (especialmente en 2 líneas de código) no da mucha confianza de que realmente sepas lo que estás haciendo.
Natix
Desafortunadamente, no funcionó para mí. Probar viewTreeObserver funcionó para mí.
Narendra Singh
109

Usé esta solución, que creo que es mejor que onWindowFocusChanged (). Si abre un DialogFragment, luego gira el teléfono, se llamará a onWindowFocusChanged solo cuando el usuario cierre el diálogo):

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

Editar: como removeGlobalOnLayoutListener está en desuso, ahora debe hacer:

@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 :)
}
Tim Autin
fuente
44
¡Esta es, con mucho, la mejor solución! porque funciona también en soluciones dinámicas donde el ancho y la altura no se conocen ni se calculan. es decir, en un diseño relativo basado en las posiciones / tamaños de otros elementos. ¡Bien hecho!
George Pligoropoulos
2
Esto requiere API 16 o superior (Jelly bean)
tomsv
77
¡NO REQUIERE API 16! El único problema es el método removeGlobalOnLayoutListener en desuso de API 16, pero hay una solución simple compatible con todos los niveles de api - stackoverflow.com/questions/16189525/…
gingo
Si necesita eliminar el oyente para que no se llame a sí mismo muchas veces y tenga problemas con diferentes API, le recomiendo que configure un booleano falso en la cabecera de la clase y luego, después de capturar los valores, configúrelo como verdadero para que no captura los valores nuevamente.
Mansour Fahad
Si elimina el oyente después de la primera ejecución, puede obtener dimensiones incorrectas: 02-14 10: 18: 53.786 15063-15063 / PuzzleFragment: altura: 1647 ancho: 996 02-14 10: 18: 53.985 15063-15063 / PuzzleFragment: altura : 1500 ancho: 996
Leos Literak
76

Como Ian afirma en este hilo de desarrolladores de Android :

De todos modos, el acuerdo es que el diseño del contenido de una ventana ocurre después de que todos los elementos se construyen y se agregan a sus vistas principales. Tiene que ser así, porque hasta que sepa qué componentes contiene una Vista, y qué contienen, y así sucesivamente, no hay una forma sensata de diseñarla.

En pocas palabras, si llama a getWidth (), etc. en un constructor, devolverá cero. El procedimiento consiste en crear todos sus elementos de vista en el constructor, luego esperar a que se invoque el método onSizeChanged () de su vista; es entonces cuando descubre por primera vez su tamaño real, por lo que es cuando configura los tamaños de sus elementos GUI.

Tenga en cuenta también que a veces se llama a onSizeChanged () con parámetros de cero: verifique este caso y regrese de inmediato (para que no obtenga una división entre cero al calcular su diseño, etc.). Algún tiempo después se llamará con los valores reales.

kerem yokuva
fuente
8
onSizeChanged funcionó para mí. onWindowFocusedChanged no se llama en mi vista personalizada.
aaronmarino
Esta parece una buena solución, pero ¿qué debería crear siempre mi vista personalizada para anular el método onSizeChanged?
M.ElSaka
Bottom line, if you call getWidth() etc. in a constructor, it will return zero.Eso es un bingo. La raíz de mis problemas.
MikeNereson
Sugiero esta solución para fragmentos, porque algunas otras soluciones devuelven 0 en mi caso.
WindRider
66

Si necesita obtener el ancho de algún widget antes de que se muestre en la pantalla, puede usar getMeasuredWidth () o getMeasuredHeight ().

myImage.measure(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
int width = myImage.getMeasuredWidth();
int height = myImage.getMeasuredHeight();
sulu.dev
fuente
55
Por alguna razón, esta fue la única respuesta que funcionó para mí, ¡gracias!
Farbod Salamat-Zadeh
Lo mismo para mí, la solución más simple, que funcionó para mí
Tima
¡Trabajar como un encanto!
Morales Batovski
1
@CommonsWare, ¿cómo proporciona esta solución la altura y el ancho antes del observador del árbol de vistas en la devolución de llamada de diseños globales, una explicación sería útil.
Mightian
22

Prefiero usar en OnPreDrawListener()lugar de addOnGlobalLayoutListener(), ya que se llama un poco antes que otros oyentes.

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

            // put your code here
            return true;
        }
    });
Ayaz Alifov
fuente
3
Tenga en cuenta que return falselos medios para cancelar el pase de dibujo actual . Para proceder, en su return truelugar.
Pang
9

Una extensión de Kotlin para observar en el diseño global y realizar una tarea determinada cuando la altura está lista dinámicamente.

Uso:

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

Implementación:

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)
}
Renetik
fuente
7

AndroidX tiene múltiples funciones de extensión que lo ayudan con este tipo de trabajo, dentro androidx.core.view

Necesitas usar Kotlin para esto.

El que mejor se ajusta aquí es doOnLayout:

Realiza la acción dada cuando se presenta esta vista. Si la vista se ha presentado y no ha solicitado un diseño, la acción se realizará de inmediato, de lo contrario, la acción se realizará después de que la vista se presente a continuación.

La acción solo se invocará una vez en el siguiente diseño y luego se eliminará.

En tu ejemplo:

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

Dependencia: androidx.core:core-ktx:1.0.0

Vlad
fuente
Esta es la única respuesta correcta. android.googlesource.com/platform/frameworks/support/+/…
hrules6872
1
estas extensiones son realmente importantes y útiles
Ahmed na
5

Un revestimiento si está utilizando RxJava y RxBindings . Enfoque similar sin el repetitivo. Esto también resuelve el truco para suprimir advertencias como en la respuesta de Tim Autin .

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

Eso es todo. No es necesario darse de baja, incluso si nunca se llama.

Odys
fuente
4

Ocurre porque la vista necesita más tiempo para inflarse. Entonces, en lugar de llamar view.widthy view.heighten el hilo principal, debe usar view.post { ... }para asegurarse de que su viewya se ha inflado. En Kotlin:

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

En Java, también puede llamar getWidth()y getHeight()métodos de una Runnabley pasar el Runnablea view.post()método.

view.post(new Runnable() {
        @Override
        public void run() {
            view.getWidth(); 
            view.getHeight();
        }
    });
Ehsan Nokandi
fuente
Esta no es la respuesta correcta, ya que es posible que la vista no se mida ni se presente cuando se ejecuta el código publicado. Sería un caso extremo, pero .postno garantiza que la vista sea presentada.
d.aemon
1
Esta respuesta fue genial y funcionó para mí. Muchas gracias Ehsan
MMG
3

La altura y el ancho son cero porque la vista no se ha creado en el momento en que solicita su altura y ancho. Una solución más simple es

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

Este método es bueno en comparación con otros métodos, ya que es corto y crujiente.

Shivam Yadav
fuente
3

Quizás esto ayude a alguien:

Crear una función de extensión para la clase Ver

nombre de archivo: 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()
            }
        })
    }
}

Esto se puede usar en cualquier vista con:

view.afterLayout {
    do something with view.height
}
Pepijn
fuente
2

La respuesta con postes incorrecta, ya que es posible que el tamaño no se vuelva a calcular.
Otra cosa importante es que la vista y todos sus antepasados ​​deben ser visibles . Para eso uso una propiedad View.isShown.

Aquí está mi función kotlin, que se puede colocar en algún lugar en utils:

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

Y el uso es:

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

Si está 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
            }
        })
Hitesh Sahu
fuente
0

Las vistas desaparecidas devuelven 0 como altura si la aplicación está en segundo plano. Este es mi código (1oo% funciona)

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

fuente
0

Necesitamos esperar a que se dibuje la vista. Para este propósito, use OnPreDrawListener. Ejemplo de 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)
Artem Botnev
fuente