如何获取视图的绝对坐标

456
我试图获取一个视图左上角的绝对屏幕像素坐标。但是,我能找到的所有方法(如getLeft()getRight())都不起作用,因为它们似乎都是相对于视图的父元素而言的,从而给出了0。有没有正确的方法来做到这一点?
如果可以的话,这是为“将图片放回原位”游戏而做的。我希望用户能够绘制一个框来选择多个碎片。我的假设是最简单的方法是从MotionEvent中获取getRawX()getRawY(),然后将这些值与包含碎片的布局的左上角进行比较。知道了碎片的大小后,我就可以确定选择了多少个碎片。我知道我可以使用getX()getY()MotionEvent上,但是由于它返回的是相对位置,这使得确定选择哪些碎片更加困难。(我知道这并非不可能,但似乎过于复杂)。
编辑:这是我尝试根据问题中的一个答案获取持有容器大小的代码。 TableLayout是包含所有拼图碎片的表格。
TableLayout tableLayout = (TableLayout) findViewById(R.id.tableLayout);
Log.d(LOG_TAG, "Values " + tableLayout.getTop() + tableLayout.getLeft());

编辑2:这是我尝试过的代码,遵循更多的建议答案。

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

这段代码是在所有初始化完成后添加的。该图像已经被分割成一个包含在TableLayout中的ImageView数组(cells[]数组)。Cells[0]是左上角的ImageView,我选择了cells[4]因为它位于中间且绝对不应该具有(0,0)的坐标。

上面显示的代码仍然在记录中给我返回了全部为0的值,这真的让我很困惑,因为各种拼图块都正确地显示了。(我尝试了将tableLayoutCorners设置为公共整数并使用默认可见性,但结果相同。)

我不知道这是否重要,但最初ImageView没有给出大小。在初始化期间,当我给它一张图片来展示时,ImageView的大小会自动由View确定。即使在他们已经得到一张图片并自动调整大小之后,这是否会导致他们的值为0呢?为了潜在地解决这个问题,我添加了如上所示的tableLayout.requestLayout()代码,但这并没有帮助。

11个回答

700

3
这正是我期望找到的函数。不幸的是,我可能没有正确使用它。我意识到在我编码的Android v1.5平台上,getLocationOnScreen会出现空指针异常这是一个已知的bug,但在v2.1中测试也没有改进。这两种方法都给了我全部为0。 我在上面包含了一段代码片段。 - Steve Haley
83
只有在布局完成后才能调用它。你在视图被定位到屏幕上之前调用了这个方法。 - Romain Guy
5
啊哈,你说得对,尽管我那样做并不明显。谢谢!如果有人想了解更多细节,我已经添加了一个回答来解释我的意思。 - Steve Haley
9
你是指像ViewTreeObserver和它的OnGlobalLayoutListener这样的东西吗?可以使用View.getViewTreeObserver()来开始操作。 - Romain Guy
8
@RomainGuy 嗯,有点像那样,但比注册(然后取消注册...)的流程更加清晰易懂。在SDK方面,这是iOS做得比Android更好的领域之一。我每天都在使用两种操作系统,可以说iOS有很多烦人的问题,但UIView的处理不是其中之一。 :) - Martin Marconcini
显示剩余16条评论

190

被接受的答案实际上并没有说明如何获取位置,所以这里提供一些更详细的信息。您需要传入一个长度为2的int数组,并将其值替换为视图的(x,y)坐标(左上角的坐标)。

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

注意事项

  • 在大多数情况下,使用getLocationInWindow替换getLocationOnScreen会得到相同的结果(请参见此答案)。但是,如果你在较小的窗口中,例如对话框或自定义键盘,则需要选择更适合你的方法。
  • 如果您在onCreate中调用此方法,则会导致返回值为(0,0),因为视图尚未被布局。您可以使用ViewTreeObserver监听布局完成的时间,并获取测量坐标(请参见此答案)。

或者重写onWindowFocusChanged方法并在其中使用。 - marticztn

114

首先,您需要获取视图的localVisible矩形。

例如:

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

希望这能帮到你


