¿Cómo recuperar las dimensiones de una vista?

209

Tengo una vista hecha de TableLayout, TableRow and TextView. Quiero que se vea como una cuadrícula. Necesito obtener la altura y el ancho de esta cuadrícula. Los métodos getHeight()y getWidth()siempre devuelven 0. Esto sucede cuando formateo la cuadrícula dinámicamente y también cuando uso una versión XML.

¿Cómo recuperar las dimensiones de una vista?


Aquí está mi programa de prueba que utilicé en Debug para verificar los resultados:

import android.app.Activity;
import android.os.Bundle;
import android.widget.TableLayout;
import android.widget.TextView;

public class appwig extends Activity {  
    @Override
    public void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.maindemo);  //<- includes the grid called "board"
      int vh = 0;   
      int vw = 0;

      //Test-1 used the xml layout (which is displayed on the screen):
      TableLayout tl = (TableLayout) findViewById(R.id.board);  
      tl = (TableLayout) findViewById(R.id.board);
      vh = tl.getHeight();     //<- getHeight returned 0, Why?  
      vw = tl.getWidth();     //<- getWidth returned 0, Why?   

      //Test-2 used a simple dynamically generated view:        
      TextView tv = new TextView(this);
      tv.setHeight(20);
      tv.setWidth(20);
      vh = tv.getHeight();    //<- getHeight returned 0, Why?       
      vw = tv.getWidth();    //<- getWidth returned 0, Why?

    } //eof method
} //eof class
Sagar Zala
fuente
en lugar de usar getWidth / Height, use getMeasuredWidth / Height después de aplicar el diseño a la actividad.
Bhups
9
Chicos, todo lo que uno tiene que hacer es llamar getHeight()y getWidth()después del ciclo de vida de la Actividad ha pedido a las opiniones que se midan, en otras palabras, hacer este tipo de cosas onResume()y eso es todo. No debe esperar que un objeto aún no diseñado sepa sus dimensiones, ese es el truco completo. No hay necesidad de la magia sugerida a continuación.
apilador de clase el
2
Llamando getHeight()/Width()en onResume()no dió> 0 valor para mí.
Darpan
@ClassStacker ¿Qué pasa con Fragment?
zdd
@zdd Haga una nueva pregunta completa y publique una referencia aquí en un comentario.
apilador de clase

Respuestas:

418

Creo que el OP ya no existe, pero en caso de que esta respuesta pueda ayudar a los futuros buscadores, pensé en publicar una solución que he encontrado. He agregado este código a mi onCreate()método:

EDITADO: 05/07/11 para incluir el código de los comentarios:

final TextView tv = (TextView)findViewById(R.id.image_test);
ViewTreeObserver vto = tv.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {

    @Override
    public void onGlobalLayout() {
        LayerDrawable ld = (LayerDrawable)tv.getBackground();
        ld.setLayerInset(1, 0, tv.getHeight() / 2, 0, 0);
        ViewTreeObserver obs = tv.getViewTreeObserver();

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
            obs.removeOnGlobalLayoutListener(this);
        } else {
            obs.removeGlobalOnLayoutListener(this);
        }
    }

});

Primero obtengo una referencia final a mi TextView(para acceder en el onGlobalLayout()método). A continuación, obtengo el ViewTreeObserverde my TextViewy agrego una OnGlobalLayoutListeneranulación onGLobalLayout(no parece haber un método de superclase para invocar aquí ...) y agrego mi código que requiere conocer las medidas de la vista en este oyente. Todo funciona como se esperaba para mí, así que espero que esto pueda ayudar.

Kevin Coppock
fuente
23
@George Bailey: Una cosa que recientemente he visto a otra persona publicar (no lo he probado yo mismo, así que YMMV) para superar las múltiples llamadas fue (dentro de onGlobalLayout ()): de tv.getViewTreeObserver().removeGlobalOnLayoutListener(this);esa manera el oyente debería ser eliminado después de la primera vez se produce
Kevin Coppock el
3
Quiero ayudar de vuelta también. Es posible que deba hacer algo como: mView.getViewTreeObserver (). RemoveGlobalOnLayoutListener (globalLayoutListener); si desea cambiar el tamaño de su vista solo una vez. Espero que esta ayuda
Henry Sou
77
La pregunta es muy antigua, ¡pero el uso también addOnLayoutChangeListenerfunciona! :)
FX
77
Además, tenga en cuenta que desde API 16, necesita: if (android.os.Build.VERSION.SDK_INT> = 16) {view.getViewTreeObserver (). RemoveOnGlobalLayoutListener (this); } else {view.getViewTreeObserver (). removeGlobalOnLayoutListener (this); }
Edison
2
Como removeGlobalOnLayoutListener está en desuso, todavía da advertencias en Eclipse. ¿Esto es normal? Con el tiempo, las advertencias de código obsoletas se inundarían por todas partes ...
Jonny
82

