Cómo obtener las coordenadas absolutas de una vista

390

Estoy tratando de obtener las coordenadas absolutas de píxeles de la pantalla de la esquina superior izquierda de una vista. Sin embargo, todos los métodos que puedo encontrar como getLeft()y getRight()no funcionan, ya que todos parecen ser relativos al padre de la vista, lo que me da 0. ¿Cuál es la forma apropiada de hacer esto?

Si ayuda, esto es para un juego de "poner la imagen de nuevo en orden". Quiero que el usuario pueda dibujar un cuadro para seleccionar varias piezas. Mi suposición es que la forma más fácil de hacerlo es hacia getRawX()y getRawY()desde el MotionEventy luego comparar esos valores con la esquina superior izquierda del diseño que contiene las piezas. Conociendo el tamaño de las piezas, puedo determinar cuántas piezas se han seleccionado. Me doy cuenta de que puedo usar getX()y getY()en el MotionEvent, pero como eso devuelve una posición relativa que hace que determinar qué piezas fueron seleccionadas sea más difícil. (No es imposible, lo sé, pero parece innecesariamente complicado).

Editar: este es el código que usé para tratar de obtener el tamaño del contenedor de retención, según una de las preguntas. TableLayoutes la mesa que contiene todas las piezas del rompecabezas.

TableLayout tableLayout = (TableLayout) findViewById(R.id.tableLayout);
Log.d(LOG_TAG, "Values " + tableLayout.getTop() + tableLayout.getLeft());

Edición 2: Aquí está el código que he probado, siguiendo más de las respuestas sugeridas.

public int[] tableLayoutCorners = new int[2];
(...)

TableLayout tableLayout = (TableLayout) findViewById(R.id.tableLayout);
tableLayout.requestLayout();
Rect corners = new Rect();
tableLayout.getLocalVisibleRect(corners);
Log.d(LOG_TAG, "Top left " + corners.top + ", " + corners.left + ", " + corners.right
            + ", " + corners.bottom);

cells[4].getLocationOnScreen(tableLayoutCorners);
Log.d(LOG_TAG, "Values " + tableLayoutCorners[0] + ", " + tableLayoutCorners[1]);

Este código se agregó después de completar toda la inicialización. La imagen se ha dividido en una matriz de ImageViews (la matriz de celdas []) contenida dentro de a TableLayout. Las celdas [0] están en la parte superior izquierda ImageView, y elegí las celdas [4] ya que está en algún lugar en el medio y definitivamente no debería tener coordenadas de (0,0).

El código que se muestra arriba todavía me da todos los 0 en los registros, lo que realmente no entiendo porque las diversas piezas del rompecabezas se muestran correctamente. (Intenté public int para tableLayoutCorners y la visibilidad predeterminada, ambos dieron el mismo resultado).

No sé si esto es significativo, pero los ImageViews originalmente no tienen un tamaño. El tamaño de los ImageViews se determina durante la inicialización automáticamente por la Vista cuando le doy una imagen para mostrar. ¿Podría esto contribuir a que sus valores sean 0, a pesar de que el código de registro es después de que se les haya dado una imagen y se hayan redimensionado automáticamente? Para contrarrestar eso, agregué el código tableLayout.requestLayout()como se muestra arriba, pero eso no ayudó.

Steve Haley
fuente

Respuestas:

632

Uso View.getLocationOnScreen()y / o getLocationInWindow().

