Determinar el tamaño de una vista de Android en tiempo de ejecución

123

Estoy tratando de aplicar una animación a una vista en mi aplicación de Android después de crear mi actividad. Para hacer esto, necesito determinar el tamaño actual de la vista y luego configurar una animación para escalar del tamaño actual al nuevo tamaño. Esta parte debe hacerse en tiempo de ejecución, ya que la vista se escala a diferentes tamaños según la entrada del usuario. Mi diseño está definido en XML.

Esto parece una tarea fácil, y hay muchas preguntas SO al respecto, aunque ninguna resolvió mi problema, obviamente. Entonces quizás me estoy perdiendo algo obvio. Tengo una idea de mi punto de vista al:

ImageView myView = (ImageView) getWindow().findViewById(R.id.MyViewID);

Esto funciona bien, pero cuando se llama getWidth(), getHeight(), getMeasuredWidth(), getLayoutParams().width, etc, todos ellos de retorno 0. He intentado también manualmente llamando measure()a la vista seguido por una llamada a getMeasuredWidth(), pero que no tiene ningún efecto.

He intentado llamar a estos métodos e inspeccionar el objeto en el depurador en mi actividad onCreate()y en onPostCreate(). ¿Cómo puedo calcular las dimensiones exactas de esta vista en tiempo de ejecución?

Nik Reiman
fuente
1
Ah, también debo señalar que la vista en sí misma definitivamente no tiene 0 ancho / alto. Aparece en la pantalla muy bien.
Nik Reiman
¿Puedes publicar la etiqueta <ImageView ... /> desde el diseño xml?
fhucho
Luché con muchas soluciones SO para este problema hasta que me di cuenta de que en mi caso las dimensiones de la Vista coincidían con la pantalla física (mi aplicación es "inmersiva" y la Vista y todos los anchos y alturas de sus padres están configurados en match_parent). En este caso, una solución más simple que se puede llamar de forma segura incluso antes de que se dibuje la Vista (por ejemplo, desde el método onCreate () de su Actividad) es usar Display.getSize (); consulte stackoverflow.com/a/30929599/5025060 para más detalles. Sé que esto no es lo que pidió @NikReiman, solo dejo una nota para aquellos que puedan usar este método más simple.
CÓDIGO-LEA

Respuestas:

180

Use el ViewTreeObserver en la Vista para esperar el primer diseño. Solo después del primer diseño, getWidth () / getHeight () / getMeasuredWidth () / getMeasuredHeight () funcionará.

ViewTreeObserver viewTreeObserver = view.getViewTreeObserver();
if (viewTreeObserver.isAlive()) {
  viewTreeObserver.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
    @Override
    public void onGlobalLayout() {
      view.getViewTreeObserver().removeOnGlobalLayoutListener(this);
      viewWidth = view.getWidth();
      viewHeight = view.getHeight();
    }
  });
}
Vikram Bodicherla
fuente
21
Tenga cuidado con esta respuesta, ya que se llamará continuamente si la vista subyacente es un video / superficie
Kevin Parker
77
¿Puedes dar más detalles sobre ese @Kevin? Dos cosas, en primer lugar, dado que es un oyente, esperaría una única devolución de llamada, y luego, tan pronto como recibamos la primera devolución de llamada, eliminaremos el oyente. ¿Me he perdido algo?
Vikram Bodicherla
44
Antes de llamar a cualquier método ViewTreeObserver, se recomienda verificar isAlive (): if (view.getViewTreeObserver (). IsAlive ()) {view.getViewTreeObserver (). RemoveGlobalOnLayoutListener (this); }
Peter Tran
44
alguna alternativa a removeGlobalOnLayoutListener(this);? porque está en desuso
Sibbs Gambling
77
@FarticlePilter, use removeOnGlobalLayoutListener () en su lugar. Se menciona en los documentos.
Vikram Bodicherla
63

En realidad, hay varias soluciones, según el escenario:

  1. El método seguro funcionará justo antes de dibujar la vista, una vez que haya finalizado la fase de diseño:
public static void runJustBeforeBeingDrawn(final View view, final Runnable runnable) {
    final OnPreDrawListener preDrawListener = new OnPreDrawListener() {
        @Override
        public boolean onPreDraw() {
            view.getViewTreeObserver().removeOnPreDrawListener(this);
            runnable.run();
            return true;
        }
    };
    view.getViewTreeObserver().addOnPreDrawListener(preDrawListener); 
}