Solo agregaré una solución alternativa, anularé el método onWindowFocusChanged de tu actividad y podrás obtener los valores de getHeight (), getWidth () desde allí.

@Override
public void onWindowFocusChanged (boolean hasFocus) {
        // the height will be set at this point
        int height = myEverySoTallView.getMeasuredHeight(); 
}
JustDanyul
fuente
44
Para citar la documentación, "Este es el mejor indicador de si esta actividad es visible para el usuario".
Cameron Lowell Palmer
66
Esto no funciona en el caso de Fragments para lo que tienes que usar ViewTreeObserver.
Mihir
Sin embargo
JustDanyul
scrollview altura total y max Scrollview.getScrolly () serán iguales? es decir, en mi caso obtuve Altura con el método anterior es 702 y obtuve el valor máximo de getScrolly () es 523,
Hitesh Dhamshaniya
Este método tampoco funciona cuando su Vista se incluye desde otro archivo xml.
Jay Donga el
19

Está tratando de obtener el ancho y la altura de un elemento que aún no se dibujó.

Si usa la depuración y se detiene en algún momento, verá que la pantalla de su dispositivo todavía está vacía, porque sus elementos aún no se dibujaron, por lo que no puede obtener el ancho y la altura de algo, eso todavía no existe.

Y, podría estar equivocado, pero setWidth() no siempre se respeta, Layoutestablece sus hijos y decide cómo medirlos (llamando child.measure()), por lo que si configura setWidth(), no está garantizado que obtenga este ancho después de que se dibuje el elemento.

Lo que necesita es usar getMeasuredWidth()(la medida más reciente de su Vista) en algún lugar después de que la vista se dibujó realmente.

Examina el Activityciclo de vida para encontrar el mejor momento.

http://developer.android.com/reference/android/app/Activity.html#ActivityLifecycle

Creo que una buena práctica es usar OnGlobalLayoutListenerasí:

yourView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
        @Override
        public void onGlobalLayout() {
            if (!mMeasured) {
                // Here your view is already layed out and measured for the first time
                mMeasured = true; // Some optional flag to mark, that we already got the sizes
            }
        }
    });

Puede colocar este código directamente onCreate(), y se invocará cuando se presenten las vistas.

Alex Orlov
fuente
Lamentablemente, tampoco funciona en OnResume. Las dimensiones finales se establecen en algún lugar después de la llamada OnResume, al menos en el emulador.
jki
¡Esa es una práctica horrible! Este oyente será llamado una y otra vez y matará su rendimiento. En lugar de usar banderas, anule el registro del oyente una vez que haya terminado.
bluewhile
15

Use el método de publicación de la Vista de esta manera

post(new Runnable() {   
    @Override
    public void run() {
        Log.d(TAG, "width " + MyView.this.getMeasuredWidth());
        }
    });
Boris Rusev
fuente
1
Eso funciona, pero realmente, ¿no hay discusión de por qué? Por cierto, esto funciona porque el ejecutable no se ejecuta hasta que se haya producido el diseño.
Norman H
7

Intenté usar onGlobalLayout()para hacer un formateo personalizado de a TextView, pero como lo notó @George Bailey, en onGlobalLayout()realidad se llama dos veces: una en la ruta de diseño inicial y la segunda después de modificar el texto.

View.onSizeChanged()funciona mejor para mí porque si modifico el texto allí, el método se llama solo una vez (durante el pase de diseño). Esto requería una subclasificación de TextView, pero en API Nivel 11+View. addOnLayoutChangeListener() puede usarse para evitar la subclasificación.

Una cosa más, para obtener el ancho correcto de la vista View.onSizeChanged(), layout_widthdebe establecerse en match_parent, no wrap_content.

Y2i
fuente
2

¿Está tratando de obtener tamaños en un constructor o cualquier otro método que se ejecuta ANTES de obtener la imagen real?

No obtendrá ninguna dimensión antes de que se midan todos los componentes (ya que su xml no conoce el tamaño de su pantalla, las posiciones de los padres y lo que sea)

Intente obtener valores después de onSizeChanged () (aunque se puede llamar con cero), o simplemente espere cuando obtenga una imagen real.

Alex Orlov
fuente
Actualicé mi pregunta original y mi código para ser más claro. Gracias.
2

Como mencionó FX, puede usar un OnLayoutChangeListenerpara la vista que desea rastrear

view.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
    @Override
    public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
        // Make changes
    }
});

Puede eliminar el oyente en la devolución de llamada si solo desea el diseño inicial.

