So erhalten Sie die absoluten Koordinaten einer Ansicht

390

Ich versuche, die absoluten Bildschirmpixelkoordinaten der oberen linken Ecke einer Ansicht zu ermitteln. Alle Methoden, die ich finden kann, funktionieren jedoch nicht getLeft()und getRight()funktionieren nicht, da sie alle relativ zum übergeordneten Element der Ansicht zu sein scheinen 0. Was ist der richtige Weg, um dies zu tun?

Wenn es hilft, ist dies für ein Spiel, bei dem das Bild wieder in Ordnung gebracht wird. Ich möchte, dass der Benutzer eine Box zeichnen kann, um mehrere Teile auszuwählen. Meine Vermutung ist , dass der einfachste Weg , dies zu tun ist , um getRawX()und getRawY()aus der MotionEventund vergleichen diese Werte dann gegen die linke obere Ecke des Layouts , die Stücke zu halten. Wenn ich die Größe der Stücke kenne, kann ich dann bestimmen, wie viele Stücke ausgewählt wurden. Mir ist klar, dass ich getX()und getY()auf dem verwenden kann MotionEvent, aber da dies eine relative Position zurückgibt, die es schwieriger macht, zu bestimmen, welche Stücke ausgewählt wurden. (Nicht unmöglich, ich weiß, aber es scheint unnötig kompliziert).

Bearbeiten: Dies ist der Code, mit dem ich versucht habe, die Größe des Vorratsbehälters gemäß einer der Fragen zu ermitteln. TableLayoutist der Tisch, der alle Puzzleteile enthält.

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

Bearbeiten 2: Hier ist der Code, den ich ausprobiert habe, und folge den vorgeschlagenen Antworten.

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

Dieser Code wurde hinzugefügt, nachdem die Initialisierung abgeschlossen ist. Das Bild wurde in ein Array von ImageViews (das Zellen [] -Array) unterteilt, das in a enthalten ist TableLayout. Zellen [0] sind oben links ImageView, und ich habe Zellen [4] ausgewählt, da sie irgendwo in der Mitte liegen und definitiv keine Koordinaten von (0,0) haben sollten.

Der oben gezeigte Code gibt mir immer noch alle Nullen in den Protokollen, was ich wirklich nicht verstehe, weil die verschiedenen Puzzleteile korrekt angezeigt werden. (Ich habe versucht, public int für tableLayoutCorners und die Standardsichtbarkeit zu verwenden, wobei beide das gleiche Ergebnis liefern.)

Ich weiß nicht, ob dies von Bedeutung ist, aber die ImageViews haben ursprünglich keine Größe. Die Größe des ImageViews wird während der Initialisierung automatisch von der Ansicht bestimmt, wenn ich ihm ein Bild zur Anzeige gebe. Könnte dies dazu beitragen, dass ihre Werte 0 sind, obwohl dieser Protokollierungscode steht, nachdem sie ein Bild erhalten und ihre Größe automatisch geändert haben? Um dem möglicherweise entgegenzuwirken, habe ich den Code tableLayout.requestLayout()wie oben gezeigt hinzugefügt , aber das hat nicht geholfen.

Steve Haley
quelle

Antworten:

632

Verwenden Sie View.getLocationOnScreen()und / oder getLocationInWindow().

Romain Guy
quelle
3
Das ist die Art von Funktion, die ich erwartet hatte. Leider muss ich es nicht richtig benutzen. Mir ist klar, dass es ein bekannter Fehler war, dass getLocationOnScreen in Android v1.5 (der Plattform, für die ich codiere) eine Nullzeiger-Ausnahme gibt, aber das Testen in v2.1 hat nicht besser funktioniert. Beide Methoden gaben mir 0s für alles. Ich habe oben einen Code-Ausschnitt eingefügt.
Steve Haley
76
Sie können es erst aufrufen, nachdem das Layout erstellt wurde. Sie rufen die Methode auf, bevor die Ansichten auf dem Bildschirm positioniert werden.
Romain Guy
5
Aha, du hast recht, obwohl es nicht offensichtlich war, dass ich das tat. Vielen Dank! Für alle, die mehr Details wünschen, habe ich eine Antwort hinzugefügt, die erklärt, was ich meine.
Steve Haley
9
Du meinst wie der ViewTreeObserver und sein OnGlobalLayoutListener? Weitere Informationen finden Sie unter View.getViewTreeObserver ().
Romain Guy
8
@RomainGuy Ja, irgendwie so, aber weniger verwirrend als sich registrieren zu müssen (und dann die Registrierung aufzuheben…). Dies ist ein Bereich, in dem iOS in Bezug auf SDK besser abschneidet als Android. Wenn ich beides täglich benutze, kann ich sagen, dass iOS viele nervige Dinge hat, aber das UIView-Handling gehört nicht dazu. :)
Martin Marconcini
127