4
那也应该是可行的……但它也给了我值为0。如果这可以帮助你找出我的错误,我在上面包含了一小段代码片段。再次感谢。 - Steve Haley
1
看我的其他评论;我在视图完成自我布局之前错误地调用了这个函数。现在已经没问题了,谢谢。 - Steve Haley
@Naveen Murthy 它提供了特定视图的坐标,我需要根布局中所点击视图的坐标。 - Aniket
22
这将返回相对于父元素的位置。要获取绝对坐标,请使用 getGlobalVisibleRect() - Jeffrey Blattman
3
@SteveHaley,我遇到了你曾经遇到的同样问题,不知何故,无论我尝试哪种方法,都会得到0。看来你已经找出了为什么会出现零的原因。即使在布局加载后,我仍然试图获取视图的坐标,但仍然得到零,这是为什么呢?感谢你的帮助。 - Pankaj Nimgade
显示剩余3条评论

51

使用全局布局监听器一直对我很有效。它的优点在于,如果布局发生更改,例如将某个东西设置为View.GONE或添加/删除子视图,则可以重新测量它们。

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

不要忘记在onGlobalLayout(){...}的结尾处删除OnGlobalLayoutListener。另外,为了确保,检查传递的值是否!= 0; 监听器可能会被调用两次,第一次w=0/h=0,第二次是实际的、正确的值。 - MacD
使用此方法来填充主布局,然后使用 setContentView() 在我的应用的特定 Activity 中出现了问题!这是一个对话框活动(windowFrame:@null, windowActionBar:false, windowIsFloating:true, windowNoTitle:true)。其主布局(一个 LinearLayout)没有具有确定的 layout_width 的直接子元素(所有子元素均为 match_parentwrap_content)。 - Mir-Ismaili

28

第一种方法:

Kotlin 中,我们可以为视图创建一个简单的扩展:

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

仅获取坐标:

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

第二种方法:

第二种方法更为简单:

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

通过view.absX()获取绝对X,通过view.absY()获取绝对Y。


8
一个更短的第一个样例:val location = IntArray(2).apply(::getLocationOnScreen) 的翻译:val location = IntArray(2).apply(::getLocationOnScreen) 可以翻译为“定义一个名为location的变量,它是一个长度为2的整型数组,并且通过 ::getLocationOnScreen 函数获取屏幕上的位置进行初始化。” - Tuan Chau

19

根据Romain Guy的评论,这是我的修复方法。希望能帮到其他遇到同样问题的人。

我确实尝试在视图还未在屏幕上布局时获取它们的位置,但这一点并不明显。那些代码行在初始化代码运行之后被放置,所以我假设一切准备就绪。然而,这段代码仍然在onCreate()中;通过使用Thread.sleep()进行实验,我发现布局实际上直到onCreate()一直执行到onResume()才最终完成。因此,代码是在布局完成定位在屏幕上之前尝试运行的。通过将代码添加到一个OnClickListener(或其他监听器)中,可以得到正确的值,因为它只能在布局完成后触发。


社区编辑建议使用以下代码:

请使用onWindowfocuschanged(boolean hasFocus)


那么你的意思是说setContentView(my_layout);不会放置所有视图。所有视图只有在onCreate()完成后才会被放置? - Ashwin
5
不完全正确。在setContentView(R.layout.something)之后,所有的视图都“存在”,但它们还没有被可视化加载。它们还没有大小或位置。这只有在第一次布局完成后才会发生,这通常会在将来的某个时间点(通常是在onResume()之后)完成。 - Steve Haley

17

您可以使用getLocationOnScreen()getLocationInWindow()获取视图的坐标。

然后,xy应该是视图左上角的位置。如果您的根布局比屏幕小(例如在对话框中),则使用getLocationInWindow将相对于其容器而不是整个屏幕。

Java解决方案

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

注意:如果值始终为0,则可能是在请求位置之前立即更改了视图。

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

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

注意:如果值始终为0,则可能是在请求位置之前立即更改了视图。

为确保视图有足够的时间更新,可以使用view.post在View的新布局计算完成后再运行位置请求:

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
}

如果您需要可靠性,还需另外添加:

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

13
只是除了上面的答案之外,关于何时和何地应该调用getLocationOnScreen的问题?
view相关的任何信息都只能在视图已经布局(创建)到屏幕上后才可用。因此,要获取位置,请将您的代码放在view.post(Runnable)中,在view被布局之后调用,就像这样:
view.post(new Runnable() {
  @Override
  public void run() {
    // This code will run when view created and rendered on screen
    // So as the answer to this question, you can put the code here
    
    int[] location = new int[2];
    view.getLocationOnScreen(location);
    int x = location[0];
    int y = location[1];
  }
});

6

这是我获取视图位置的实用函数,它将返回一个包含x值y值Point对象。

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

使用

Point viewALocation = getLocationOnScreen(viewA);

0

获取屏幕上视图的位置和尺寸

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

            }
        });
    }

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