Uso de la muestra:

    ViewUtil.runJustBeforeBeingDrawn(yourView, new Runnable() {
        @Override
        public void run() {
            //Here you can safely get the view size (use "getWidth" and "getHeight"), and do whatever you wish with it
        }
    });
  1. En algunos casos, es suficiente medir el tamaño de la vista manualmente:
view.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
int width=view.getMeasuredWidth(); 
int height=view.getMeasuredHeight();

Si conoce el tamaño del contenedor:

    val widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(maxWidth, View.MeasureSpec.AT_MOST)
    val heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(maxHeight, View.MeasureSpec.AT_MOST)
    view.measure(widthMeasureSpec, heightMeasureSpec)
    val width=view.measuredWidth
    val height=view.measuredHeight
  1. Si tiene una vista personalizada que ha ampliado, puede obtener su tamaño en el método "onMeasure", pero creo que funciona bien solo en algunos casos:
protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
    final int newHeight= MeasureSpec.getSize(heightMeasureSpec);
    final int newWidth= MeasureSpec.getSize(widthMeasureSpec);
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
  1. Si escribe en Kotlin, puede usar la siguiente función, que detrás de escena funciona exactamente como la runJustBeforeBeingDrawnque he escrito:

    view.doOnPreDraw { actionToBeTriggered() }

    Tenga en cuenta que debe agregar esto a gradle (que se encuentra aquí ):

    implementation 'androidx.core:core-ktx:#.#'
desarrollador de Android
fuente
1
Parece que la solución # 1 no siempre funcionará OnPreDrawListener. Caso probado cuando desea ejecutar el código de Activity onCreate()e inmediatamente la aplicación en segundo plano. Fácil de repetir, especialmente en dispositivos más lentos. Si reemplaza OnPreDrawListenercon OnGlobalLayoutListener- no verá el mismo problema.
interrogador
@questioner No entiendo, pero estoy feliz de que al final te haya ayudado.
Desarrollador de Android
Aunque generalmente uso ViewTreeObservero post, en mi caso, el número 2 ayudó ( view.measure).
CoolMind
3
@CoolMind También puede usar un nuevo método, en caso de que use Kotlin. Respuesta actualizada para incluirlo también.
Desarrollador de Android
¿Por qué Kotlin tiene funciones adicionales para esto? google está loco, este lenguaje es inútil (solo debería quedarse con java), es mucho más antiguo que swift pero solo swift obtuvo suficiente popularidad tiobe.com/tiobe-index (swift 12 position y kotlin 38)
user924
18

¿Llama getWidth()antes de que la vista se muestre en la pantalla?

Un error común cometido por los nuevos desarrolladores de Android es utilizar el ancho y la altura de una vista dentro de su constructor. Cuando se llama al constructor de una vista, Android aún no sabe qué tan grande será la vista, por lo que los tamaños se establecen en cero. Los tamaños reales se calculan durante la etapa de diseño, que ocurre después de la construcción pero antes de que se dibuje algo. Puede usar el onSizeChanged()método para recibir notificaciones de los valores cuando se conocen, o puede usar los métodos getWidth()y getHeight()más adelante, como en el onDraw()método.

Mark B
fuente
2
Entonces, ¿eso significa que necesito anular ImageView solo para capturar el onSizeChange()evento y conocer el tamaño real? Eso parece un poco exagerado ... aunque en este momento estoy desesperado solo para que el código funcione, así que vale la pena intentarlo.
Nik Reiman
Estaba pensando más bien en esperar hasta que tu onCreate () haya terminado en tu Actividad.
Mark B el
1
Como se mencionó, intenté poner este código en el onPostCreate()que se ejecuta después de que la vista se crea y se inicia por completo.
Nik Reiman
1
¿De dónde es el texto citado?
Intrincaciones
1
esta cita para la vista personalizada, no la publique para tal respuesta cuando le preguntamos sobre el tamaño de la vista en general
usuario924
17

Basado en el consejo de @ mbaird, encontré una solución viable subclasificando la ImageViewclase y anulando onLayout(). Luego creé una interfaz de observador que implementó mi actividad y pasé una referencia a sí mismo a la clase, lo que le permitió contar la actividad cuando realmente había terminado el dimensionamiento.

No estoy 100% convencido de que esta sea la mejor solución (de ahí que todavía no marque esta respuesta como correcta), pero funciona y, según la documentación, es la primera vez que se puede encontrar el tamaño real de una vista.

