GetLocationOnScreen() vs getLocationInWindow()

在这两种方法的上下文中,屏幕和视图的区别是什么?

我有一个按钮,我想得到它中心的 x 坐标。

我想这就足够了:

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

但是,如果我用了

getLocationOnScreen()还是 getLocationInWindow()

(当然,增加了按钮宽度的一半)

74254 次浏览

getLocationOnScreen()将根据 手机屏幕得到位置。
getLocationInWindow()将根据 活动窗口得到位置。

至于 正常活动(非全屏活动)与手机屏幕及活动窗口的关系如下:

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

对于 x坐标,两个方法的值通常是相同的。
对于 y坐标,这些值与状态栏的高度有所不同。

我不认为 这个答案是正确的。如果我创建一个新项目,并通过添加以下代码片段仅编辑 MainActivity:

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

我将看到打印到控制台 108 108。这是使用运行4.3的 Nexus 7。我使用运行安卓2.2版本的仿真器也得到了类似的结果。

正常的活动窗口将使用 FILL _ PARENTxFILL _ PARENT 作为它们的 Windows 管理器。LayoutParams,这导致它们布局成整个屏幕的大小。窗口位于状态栏和其他装饰物的下方(关于 z 顺序,而不是 y 坐标) ,所以我相信一个更准确的图表应该是:

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

如果单步查看这两个方法的源代码,您将看到 getLocationInWindow沿视图的视图层次结构向上遍历 RootViewImpl,将视图坐标和 减去父滚动偏移量。在我上面描述的例子中,ViewRootImpl 从 WindowSession 获取状态栏高度,并通过 fitSystemWindows 将其传递给 ActionBarOverlayLayout,后者将该值添加到操作栏高度。然后 ActionBarOverlayout 获取这个总和值,并将其应用到其内容视图(即布局的父视图)中作为边距。

因此,内容的布局低于状态栏不是因为窗口的 y 坐标低于状态栏,而是因为活动的内容视图应用了边距。

如果你查看 getLocationOnScreen源代码,你会看到它只是调用 getLocationInWindow,然后添加窗口的左边和顶部坐标(它们也通过 ViewRootImpl 传递给 View,从 WindowSession 获取它们)。在正常情况下,这些值都为零。在某些情况下,这些值可能是非零的,例如放置在屏幕中间的对话框窗口。


因此,总结一下: 一个正常活动的窗口填满了整个屏幕,甚至状态栏和修饰符下面的空间也是如此。这两个方法将返回相同的 x 和 y 坐标。只有在窗口实际偏移的特殊情况下,这两个值才会不同。

目前公认的答案有点冗长。这里是一个较短的答案。

getLocationOnScreen()getLocationInWindow()通常返回相同的值。这是因为窗口通常与屏幕大小相同。但是,有时窗口比屏幕小。例如,在 Dialog或自定义系统键盘中。

因此,如果您知道所需的坐标始终是相对于屏幕的(就像在正常活动中一样) ,那么可以使用 getLocationOnScreen()。但是,如果视图所在的窗口可能比屏幕小(如对话框或自定义键盘) ,则使用 getLocationInWindow()

相关资料

对于 getLocationOnScreen()xy将返回视图的左上角。

使用 getLocationInWindow()将相对于它的容器,而不是整个屏幕。如果您的根布局比屏幕小(比如在对话框中) ,那么这将不同于 getLocationOnScreen()。在大多数情况下,它们将是相同的。

注意: 如果值总是0,那么在请求位置之前可能会立即更改视图。您可以使用 浏览,发布来确保值是可用的

Java 解决方案

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

为了确保视图有机会更新,在使用 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 溶液

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

为了确保视图有机会更新,在使用 view.post计算出视图的新布局之后运行位置请求:

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

我建议创建一个扩展函数来处理这个问题:

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


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

如果你需要可靠性,还可以补充一点:

// 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)
}
}