我试图获得一个视图的左上角的绝对屏幕像素坐标。然而,我能找到的所有方法,如getLeft()和getRight()都不工作,因为它们似乎都是相对于视图的父视图,因此给我0。正确的做法是什么?

If it helps, this is for a 'put the picture back in order' game. I want the user to be able to draw a box to select multiple pieces. My assumption is that the easiest way to do that is to getRawX() and getRawY() from the MotionEvent and then compare those values against the top left corner of the layout holding the pieces. Knowing the size of the pieces, I can then determine how many pieces have been selected. I realise I can use getX() and getY() on the MotionEvent, but as that returns a relative position that makes determining which pieces were selected more difficult. (Not impossible, I know, but it seems unnecessarily complicated).

编辑:这是我用来尝试得到持有容器的大小的代码,每一个问题。tableelayout是包含所有拼图碎片的表格。

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

这段代码是在所有初始化完成后添加的。图像被划分为一个包含在tableelayout中的ImageViews数组(cells[]数组)。单元格[0]是ImageView的左上角,我选择单元格[4]是因为它在中间的某个地方而且它的坐标肯定不应该是(0,0)

上面显示的代码仍然在日志中给我所有的0,我真的不明白,因为各种拼图块被正确显示。(我尝试了公共int table elayoutcorners和默认可见性,两者都给出相同的结果。)

我不知道这是否重要,但ImageViews最初没有给定大小。ImageViews的大小是在初始化过程中由视图自动决定的,当我给它一个图像来显示。这是否会导致它们的值为0,即使日志代码是在给它们一个图像并自动调整自己的大小之后?为了潜在地解决这个问题,我添加了如上所示的tableLayout.requestLayout()代码,但这并没有帮助。


使用View.getLocationOnScreen()和/或getLocationInWindow()。


首先,你必须获得视图的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));

希望这能有所帮助


根据Romain Guy的评论,以下是我如何修复它。希望它能帮助其他有这个问题的人。

I was indeed trying to get the positions of the views before they had been laid out on the screen but it wasn't at all obvious that was happening. Those lines had been placed after the initilisation code ran, so I assumed everything was ready. However, this code was still in onCreate(); by experimenting with Thread.sleep() I discovered that the layout is not actually finalised until after onCreate() all the way to onResume() had finished executing. So indeed, the code was trying to run before the layout had finished being positioned on the screen. By adding the code to an OnClickListener (or some other Listener) the correct values were obtained because it could only be fired after the layout had finished.


下面这句话是建议社区编辑的:

请使用onWindowfocuschanged(boolean hasFocus)


使用全局布局监听器对我来说一直都很有效。它的优点是,如果布局发生改变,例如,如果某些东西被设置为视图,则可以重新测量。添加/删除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);
 }

公认的答案实际上并没有告诉如何获得位置,所以这里有更多的细节。你传入一个长度为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来监听布局何时完成,你可以得到测量的坐标。(请看这个答案。)


我的获取视图位置的utils函数,它会返回一个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);

第一个方法:

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

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()和view.absY()获得绝对X


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

然后,x和y应该是视图的左上角。如果你的根布局比屏幕小(就像在对话框中),使用getLocationInWindow将相对于它的容器,而不是整个屏幕。

Java解决方案

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

注意:如果value总是0,您可能会在请求位置之前立即更改视图。

为了确保视图有机会更新,在视图的新布局被使用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];
});

~~

芬兰湾的科特林解决方案

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

注意:如果value总是0,您可能会在请求位置之前立即更改视图。

为了确保视图有机会更新,在视图的新布局被使用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
}

如果你要求可靠性,还可以加上:

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

在屏幕上获得视图位置和尺寸

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

            }
        });
    }

除了上面的答案,对于在哪里和何时调用getLocationOnScreen的问题?

对于与视图相关的任何信息,只有在视图被布局(创建)到屏幕上之后才可用。所以要获得位置,把你的代码放在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];
               myView.getLocationOnScreen(location);
               int x = location[0];
               int y = location[1];
            }
        });  

binding.ivStory.setOnTouchListener(object : View.OnTouchListener{
        override fun onTouch(p0: View?, event: MotionEvent?): Boolean {
            val x = event?.x
            vak y = event?.y

            when (event!!.action) {
                MotionEvent.ACTION_UP ->

                if (x != null) {
                   //Do your stuff                    }

                MotionEvent.ACTION_DOWN  -> {

                }
            }
            return true
        }

    })

当你触摸屏幕时,这是Kotlin获得屏幕视图坐标的方法。 确保你同时处理动作向上和向下,否则可能会导致多次点击/触摸。