Nik Reiman
fuente
Bueno saber. Si encuentro algo que te permita evitar tener que subclasificar la Vista, lo publicaré aquí. Estoy un poco sorprendido de que PostCreate () no haya funcionado.
Mark B
Bueno, intenté esto y parece funcionar. Simplemente no es la mejor solución, ya que este método es con frecuencia, no solo una vez al inicio. :(
Tobias Reich
10

Aquí está el código para obtener el diseño anulando una vista si API <11 (API 11 incluye la función View.OnLayoutChangedListener):

public class CustomListView extends ListView
{
    private OnLayoutChangedListener layoutChangedListener;

    public CustomListView(Context context)
    {
        super(context);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b)
    {
        if (layoutChangedListener != null)
        {
            layoutChangedListener.onLayout(changed, l, t, r, b);
        }
        super.onLayout(changed, l, t, r, b);
    }

    public void setLayoutChangedListener(
        OnLayoutChangedListener layoutChangedListener)
    {
        this.layoutChangedListener = layoutChangedListener;
    }
}
public interface OnLayoutChangedListener
{
    void onLayout(boolean changed, int l, int t, int r, int b);
}
Gregm
fuente
5

Use el siguiente código, se le da el tamaño de la vista.

@Override
public void onWindowFocusChanged(boolean hasFocus) {
       super.onWindowFocusChanged(hasFocus);
       Log.e("WIDTH",""+view.getWidth());
       Log.e("HEIGHT",""+view.getHeight());
}
Asif Patel
fuente
5

Esto funciona para mí en mi onClickListener:

yourView.postDelayed(new Runnable() {               
    @Override
    public void run() {         
        yourView.invalidate();
        System.out.println("Height yourView: " + yourView.getHeight());
        System.out.println("Width yourView: " + yourView.getWidth());               
    }
}, 1);
Biro Csaba Norbert
fuente
44
No necesita publicar Delay cuando está en un escucha de clics. No puede hacer clic en nada hasta que todas las vistas se hayan medido y aparecido en la pantalla. Por lo tanto, ya se habrán medido para cuando haga clic en ellos.
afollestad
Este código está mal La demora posterior no garantiza que en el siguiente tic se mida su vista.
Ian Wong
Yo recomendaría no ir para postDelayed (). No es probable que suceda, pero ¿qué pasa si su vista no se mide en 1 segundo y llama a esto ejecutable para obtener el ancho / alto de la vista? Todavía resultaría 0. Editar: Ah, y por cierto, 1 está en milisegundos, por lo que fallará con seguridad.
Alex
4

También me ha perdido alrededor getMeasuredWidth()y getMeasuredHeight() getHeight()y getWidth()durante mucho tiempo .......... más tarde me encontré que conseguir la anchura y la altura de la vista en la onSizeChanged()que es la mejor manera de hacer esto ........ que pueda dinámicamente obtenga su ancho ACTUAL y la altura ACTUAL de su vista anulando elonSizeChanged( método).

podría echar un vistazo a esto que tiene un fragmento de código elaborado. Nueva publicación de blog: cómo obtener las dimensiones de ancho y alto de una vista personalizada (vista extendida) en Android http://syedrakibalhasan.blogspot.com/2011/02/how-to-get-width-and-height-dimensions.html

Rakib
fuente
0

Puede obtener tanto la posición como la dimensión de la vista en la pantalla

 val viewTreeObserver: ViewTreeObserver = videoView.viewTreeObserver;

if (viewTreeObserver.isAlive) {
    viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
        override fun onGlobalLayout() {
            //Remove Listener
            videoView.viewTreeObserver.removeOnGlobalLayoutListener(this);

            //View Dimentions
            viewWidth = videoView.width;
            viewHeight = videoView.height;

            //View Location
            val point = IntArray(2)
            videoView.post {
                videoView.getLocationOnScreen(point) // or getLocationInWindow(point)
                viewPositionX = point[0]
                viewPositionY = point[1]
            }

        }
    });
}
Hitesh Sahu
fuente
-1

funciona a la perfección para mí:

 protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        base.OnElementPropertyChanged(sender, e);
        CTEditor ctEdit = Element as CTEditor;
        if (ctEdit == null) return;           
        if (e.PropertyName == "Text")
        {
            double xHeight = Element.Height;
            double aHaight = Control.Height;
            double height;                
            Control.Measure(LayoutParams.MatchParent,LayoutParams.WrapContent);
            height = Control.MeasuredHeight;
            height = xHeight / aHaight * height;
            if (Element.HeightRequest != height)
                Element.HeightRequest = height;
        }
    }
Átomo
fuente