Romain Guy
fuente
3
Ahora ese es el tipo de función que esperaba encontrar. Desafortunadamente, no debo estar usándolo correctamente. Me doy cuenta de que era un error conocido que getLocationOnScreen ofrece una excepción de puntero nulo en Android v1.5 (la plataforma para la que estoy codificando), pero las pruebas en v2.1 no funcionaron mejor. Ambos métodos me dieron 0 para todo. Incluí un fragmento de código arriba.
Steve Haley
76
Solo puede invocarlo DESPUÉS de que el diseño haya sucedido. Está llamando al método antes de que las vistas se coloquen en la pantalla.
Romain Guy
55
Ajá, tienes razón, aunque no era obvio que estaba haciendo eso. ¡Gracias! Para cualquier persona curiosa por más detalles, he agregado una respuesta que explica lo que quiero decir.
Steve Haley
99
¿Te refieres a ViewTreeObserver y su OnGlobalLayoutListener? Consulte View.getViewTreeObserver () para comenzar.
Romain Guy
8
@RomainGuy Sí, algo así, pero menos confuso que tener que registrarse (y luego anular el registro ...). Esta es un área donde iOS lo hace mejor que Android en términos de SDK. Usando ambos a diario, puedo decir que iOS tiene muchas cosas molestas, pero el manejo de UIView no es uno de esos. :)
Martin Marconcini
127

La respuesta aceptada en realidad no decía cómo obtener la ubicación, así que aquí hay un poco más de detalle. Se pasa una intmatriz de longitud 2 y los valores se reemplazan con las coordenadas de la vista (x, y) (de la esquina superior izquierda).

int[] location = new int[2];
myView.getLocationOnScreen(location);
int x = location[0];
int y = location[1];

Notas

  • Reemplazar getLocationOnScreencon getLocationInWindowdebería dar los mismos resultados en la mayoría de los casos (ver esta respuesta ). Sin embargo, si se encuentra en una ventana más pequeña, como un cuadro de diálogo o un teclado personalizado, entonces deberá elegir cuál se adapta mejor a sus necesidades.
  • Obtendrá (0,0)si llama a este método onCreateporque la vista aún no se ha presentado. Puede usar a ViewTreeObserverpara escuchar cuándo se realiza el diseño y puede obtener las coordenadas medidas. (Ver esta respuesta )
Suragch
fuente
86

Primero debes obtener el rectángulo visible local de la vista

Por ejemplo:

Rect rectf = new Rect();

//For coordinates location relative to the parent
anyView.getLocalVisibleRect(rectf);

//For coordinates location relative to the screen/display
anyView.getGlobalVisibleRect(rectf);

Log.d("WIDTH        :", String.valueOf(rectf.width()));
Log.d("HEIGHT       :", String.valueOf(rectf.height()));
Log.d("left         :", String.valueOf(rectf.left));
Log.d("right        :", String.valueOf(rectf.right));
Log.d("top          :", String.valueOf(rectf.top));
Log.d("bottom       :", String.valueOf(rectf.bottom));

Espero que esto ayude

Naveen Murthy
fuente
3
Eso también debería haber funcionado ... Sin embargo, también me da valores de 0. Incluí un fragmento de código arriba si eso te ayuda a descubrir qué hice mal. Gracias de nuevo.
Steve Haley
1
Ver mis otros comentarios; Accidentalmente estaba llamando a esto antes de que las Vistas terminaran de exponerse. Esto funciona bien ahora gracias.
Steve Haley
@Naveen Murthy da coordenadas a una vista específica, necesito coordenadas de la vista clicada en el diseño raíz ..
Aniket
20
Esto devuelve la ubicación relativa al padre. uso getGlobalVisibleRect()para coordenadas absolutas.
Jeffrey Blattman
3
@SteveHaley, he estado enfrentando el mismo problema que tú, por alguna razón me da 0 en cada método que intento. Parece que lo has descubierto por qué está dando cero. Estoy tratando de obtener coordenadas de vista después de cargar el diseño, pero sigo obteniendo cero, ¿por qué es así? Gracias por su ayuda
Pankaj Nimgade
46

Usar un oyente de diseño global siempre me ha funcionado bien. Tiene la ventaja de poder volver a medir las cosas si se cambia el diseño, por ejemplo, si algo se establece en View.GONE o se agregan / eliminan vistas secundarias.

