getLocationOnScreen() vs getLocationInWindow()

JavaAndroidViewScreen

Java Problem Overview


What is the difference between screen and view in the context of these two methods?

I have a button and I want to get the x coordinate of its center.

I guess this would be enough:

public int getButtonXPosition() {
	return (button.getLeft()+button.getRight())/2;
}

but then, what difference would it make if I would have used

getLocationOnScreen() or getLocationInWindow() ?

(adding half of the button's width to that, of course)

Java Solutions


Solution 1 - Java

I don't think this answer is correct. If I create a new project, and edit only the MainActivity by adding the following snippet:

public boolean dispatchTouchEvent(MotionEvent ev) {
    View contentsView = findViewById(android.R.id.content);

    int test1[] = new int[2];
    contentsView.getLocationInWindow(test1);

    int test2[] = new int[2];
    contentsView.getLocationOnScreen(test2);

    System.out.println(test1[1] + " " + test2[1]);

    return super.dispatchTouchEvent(ev);
}

I will see printed to the console 108 108. This is using a Nexus 7 running 4.3. I have similar results using emulators running android versions as far back as 2.2.

Normal activity windows will have FILL_PARENTxFILL_PARENT as their WindowManager.LayoutParams, which results in them laying out to the size of the entire screen. The Window is laid out underneath (in regards to z-order, not y-coordinates) the statusbar and other decorations, so I believe a more accurate chart would be:

|--phone screen-----activity window---| 
|--------status bar-------------------| 
|                                     | 
|                                     | 
|-------------------------------------| 

If you step through the source of these two methods, you will see that getLocationInWindow traverses up your view's view hierarchy up to the RootViewImpl, summing view coordinates and subtracting parent scroll offsets. In the case I described above the ViewRootImpl is getting the status bar height from the WindowSession, and passing it down through fitSystemWindows to the ActionBarOverlayLayout, which adds this value to the actionbar height. ActionBarOverlayLayout then takes this summed value and applies it to its content view, which is the parent of your layout, as a margin.

So, your content is laid out lower than the status bar not as a result of the window starting at a lower y coordinate than the status bar, but instead as a result of a margin being applied to your activity's content view.

If you peer into the getLocationOnScreen source you'll see it merely calls getLocationInWindow and then adds the Window's left and top coords (which are also passed to the View by ViewRootImpl, which fetches them from the WindowSession). In the normal case, these values will both be zero. There are some situations where these values may be non-zero, for example a dialog window that is placed in the middle of the screen.


So, to summarize: A normal activity's window fills the entire screen, even the space under the status bar and decorations. The two methods in question will return the same x and y coordinates. Only in special cases such as dialogs where the Window is actually offset will these two values differ.

Solution 2 - Java

getLocationOnScreen() will get the location based on the phone screen.
getLocationInWindow() will get the location based on the activity window.

For the normal activity (not full-screen activity), the relation with phone screen and activity window is as shown below:

|--------phone screen--------|
|---------status bar---------|
|                            |
|----------------------------|
|------activity window-------|
|                            |
|                            |
|                            |
|                            |
|                            |
|                            |
|----------------------------|

For the x coordinate, the value of both methods is usually the same.
For the y coordinate, the values have a difference for the status bar's height.

Solution 3 - Java

The current accepted answer is a little wordy. Here is a shorter one.

getLocationOnScreen() and getLocationInWindow() normally return the same values. This is because the window is normally the same size as the screen. However, sometimes the window is smaller than the screen. For example, in a Dialog or a custom system keyboard.

So if you know that the coordinates you want are always relative to the screen (like in a normal activity), then you can use getLocationOnScreen(). However, if your view is in a Window that might be smaller than the screen (like in a Dialog or custom keyboard), then use getLocationInWindow().

Related

Solution 4 - Java

For getLocationOnScreen(), x and y will return the top-left corner of the view.

Using getLocationInWindow() will be relative to its container, not the entire screen. This will be different than getLocationOnScreen() if your root layout is smaller than the screen (like in a Dialog). For most cases they will be identical.

> NOTE: If value is always 0, you are likely changing the view immediately before requesting location. You can use view.post to ensure the values are available

Java Solution

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

To ensure view has had a chance to update, run your location request after the View's new layout has been calculated by using 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 Solution

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

To ensure view has had a chance to update, run your location request after the View's new layout has been calculated by using view.post:

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

I recommend creating an extension function for handling this:

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

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

And if you require reliability, also add:

// To use, call:
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)
    }
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionElad BendaView Question on Stackoverflow
Solution 1 - JavagrouchoView Answer on Stackoverflow
Solution 2 - JavaIvanView Answer on Stackoverflow
Solution 3 - JavaSuragchView Answer on Stackoverflow
Solution 4 - JavaGiboltView Answer on Stackoverflow