Как получить абсолютные координаты вида

390

Я пытаюсь получить абсолютные координаты пикселей экрана в верхнем левом углу представления. Тем не менее, все методы, которые я могу найти, такие как getLeft()и getRight()не работают, так как все они кажутся относительными к родителю представления, таким образом, дают мне 0. Как правильно это сделать?

Если это поможет, это для игры «навести порядок». Я хочу, чтобы пользователь мог нарисовать прямоугольник, чтобы выбрать несколько частей. Мое предположение состоит в том , что самый простой способ сделать это, чтобы getRawX()и getRawY()из , MotionEventа затем сравнить эти значения с верхним левым углом расположения удерживающей части. Зная размер кусочков, я могу определить, сколько кусочков было выбрано. Я понимаю, что могу использовать 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]);

Этот код был добавлен после завершения инициализации. Изображение было разделено на массив ImageViews (массив cell []), содержащийся в a TableLayout. Ячейки [0] находятся в верхнем левом углу ImageView, и я выбрал ячейки [4], так как они где-то посередине и, безусловно, не должны иметь координаты (0,0).

Код, показанный выше, все еще дает мне все 0 в журналах, которые я действительно не понимаю, потому что различные части головоломки отображаются правильно. (Я попробовал public int для tableLayoutCorners и видимости по умолчанию, оба дали одинаковый результат.)

Я не знаю, значимо ли это, но ImageViewизначально размер не указан. Размер ImageViews определяется во время инициализации автоматически при просмотре, когда я даю ему изображение для отображения. Может ли это способствовать тому, что их значения будут равны 0, даже если этот код регистрации будет после того, как они получили изображение и автоматически изменили размеры сами? Чтобы потенциально противостоять этому, я добавил код, tableLayout.requestLayout()как показано выше, но это не помогло.

Стив Хейли
источник

Ответы:

632

Используйте View.getLocationOnScreen()и / или getLocationInWindow().

Ромэн Гай
источник
3
Вот такую ​​функцию я ожидал найти. К сожалению, я не должен использовать это должным образом. Я понимаю, что это была известная ошибка, что getLocationOnScreen выдает исключение нулевого указателя в Android v1.5 (платформа, для которой я кодирую), но тестирование в v2.1 не сработало лучше. Оба метода дали мне 0 для всего. Я включил фрагмент кода выше.
Стив Хейли
76
Вы можете только вызвать его ПОСЛЕ макет произошло. Вы вызываете метод до того, как представления будут размещены на экране.
Romain Guy
5
Ага, вы правы, хотя было не очевидно, что я это делал. Спасибо! Для тех, кому интересно узнать больше, я добавил ответ, который объясняет, что я имею в виду.
Стив Хейли
9
Вы имеете в виду, как ViewTreeObserver и его OnGlobalLayoutListener? См. View.getViewTreeObserver (), чтобы начать.
Romain Guy
8
@RomainGuy Да, вроде как, но менее запутанно, чем необходимость регистрироваться (а затем отменять регистрацию…). Это область, где iOS делает это лучше, чем Android с точки зрения SDK. Я могу сказать, что в iOS каждый день есть много неприятных вещей, но UIView не относится к ним. :)
Мартин Маркончини
127

Принятый ответ на самом деле не говорит, как получить местоположение, поэтому здесь немного подробнее. Вы передаете intмассив длины 2, и значения заменяются координатами представления (x, y) (верхнего левого угла).

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

Ноты

  • Замена getLocationOnScreenна getLocationInWindowдолжна давать одинаковые результаты в большинстве случаев (см. Этот ответ ). Однако, если вы находитесь в меньшем окне, например, в диалоговом окне или на пользовательской клавиатуре, вам нужно будет выбрать, какое из них лучше соответствует вашим потребностям.
  • Вы получите, (0,0)если вызовете этот метод, onCreateпотому что представление еще не было выложено. Вы можете использовать ViewTreeObserverдля прослушивания, когда макет сделан, и вы можете получить измеренные координаты. (Смотрите этот ответ .)
Suragch
источник
86

Сначала вы должны получить локально видимый прямоугольник вида

Например:

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

Надеюсь, это поможет

Навин Мурти
источник
3
Это также должно было сработать ... Однако это также дает мне значения 0. Я включил фрагмент кода выше, если это поможет вам понять, что я сделал не так. Еще раз спасибо.
Стив Хейли
1
Смотрите мои другие комментарии; Я случайно назвал это прежде, чем Представления закончили выкладываться. Теперь это работает нормально, спасибо.
Стив Хейли
@Naveen Murthy дает координаты для конкретного вида, мне нужны координаты кликаемого вида в корневом макете ..
Aniket
20
это возвращает местоположение относительно родителя. использовать getGlobalVisibleRect()для абсолютных координат.
Джеффри Блаттман,
3
@ SteveHaley, я столкнулся с той же проблемой, что и вы, по какой-то причине он дает мне 0 в каждом методе, который я пробую. Кажется, вы поняли, почему это дает ноль. Я пытаюсь получить координаты вида после загрузки макета, но я получаю ноль, почему это так? Спасибо за вашу помощь
Pankaj Nimgade
46