public void onCreate(Bundle savedInstanceState)
{
     super.onCreate(savedInstanceState);

     // inflate your main layout here (use RelativeLayout or whatever your root ViewGroup type is
     LinearLayout mainLayout = (LinearLayout ) this.getLayoutInflater().inflate(R.layout.main, null); 

     // set a global layout listener which will be called when the layout pass is completed and the view is drawn
     mainLayout.getViewTreeObserver().addOnGlobalLayoutListener(
       new ViewTreeObserver.OnGlobalLayoutListener() {
          public void onGlobalLayout() {
               //Remove the listener before proceeding
               if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
                    mainLayout.getViewTreeObserver().removeOnGlobalLayoutListener(this);
               } else {
                    mainLayout.getViewTreeObserver().removeGlobalOnLayoutListener(this);
               }

               // measure your views here
          }
       }
     );

     setContentView(mainLayout);
 }
Simón
fuente
No olvide eliminar OnGlobalLayoutListener dentro de (al final de) onGlobalLayout () {...}. Además, solo para estar seguro, verifique si lo que ha pasado! = 0; Se puede llamar al oyente dos veces, una vez con un valor de w = 0 / h = 0, la segunda vez con los valores reales y correctos.
MacD
¡Esta forma de inflar el diseño principal y luego usarlo setContentView()causó un problema en un especial Activityen mi aplicación! Esa fue una actividad de diálogo ( windowFrame:@null, windowActionBar:false, windowIsFloating:true, windowNoTitle:true). Su diseño principal (a LinearLayout) no tiene ningún hijo directo con determinado layout_width(Todos ellos fueron match_parento wrap_content).
Mir-Ismaili
17

Siguiendo el comentario de Romain Guy, así es como lo arreglé. Esperemos que ayude a cualquiera que también haya tenido este problema.

De hecho, estaba tratando de obtener las posiciones de las vistas antes de que se presentaran en la pantalla, pero no era del todo obvio que sucediera. Esas líneas se colocaron después de que se ejecutó el código de inicialización, por lo que supuse que todo estaba listo. Sin embargo, este código todavía estaba en onCreate (); Al experimentar con Thread.sleep () descubrí que el diseño no está realmente finalizado hasta después de que onCreate () hasta que onResume () haya terminado de ejecutarse. De hecho, el código estaba tratando de ejecutarse antes de que el diseño terminara de colocarse en la pantalla. Al agregar el código a un OnClickListener (o algún otro Listener) se obtuvieron los valores correctos porque solo se podía disparar una vez que el diseño había terminado.


La línea a continuación se sugirió como edición comunitaria:

por favor use onWindowfocuschanged(boolean hasFocus)

Steve Haley
fuente
Entonces quieres decir que setContentView (my_layout); No colocará todas las vistas. ¿Todas las vistas se colocarán solo cuando termine onCreate ()?
Ashwin
55
No exactamente. Todas las vistas "existen" después setContentView(R.layout.something), pero no se han cargado visualmente. No tienen un tamaño o una posición. Eso no sucede hasta que finaliza la primera pasada de diseño, lo que sucede en algún momento en el futuro (generalmente después de onResume ()).
Steve Haley
14

Primera forma:

En Kotlin podemos crear una extensión simple para ver:

fun View.getLocationOnScreen(): Point
{
    val location = IntArray(2)
    this.getLocationOnScreen(location)
    return Point(location[0],location[1])
}

Y simplemente obtenga coordenadas:

val location = yourView.getLocationOnScreen()
val absX = location.x
val absY = location.y

Segunda forma:

La segunda forma es más simple:

fun View.absX(): Int
{
    val location = IntArray(2)
    this.getLocationOnScreen(location)
    return location[0]
}

fun View.absY(): Int
{
    val location = IntArray(2)
    this.getLocationOnScreen(location)
    return location[1]
}

y simplemente obtener X absoluto por view.absX()Y porview.absY()

Amir Hossein Ghasemi
fuente
66
Una versión más corta para su primera muestra:val location = IntArray(2).apply(::getLocationOnScreen)
Tuan Chau,
5

Mis utilidades funcionan para obtener la ubicación de la vista, devolverá un Pointobjeto con x valueyy value