In der akzeptierten Antwort wurde nicht angegeben, wie der Standort ermittelt werden soll. Hier finden Sie einige Details. Sie übergeben ein intArray der Länge 2 und die Werte werden durch die Koordinaten der Ansicht (x, y) (der oberen linken Ecke) ersetzt.

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

Anmerkungen

  • Das Ersetzen getLocationOnScreendurch getLocationInWindowsollte in den meisten Fällen zu denselben Ergebnissen führen (siehe diese Antwort ). Wenn Sie sich jedoch in einem kleineren Fenster wie einem Dialogfeld oder einer benutzerdefinierten Tastatur befinden, müssen Sie auswählen, welches Fenster Ihren Anforderungen besser entspricht.
  • Sie erhalten, (0,0)wenn Sie diese Methode aufrufen, onCreateda die Ansicht noch nicht angelegt wurde. Mit a können Sie ViewTreeObserverabhören, wann das Layout fertig ist, und die gemessenen Koordinaten abrufen. (Siehe diese Antwort .)
Suragch
quelle
86

Zuerst müssen Sie das localVisible-Rechteck der Ansicht abrufen

Zum Beispiel:

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

Hoffe das wird helfen

Naveen Murthy
quelle
3
Das hätte auch funktionieren sollen ... Allerdings gibt es mir auch Werte von 0. Ich habe oben einen Code-Ausschnitt eingefügt, damit Sie herausfinden können, was ich falsch gemacht habe. Danke noch einmal.
Steve Haley
1
Siehe meine anderen Kommentare; Ich habe dies versehentlich angerufen, bevor die Ansichten fertig waren. Das funktioniert jetzt gut, danke.
Steve Haley
@ Naveen Murthy gibt es Koordinaten zu bestimmten Ansicht, ich brauche Koordinaten der angeklickten Ansicht im Root-Layout ..
Aniket
20
Dies gibt den Speicherort relativ zum übergeordneten Element zurück. Verwendung getGlobalVisibleRect()für absolute Koordinaten.
Jeffrey Blattman
3
@SteveHaley, ich hatte das gleiche Problem wie Sie. Aus irgendeinem Grund gibt es mir bei jeder Methode, die ich versuche, 0. Es scheint, Sie haben herausgefunden, warum es Null gibt. Ich versuche, Sichtkoordinaten zu erhalten, nachdem das Layout geladen wurde. Ich erhalte immer noch Null. Warum ist das so? Vielen Dank für Ihre Hilfe
Pankaj Nimgade
46

Die Verwendung eines globalen Layout-Listeners hat bei mir immer gut funktioniert. Es hat den Vorteil, dass Dinge neu gemessen werden können, wenn das Layout geändert wird, z. B. wenn etwas auf View.GONE gesetzt ist oder untergeordnete Ansichten hinzugefügt / entfernt werden.

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);
 }
Simon
quelle
Vergessen Sie nicht, den OnGlobalLayoutListener innerhalb (am Ende) des onGlobalLayout () {...} zu entfernen. Überprüfen Sie auch, um sicherzugehen, ob das, was passiert ist! = 0; Der Listener kann zweimal aufgerufen werden, einmal mit einem Wert von w = 0 / h = 0, das zweite Mal mit den tatsächlichen, korrekten Werten.
MacD
Diese Art, das Hauptlayout aufzublasen und dann zu verwenden, setContentView()verursachte ein Problem in einem Special Activityin meiner App! Das war eine Dialogaktivität ( windowFrame:@null, windowActionBar:false, windowIsFloating:true, windowNoTitle:true). Sein Hauptlayout (a LinearLayout) hat kein direktes Kind mit bestimmt layout_width(Alle waren match_parentoder wrap_content).
Mir-Ismaili
17

Nach dem Kommentar von Romain Guy habe ich Folgendes behoben. Hoffentlich hilft es allen anderen, die dieses Problem ebenfalls hatten.

