70

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

我有一个按钮,我想获取其中心的 x 坐标。

我想这就足够了:

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

但是,如果我使用的话会有什么不同

getLocationOnScreen()还是getLocationInWindow()

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

4

4 回答 4

119

我不认为这个答案是正确的。如果我创建一个新项目,并通过添加以下代码段仅编辑 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。使用运行 android 版本的模拟器,我有类似的结果,可以追溯到 2.2。

正常活动窗口将 FILL_PARENTxFILL_PARENT 作为它们的 WindowManager.LayoutParams,这导致它们布局到整个屏幕的大小。窗口位于状态栏和其他装饰下方(关于 z 顺序,而不是 y 坐标),所以我相信更准确的图表是:

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

如果您逐步了解这两种方法的源代码,您会看到它getLocationInWindow向上遍历视图的视图层次结构,直至 RootViewImpl,将视图坐标相加并减去父滚动偏移量。在我上面描述的情况下,ViewRootImpl 从 WindowSession 获取状态栏高度,并通过 fitSystemWindows 向下传递到 ActionBarOverlayLayout,它将这个值添加到操作栏高度。ActionBarOverlayLayout 然后将这个总和值应用到它的内容视图,它是你的布局的父级,作为一个边距。

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

如果您getLocationOnScreen查看源代码,您会看到它只是调用getLocationInWindow然后添加窗口的左坐标和上坐标(它们也由 ViewRootImpl 传递给视图,它从 WindowSession 中获取它们)。在正常情况下,这些值都为零。在某些情况下,这些值可能不为零,例如位于屏幕中间的对话窗口。


所以,总结一下:一个正常活动的窗口会填满整个屏幕,甚至是状态栏和装饰下的空间。有问题的两种方法将返回相同的 x 和 y 坐标。只有在特殊情况下,例如窗口实际偏移的对话框,这两个值才会不同。

于 2013-11-22T21:11:45.373 回答
35

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

对于普通activity(不是全屏activity),手机屏幕和activity窗口的关系如下图:

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

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

于 2013-07-16T10:06:14.490 回答
11

当前接受的答案有点罗嗦。这是一个较短的。

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

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

有关的

于 2018-03-31T09:52:28.430 回答
4

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

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

注意:如果值始终为 0,您可能会在请求位置之前立即更改视图。您可以使用view.post来确保值可用

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)
    }
}
于 2020-01-20T20:34:39.617 回答