Zeoflita
fuente
1

ViewTreeObserver y onWindowFocusChanged() no son tan necesarios en absoluto.

Si infla el TextViewdiseño como y / o pone algo de contenido en él y lo configura LayoutParams, puede usar getMeasuredHeight()ygetMeasuredWidth() .

PERO tienes que tener cuidado conLinearLayouts (tal vez también con otros ViewGroups). El problema es que puede obtener el ancho y la altura después, onWindowFocusChanged()pero si intenta agregar algunas vistas, no puede obtener esa información hasta que se haya dibujado todo. Intenté agregar múltiples TextViewspara LinearLayoutsimitar un FlowLayout(estilo de ajuste) y, por lo tanto, no pude usarlo Listeners. Una vez que se inicia el proceso, debe continuar sincrónicamente. Entonces, en tal caso, es posible que desee mantener el ancho en una variable para usarlo más tarde, ya que al agregar vistas al diseño, es posible que lo necesite.

Genom
fuente
1

A pesar de que la solución propuesta funciona, podría no ser la mejor solución para cada caso porque, según la documentación de ViewTreeObserver.OnGlobalLayoutListener

Definición de interfaz para invocar una devolución de llamada cuando cambia el estado del diseño global o la visibilidad de las vistas dentro del árbol de vistas.

lo que significa que se llama muchas veces y no siempre se mide la vista (tiene su altura y ancho determinados)

Una alternativa es usar lo ViewTreeObserver.OnPreDrawListenerque se llama solo cuando la vista está lista para ser dibujada y tiene todas sus medidas.

final TextView tv = (TextView)findViewById(R.id.image_test);
ViewTreeObserver vto = tv.getViewTreeObserver();
vto.addOnPreDrawListener(new OnPreDrawListener() {

    @Override
    public void onPreDraw() {
        tv.getViewTreeObserver().removeOnPreDrawListener(this);
        // Your view will have valid height and width at this point
        tv.getHeight();
        tv.getWidth();
    }

});
Kayvan N
fuente
0

Puede usar una transmisión que se llama en OnResume ()

Por ejemplo:

int vh = 0;   
int vw = 0;

public void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.maindemo);  //<- includes the grid called "board"

      registerReceiver(new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                TableLayout tl = (TableLayout) findViewById(R.id.board);  
                tl = (TableLayout) findViewById(R.id.board);
                vh = tl.getHeight();       
                vw = tl.getWidth();               
            }
      }, new IntentFilter("Test"));
}

protected void onResume() {
        super.onResume();

        Intent it = new Intent("Test");
        sendBroadcast(it);

}

No puede obtener la altura de una vista en OnCreate (), onStart (), o incluso en onResume () por la razón que respondió kcoppock

Lucas Ferreira Batista
fuente
0

La altura y el ancho son cero porque la vista no se ha creado cuando 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
-1

Respuesta simple: Esto funcionó para mí sin ningún problema. Parece que la clave es asegurarse de que la vista tenga el foco antes de obtener la altura, etc. Haga esto usando el método hasFocus (), luego usando el método getHeight () en ese orden. Solo se requieren 3 líneas de código.

ImageButton myImageButton1 =(ImageButton)findViewById(R.id.imageButton1); myImageButton1.hasFocus();

int myButtonHeight = myImageButton1.getHeight();

Log.d("Button Height: ", ""+myButtonHeight );//Not required

Espero eso ayude.

Monk4D
fuente
-3

Use getMeasuredWidth () y getMeasuredHeight () para su vista.

Guía del desarrollador: Ver

Vladimir Ivanov
fuente
44
Como se ha dicho antes de que estos métodos sólo devolverá un resultado válido efter el tamaño se ha meassured. Dentro de una actividad, esto es válido cuando se llama al método onWindowFocusChanged os.
slott
-8

CORRECCIÓN: Descubrí que la solución anterior es terrible. Especialmente cuando tu teléfono está lento. Y aquí, encontré otra solución: calcular el valor de px del elemento, incluidos los márgenes y los rellenos: dp a px: https://stackoverflow.com/a/6327095/1982712

o dimens.xml a px: https://stackoverflow.com/a/16276351/1982712

sp a px: https://stackoverflow.com/a/9219417/1982712 (revierta la solución)

o dimens a px: https://stackoverflow.com/a/16276351/1982712

y eso es.

espinilla
fuente
10
Por lo general, es una mala idea esperar que todo se configure correctamente después de un número de milisegundos elegido arbitrariamente. El dispositivo podría ser lento, otros procesos / subprocesos podrían estar ejecutándose, podría no funcionar en el depurador, podría no funcionar en la próxima versión del sistema operativo, ...
Kristopher Johnson