Ich habe zwar versucht, die Positionen der Ansichten zu ermitteln, bevor sie auf dem Bildschirm angezeigt wurden, aber es war überhaupt nicht offensichtlich, dass dies geschah. Diese Zeilen wurden platziert, nachdem der Initilisierungscode ausgeführt wurde, sodass ich davon ausging, dass alles bereit war. Dieser Code befand sich jedoch noch in onCreate (). Beim Experimentieren mit Thread.sleep () stellte ich fest, dass das Layout erst fertiggestellt wird, nachdem onCreate () bis zur Ausführung von onResume () abgeschlossen wurde. Der Code versuchte also tatsächlich auszuführen, bevor das Layout nicht mehr auf dem Bildschirm positioniert war. Durch Hinzufügen des Codes zu einem OnClickListener (oder einem anderen Listener) wurden die korrekten Werte erhalten, da er erst nach Abschluss des Layouts ausgelöst werden konnte.


Die folgende Zeile wurde als Community-Bearbeitung vorgeschlagen:

benutzen Sie bitte onWindowfocuschanged(boolean hasFocus)

Steve Haley
quelle
Sie wollen also sagen, dass setContentView (my_layout); wird nicht alle Ansichten platzieren. Alle Ansichten werden erst platziert, nachdem onCreate () abgeschlossen ist.
Ashwin
5
Nicht ganz. Alle Ansichten "existieren" danach setContentView(R.layout.something), wurden jedoch nicht visuell geladen. Sie haben keine Größe oder Position. Dies geschieht erst, wenn der erste Layoutdurchlauf abgeschlossen ist, was zu einem späteren Zeitpunkt geschieht (in der Regel nach onResume ()).
Steve Haley
14

Erster Weg:

In Kotlin können wir eine einfache Erweiterung für die Ansicht erstellen:

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

Und einfach Koordinaten bekommen:

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

Zweiter Weg:

Der zweite Weg ist einfacher:

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

und einfach das absolute X von view.absX()und Y von erhaltenview.absY()

Amir Hossein Ghasemi
quelle
6
Eine kürzere Version für Ihre 1. Probe:val location = IntArray(2).apply(::getLocationOnScreen)
Tuan Chau
5

Meine Utils-Funktion zum Abrufen des Ansichtsorts gibt ein PointObjekt mit x valueund zurücky value

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

Verwenden von

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

Sie können die Koordinaten einer Ansicht mit getLocationOnScreen()oder abrufengetLocationInWindow()

Danach xund ysollte die obere linke Ecke der Ansicht sein. Wenn Ihr Root-Layout kleiner als der Bildschirm ist (wie in einem Dialogfeld), bezieht getLocationInWindowsich die Verwendung auf den Container und nicht auf den gesamten Bildschirm.

Java-Lösung

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

HINWEIS: Wenn der Wert immer 0 ist, ändern Sie wahrscheinlich die Ansicht unmittelbar vor der Anforderung des Speicherorts.

Um sicherzustellen, dass die Ansicht aktualisiert werden konnte, führen Sie Ihre Standortanforderung aus, nachdem das neue Layout der Ansicht mithilfe von Folgendem berechnet wurde 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];
});

~~

Kotlin-Lösung

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

HINWEIS: Wenn der Wert immer 0 ist, ändern Sie wahrscheinlich die Ansicht unmittelbar vor der Anforderung des Speicherorts.

Um sicherzustellen, dass die Ansicht aktualisiert werden konnte, führen Sie Ihre Standortanforderung aus, nachdem das neue Layout der Ansicht mithilfe von Folgendem berechnet wurde view.post:

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

Ich empfehle, eine Erweiterungsfunktion zu erstellen, um dies zu handhaben:

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

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

Und wenn Sie Zuverlässigkeit benötigen, fügen Sie hinzu:

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
quelle
0

Verwenden Sie diesen Code, um genaue X- und Y-Koordinaten zu finden:

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

Ich habe einige Werte addiert / subtrahiert, um meine Ansicht anzupassen. Bitte machen Sie diese Zeilen erst, nachdem die gesamte Ansicht aufgeblasen wurde.

Tarun
quelle
"Ich habe einige Werte addiert / subtrahiert, um meine Ansicht anzupassen." Warum??
ToolmakerSteve
Ich meine, gemäß meiner Anforderung (Platzierung der Ansicht) habe ich Werte addiert und subtrahiert, um meine Ansicht anzupassen.
Tarun
0

Holen Sie sich sowohl Ansichtsposition als auch Bemaßung auf dem Bildschirm

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
quelle
0

Nur zusätzlich zu den obigen Antworten, für die Frage, wo und wann Sie anrufen sollten getLocationOnScreen?

Informationen zu einer Ansicht, die Sie abrufen möchten, sind erst verfügbar, nachdem die Ansicht auf dem Bildschirm angezeigt wurde. Sie können dies einfach tun, indem Sie Ihren Code, der von der Erstellung der Ansicht abhängt, in diese (view.post (Runnable)) einfügen:

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
quelle