getLocationOnScreen()与getLocationInWindow()的区别

73

在这两种方法的背景下,“屏幕”和“视图”有什么区别?

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

我猜这就足够了:

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

但问题是,如果我使用了 getLocationOnScreen()getLocationInWindow(),会有什么区别呢?

当然,还要加上按钮宽度的一半。


1
随着Android N及其多窗口支持(https://developer.android.com/preview/features/multi-window.html)的到来,您应该更倾向于使用getLocationInWindow()而不是getLocationOnScreen(),因为后者可能会导致一些意外的行为。 - Piotr Zawadzki
4个回答

123

我不认为这个答案是正确的。如果我创建一个新项目,并仅编辑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_PARENT x FILL_PARENT作为其WindowManager.LayoutParams,这将导致它们布局为整个屏幕的大小。该窗口在状态栏和其他装饰品下方(对于z顺序而言,而不是y坐标),因此我相信更准确的图表应该是:

|--phone screen-----activity window---| 
|--------status bar-------------------| 
|                                     | 
|                                     | 
|-------------------------------------| 
如果您逐步查看这两种方法的源代码,您会发现getLocationInWindow一直遍历您视图的视图层次结构,直到RootViewImpl,累加视图坐标并减去父滚动偏移量。在上述情况下,ViewRootImpl从WindowSession中获取状态栏高度,并通过fitSystemWindows将其传递给ActionBarOverlayLayout,后者将此值添加到操作栏高度中。然后,ActionBarOverlayLayout将这个总和值作为边距应用于其内容视图,该内容视图是您布局的父视图。

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

如果您查看getLocationOnScreen的源代码,您会发现它仅调用getLocationInWindow,然后添加窗口的左侧和顶部坐标(这些坐标也由ViewRootImpl传递给View,后者从WindowSession获取它们)。在正常情况下,这些值都将为零。有一些情况下,这些值可能为非零,例如放置在屏幕中央的对话框窗口。


因此,总结一下:普通活动的窗口占据整个屏幕,甚至包括状态栏和装饰的空间。这两种方法将返回相同的x和y坐标。只有在特殊情况下,例如窗口实际上被偏移的对话框中,这两个值才会不同。


为什么我在手机上添加屏幕导航栏后,我的Activity窗口会缩小?它也像状态栏一样是一个装饰。 - Rohan Bhatia

35

getLocationOnScreen()将基于手机屏幕获取位置。
getLocationInWindow()将基于Activity窗口获取位置。

对于普通Activity(非全屏Activity),与手机屏幕和Activity窗口的关系如下所示:

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

对于x坐标,两种方法的值通常相同。
对于y坐标,由于状态栏的高度不同,这两个方法的值会有所区别。


14
在创建一个空项目并调用这两种方法时,我的观察结果与这个答案不一致。我在下面的回答中有进一步解释。 - groucho

12

目前被接受的答案有些冗长,这里提供一个更简短的答案。

getLocationOnScreen()getLocationInWindow() 通常返回相同的值。这是因为窗口大小通常和屏幕一样大。然而,有时窗口比屏幕小。例如,在对话框或自定义系统键盘中。

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

相关


5

对于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)
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接