Использование глобального макета слушателя всегда хорошо работало для меня. Он имеет преимущество в том, что может перезаписывать вещи при изменении макета, например, если что-то установлено в 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);
 }
Саймон
источник
Не забудьте удалить OnGlobalLayoutListener внутри (в конце) onGlobalLayout () {...}. Кроме того, просто чтобы убедиться, проверьте, что передано! = 0; слушатель может быть вызван дважды, один раз со значением w = 0 / h = 0, второй раз с действительными, правильными значениями.
MacD
Этот способ раздувания основного макета и последующего использования setContentView()вызвал проблему в специальном Activityприложении в моем приложении! Это было диалоговое занятие ( windowFrame:@null, windowActionBar:false, windowIsFloating:true, windowNoTitle:true). Его основной план (а LinearLayout) не имеет прямого потомка с определенным layout_width(все они были match_parentили wrap_content).
Мир Исмаили
17

После комментария Ромена Гая вот как я это исправил. Надеюсь, это поможет кому-нибудь еще, у кого также была эта проблема.

Я действительно пытался выяснить положение представлений до того, как они были выложены на экран, но это не было очевидно, что происходило. Эти строки были размещены после запуска кода инициализации, поэтому я предположил, что все было готово. Тем не менее, этот код все еще был в onCreate (); Поэкспериментировав с Thread.sleep (), я обнаружил, что макет на самом деле не завершен до тех пор, пока onCreate () полностью не завершит выполнение onResume (). И действительно, код пытался выполнить до того, как макет был размещен на экране. При добавлении кода в OnClickListener (или некоторый другой Listener) были получены правильные значения, потому что он мог быть запущен только после завершения макета.


Строка ниже была предложена как правка сообщества:

пожалуйста, используйте onWindowfocuschanged(boolean hasFocus)

Стив Хейли
источник
Итак, вы хотите сказать, что setContentView (my_layout); не будет размещать все виды. Все представления будут размещены только после завершения onCreate ()?
Эшвин
5
Не совсем. Все представления «существуют» после setContentView(R.layout.something), но они не были загружены визуально. У них нет размера или позиции. Это не произойдет, пока не завершится первый проход макета, что произойдет в какой-то момент в будущем (обычно после onResume ()).
Стив Хейли
14

Первый путь:

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

и просто получить абсолютное X view.absX()и Y поview.absY()

Амир Хоссейн Гасеми
источник
6
Укороченная версия для вашего первого образца:val location = IntArray(2).apply(::getLocationOnScreen)
Туан Чау
5

Моя утилита для определения местоположения, она возвращает Pointобъект с x valueиy value

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);
Фан Ван Линь
источник
5

Вы можете получить координаты вида используя 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];

ПРИМЕЧАНИЕ. Если значение всегда равно 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];
});

~~

Kotlin Solution

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

ПРИМЕЧАНИЕ. Если значение всегда равно 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)
    }
}
Gibolt
источник
0

Используйте этот код, чтобы найти точные X и Y кординаты:

int[] array = new int[2];
ViewForWhichLocationIsToBeFound.getLocationOnScreen(array);
if (AppConstants.DEBUG)
    Log.d(AppConstants.TAG, "array  X = " + array[0] + ", Y = " + array[1]);
ViewWhichToBeMovedOnFoundLocation.setTranslationX(array[0] + 21);
ViewWhichToBeMovedOnFoundLocation.setTranslationY(array[1] - 165);

Я добавил / вычел некоторые значения, чтобы скорректировать мой взгляд. Пожалуйста, делайте эти строки только после того, как весь вид был раздут.

Tarun
источник
«Я добавил / вычел некоторые значения, чтобы скорректировать мой взгляд». Почему??
ToolmakerSteve
Я имею в виду в соответствии с моим требованием (размещение вида), я добавил и вычел значения, чтобы скорректировать мой вид.
Тарун
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]
                }

            }
        });
    }
Хите саху
источник
0

Просто в дополнение к ответам выше, на вопрос, куда и когда вам следует позвонить getLocationOnScreen?

Любая информация, относящаяся к любому представлению, которое вы хотите получить, будет доступна только после того, как представление было размещено на экране. Вы можете легко сделать это, поместив свой код, который зависит от создания представления внутри этого (view.post (Runnable)):

view.post(new Runnable() {
            @Override
            public void run() {

               // This code will run view created and rendered on screen

               // So as the answer to this question, you can put
               int[] location = new int[2];
               myView.getLocationOnScreen(location);
               int x = location[0];
               int y = location[1];
            }
        });  
Сурадж Вайшнав
источник