public static Point getLocationOnScreen(View view){
    int[] location = new int[2];
    view.getLocationOnScreen(location);
    return new Point(location[0], location[1]);
}

Utilizando

Point viewALocation = getLocationOnScreen(viewA);
Phan Van Linh
fuente
5

Puede obtener las coordenadas de una vista usando getLocationOnScreen()ogetLocationInWindow()

Luego, xy ydebe ser la esquina superior izquierda de la vista. Si su diseño raíz es más pequeño que la pantalla (como en un Diálogo), el uso getLocationInWindowserá relativo a su contenedor, no a toda la pantalla.

Solución Java

int[] point = new int[2];
view.getLocationOnScreen(point); // or getLocationInWindow(point)
int x = point[0];
int y = point[1];

NOTA: Si el valor es siempre 0, es probable que esté cambiando la vista inmediatamente antes de solicitar la ubicación.

Para garantizar que la vista haya tenido la oportunidad de actualizarse, ejecute su solicitud de ubicación después de que se haya calculado el nuevo diseño de la Vista utilizando view.post:

view.post(() -> {
    // Values should no longer be 0
    int[] point = new int[2];
    view.getLocationOnScreen(point); // or getLocationInWindow(point)
    int x = point[0];
    int y = point[1];
});

~~

Solución Kotlin

val point = IntArray(2)
view.getLocationOnScreen(point) // or getLocationInWindow(point)
val (x, y) = point

NOTA: Si el valor es siempre 0, es probable que esté cambiando la vista inmediatamente antes de solicitar la ubicación.

Para garantizar que la vista haya tenido la oportunidad de actualizarse, ejecute su solicitud de ubicación después de que se haya calculado el nuevo diseño de la Vista utilizando view.post:

view.post {
    // Values should no longer be 0
    val point = IntArray(2)
    view.getLocationOnScreen(point) // or getLocationInWindow(point)
    val (x, y) = point
}

Recomiendo crear una función de extensión para manejar esto:

// To use, call:
val (x, y) = view.screenLocation

val View.screenLocation get(): IntArray {
    val point = IntArray(2)
    getLocationOnScreen(point)
    return point
}

Y si necesita confiabilidad, agregue también:

view.screenLocationSafe { x, y -> Log.d("", "Use $x and $y here") }

fun View.screenLocationSafe(callback: (Int, Int) -> Unit) {
    post {
        val (x, y) = screenLocation
        callback(x, y)
    }
}
Gibolt
fuente
0

Use este código para encontrar las coordenadas exactas de X e Y:

int[] array = new int[2];
ViewForWhichLocationIsToBeFound.getLocationOnScreen(array);
if (AppConstants.DEBUG)
    Log.d(AppConstants.TAG, "array  X = " + array[0] + ", Y = " + array[1]);
ViewWhichToBeMovedOnFoundLocation.setTranslationX(array[0] + 21);
ViewWhichToBeMovedOnFoundLocation.setTranslationY(array[1] - 165);

He agregado / restado algunos valores para ajustar mi vista. Haga estas líneas solo después de que se haya inflado toda la vista.

Tarun
fuente
"He agregado / restado algunos valores para ajustar mi vista". ¿¿Por qué??
ToolmakerSteve
Quiero decir, según mi requisito (Colocación de la vista), he agregado y restado valores para ajustar mi vista.
Tarun
0

Obtenga tanto la posición de visualización como la dimensión 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
0

Además de las respuestas anteriores, para la pregunta ¿dónde y cuándo debe llamar getLocationOnScreen?

En cualquier información relacionada con cualquier vista que desee obtener, solo estará disponible después de que la vista se haya presentado en la pantalla. Puede hacer esto fácilmente poniendo su código que depende de la creación de la vista dentro de este (view.post (Runnable)):

view.post(new Runnable() {
            @Override
            public void run() {

               // This code will run view created and rendered on screen

               // So as the answer to this question, you can put
               int[] location = new int[2];
               myView.getLocationOnScreen(location);
               int x = location[0];
               int y = location[1];
            }
        });  
Suraj Vaishnav
fuente