GetWidth () и getHeight () вида возвращают 0

513

Я создаю все элементы в моем проекте Android динамически. Я пытаюсь получить ширину и высоту кнопки, чтобы я мог вращать эту кнопку вокруг. Я просто пытаюсь научиться работать с языком Android. Тем не менее, он возвращает 0.

Я провел некоторое исследование и понял, что это нужно сделать где-то, кроме onCreate()метода. Если кто-то может дать мне пример того, как это сделать, это было бы здорово.

Вот мой текущий код:

package com.animation;

import android.app.Activity;
import android.os.Bundle;
import android.view.animation.Animation;
import android.view.animation.LinearInterpolator;
import android.view.animation.RotateAnimation;
import android.widget.Button;
import android.widget.LinearLayout;

public class AnimateScreen extends Activity {


//Called when the activity is first created.
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    LinearLayout ll = new LinearLayout(this);

    LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
    layoutParams.setMargins(30, 20, 30, 0);

    Button bt = new Button(this);
    bt.setText(String.valueOf(bt.getWidth()));

    RotateAnimation ra = new RotateAnimation(0,360,bt.getWidth() / 2,bt.getHeight() / 2);
    ra.setDuration(3000L);
    ra.setRepeatMode(Animation.RESTART);
    ra.setRepeatCount(Animation.INFINITE);
    ra.setInterpolator(new LinearInterpolator());

    bt.startAnimation(ra);

    ll.addView(bt,layoutParams);

    setContentView(ll);
}

Любая помощь приветствуется.

ngreenwood6
источник

Ответы:

197

Вы звоните getWidth()слишком рано. Пользовательский интерфейс еще не был измерен и выложен на экране.

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

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

CommonsWare
источник
82
ngreenwood6, какое у вас другое решение?
Андрей
12
@Andrew - если вы хотите, чтобы negreenwood6 уведомлялся о ваших последующих действиях, вы должны начать свое сообщение, как я сделал для вас (я думаю, что достаточно первых трех букв) - CommonsWare получает уведомление автоматически, так как он написал этот ответ, но ngreen не если вы не обратитесь к ним.
Питер Айтай
11
@Override public void onWindowFocusChanged (boolean hasFocus) {// TODO Сгенерированный автоматически метод заглушки super.onWindowFocusChanged (hasFocus); // Здесь вы можете получить размер! }
Сана
5
@ ngreenwood6 Итак, что ты решил?
Нима Вазири
5
Используйте этот слушатель, чтобы получить размер, когда ваш экран готов. view.getViewTreeObserver (). addOnGlobalLayoutListener (new ViewTreeObserver.OnGlobalLayoutListener () {}
Синан Диздаревич,
816

Основная проблема заключается в том, что вам нужно дождаться фазы рисования для фактических измерений (особенно с динамическими значениями, такими как wrap_contentили match_parent), но обычно эта фаза не завершена onResume(). Так что вам нужен обходной путь для ожидания этого этапа. Существуют различные возможные решения этого:


1. Прослушивание событий Draw / Layout: ViewTreeObserver

ViewTreeObserver запускается для различных событий рисования. Обычно OnGlobalLayoutListenerэто то, что вы хотите получить для измерения, поэтому код в слушателе будет вызываться после фазы компоновки, поэтому измерения готовы:

view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                view.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                view.getHeight(); //height is ready
            }
        });

Примечание. Слушатель будет немедленно удален, поскольку в противном случае он будет срабатывать при каждом событии макета. Если вам нужно поддерживать приложения SDK Lvl <16, используйте это, чтобы отменить регистрацию слушателя:

public void removeGlobalOnLayoutListener (ViewTreeObserver.OnGlobalLayoutListener victim)


2. Добавьте исполняемый файл в очередь макетов: View.post ()

Не очень известное и мое любимое решение. По сути, просто используйте метод post представления View с вашим собственным runnable. Это в основном ставит ваш код в очередь после измерения представления, макета и т. Д., Как заявлено Romain Guy :

Очередь событий пользовательского интерфейса будет обрабатывать события по порядку. После вызова setContentView () очередь событий будет содержать сообщение с просьбой о ретрансляции, поэтому все, что вы отправляете в очередь, произойдет после передачи макета

Пример:

final View view=//smth;
...
view.post(new Runnable() {
            @Override
            public void run() {
                view.getHeight(); //height is ready
            }
        });

Преимущество перед ViewTreeObserver:

  • Ваш код выполняется только один раз, и вам не нужно отключать Observer после выполнения, что может быть хлопотно
  • менее подробный синтаксис

Ссылки:


3. Перезаписать метод onLayout в представлениях

Это практично только в определенных ситуациях, когда логика может быть заключена в самом представлении, в противном случае это довольно многословный и громоздкий синтаксис.

view = new View(this) {
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        view.getHeight(); //height is ready
    }
};

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


4. Проверьте, прошел ли этап макета

Если у вас есть код, который выполняется несколько раз при создании пользовательского интерфейса, вы можете использовать следующий метод поддержки v4 lib:

View viewYouNeedHeightFrom = ...
...
if(ViewCompat.isLaidOut(viewYouNeedHeightFrom)) {
   viewYouNeedHeightFrom.getHeight();
}

Возвращает true, если представление было выполнено хотя бы через один макет с момента последнего присоединения к окну или отсоединения от него.


Дополнительно: получение статически определенных измерений

Если достаточно просто получить статически определенную высоту / ширину, вы можете сделать это с помощью:

Но учтите, что это может отличаться от фактической ширины / высоты после рисования. Javadoc отлично описывает разницу:

Размер представления выражается шириной и высотой. Вид фактически обладает двумя парами значений ширины и высоты.

Первая пара известна как измеренная ширина и измеренная высота. Эти измерения определяют, насколько большим должно быть представление в пределах его родителя (см. Layout для более подробной информации.) Измеренные измерения можно получить, вызвав getMeasuredWidth () и getMeasuredHeight ().

Вторая пара просто называется шириной и высотой, а иногда шириной и высотой рисования. Эти размеры определяют фактический размер вида на экране, во время рисования и после макета. Эти значения могут, но не обязательно, отличаться от измеренной ширины и высоты. Ширина и высота могут быть получены путем вызова getWidth () и getHeight ().

Патрик Фавр
источник
1
Я использую view.post во фрагменте. Мне нужно измерить вид, чей рост родителей равен 0dp и у него установлен вес. Все, что я пробую, возвращает высоту ноль (я также пробовал глобальный слушатель расположения). Когда я помещаю код view.post в onViewCreated с задержкой 125, он работает, но не без задержки. Идеи?
Psest328
6
Очень жаль, что вы можете ответить только один раз, действительно хорошее объяснение. У меня возникла проблема с ротацией просмотров, если я вышел из приложения и перезапустил его из последних приложений. Если я отмахнулся оттуда и сделал новый перезапуск, все было в порядке. View.post () спас мой день!
Матиас
1
Спасибо. Я обычно использую View.post () (2-й метод), но если он вызывается из фонового потока, он иногда не работает. Итак, не забудьте написать runOnUiThread(new Runnable() {...}. В настоящее время я использую изменение 1 - го метода: addOnLayoutChangeListener. Не забудьте затем удалить его изнутри: removeOnLayoutChangeListener. Это работает из фоновой темы, а также.
CoolMind
2
К сожалению для меня View.post () не работает все время. У меня есть 2 проекта, которые очень похожи. В одном проекте это работает, в другом не работает. :(. В обеих ситуациях я вызываю метод из метода onCreate () из Activity. Любое предложение, почему?
Adrian Buciuman
1
Я нахожу проблему. В одном проекте представление имеет android:visibility="gone", а в другом проекте свойство видимости было invisible. Если видимость будет goneширина, то всегда будет 0.
Адриан Бучуман
262

Мы можем использовать

@Override
 public void onWindowFocusChanged(boolean hasFocus) {
  super.onWindowFocusChanged(hasFocus);
  //Here you can get the size!
 }
Sana
источник
10
так как мы можем работать во фрагментах? это работает только в Activity?
Olkunmustafa
8
Это должно сделать вещь, но это не должно быть ответом. Пользователь хочет получить размеры в любой момент времени вместо того, чтобы рисовать в окне. Кроме того, этот метод вызывается несколько раз или каждый раз, когда в окне есть изменения (View скрыть, пропали, добавить новые представления и т. Д.). Так что используйте его осторожно :)
Доктор АНДРО
1
Это взлом, и кажется, что использование ViewTreeObserver было бы лучше.
i_am_jorf
Не хочу показаться грубым, но оставление автоматически сгенерированного комментария (особенно в двух строках кода) не дает большой уверенности в том, что вы действительно знаете, что делаете.
Natix
К сожалению, это не сработало для меня. Попытка viewTreeObserver сработала для меня.
Нарендра Сингх
109

Я использовал это решение, которое я считаю лучше, чем onWindowFocusChanged (). Если вы откроете DialogFragment, а затем поверните телефон, onWindowFocusChanged будет вызываться только тогда, когда пользователь закрывает диалоговое окно):

    yourView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {

        @Override
        public void onGlobalLayout() {
            // Ensure you call it only once :
            yourView.getViewTreeObserver().removeGlobalOnLayoutListener(this);

            // Here you can get the size :)
        }
    });

Изменить: так как removeGlobalOnLayoutListener устарела, теперь вы должны сделать:

@SuppressLint("NewApi")
@SuppressWarnings("deprecation")
@Override
public void onGlobalLayout() {

    // Ensure you call it only once :
    if(android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
        yourView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
    }
    else {
        yourView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
    }

    // Here you can get the size :)
}
Тим Отин
источник
4
это, безусловно, лучшее решение! потому что он работает также на динамических решениях, где ширина и высота не известны и не рассчитаны. то есть в относительном расположении, основанном на положениях / размерах других элементов. Отлично сработано!
Джордж Плигоропулос
2
Для этого требуется API 16 или выше (Jelly bean)
tomsv
7
НЕ ТРЕБУЕТ API 16! Единственная проблема - это метод removeGlobalOnLayoutListener, устаревший из API 16, но есть простое решение, совместимое со всеми уровнями API - stackoverflow.com/questions/16189525/…
gingo
Если вам нужно удалить прослушиватель так, чтобы он не вызывал себя много раз, и у вас возникли проблемы с различными API, я рекомендую установить ложное логическое значение в заголовке класса, а затем после захвата значений установить его в значение true, чтобы это не захватывает значения снова.
Мансур Фахад
Если вы удалите слушателя после первого запуска, вы можете получить неправильные размеры: 02-14 10: 18: 53.786 15063-15063 / PuzzleFragment: высота: 1647 ширина: 996 02-14 10: 18: 53.985 15063-15063 / PuzzleFragment: высота : 1500 ширина: 996
Леос Литерак
76

Как утверждает Ян в этой теме для разработчиков Android :

В любом случае, дело в том, что расположение содержимого окна происходит после того, как все элементы построены и добавлены в их родительские представления. Так должно быть, потому что, пока вы не узнаете, какие компоненты содержит представление, что они содержат и т. Д., Нет разумного способа выложить его.

В итоге, если вы вызовете getWidth () и т. Д. В конструкторе, он вернет ноль. Процедура состоит в том, чтобы создать все ваши элементы представления в конструкторе, а затем дождаться вызова метода onSizeChanged () вашего View - тогда вы впервые узнаете свой реальный размер, и именно тогда вы установите размеры элементов GUI.

Также имейте в виду, что onSizeChanged () иногда вызывается с нулевыми параметрами - проверьте этот случай и немедленно вернитесь (чтобы вы не делили на ноль при расчете макета и т. Д.). Через некоторое время он будет вызван с реальными значениями.

керем йокува
источник
8
onSizeChanged работал для меня. onWindowFocusedChanged не вызывается в моем пользовательском представлении.
Ааронмарино
это выглядит хорошим решением, но что я должен всегда создавать свой собственный вид для переопределения метода onSizeChanged?
М.Эльсака
Bottom line, if you call getWidth() etc. in a constructor, it will return zero.это бинго Корень моих проблем.
MikeNereson
Я предлагаю это решение для фрагментов, потому что некоторые другие решения возвращают 0 в моем случае.
WindRider
66

Если вам нужно получить ширину какого-либо виджета до его отображения на экране, вы можете использовать getMeasuredWidth () или getMeasuredHeight ().

myImage.measure(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
int width = myImage.getMeasuredWidth();
int height = myImage.getMeasuredHeight();
sulu.dev
источник
5
По некоторым причинам, это был единственный ответ, который работал для меня - спасибо!
Фарбод Саламат-Заде
То же самое для меня, самое простое решение, которое сработало для меня
Тима
Работай как шарм!
Моралес Батовски
1
@CommonsWare, как это решение дает высоту и ширину перед наблюдателем дерева представлений при обратном вызове глобальных макетов, объяснение будет полезным.
Mightian
22

Я бы предпочел использовать OnPreDrawListener()вместо addOnGlobalLayoutListener(), так как он вызывается немного раньше, чем другие слушатели.

    view.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener()
    {
        @Override
        public boolean onPreDraw()
        {
            if (view.getViewTreeObserver().isAlive())
                view.getViewTreeObserver().removeOnPreDrawListener(this);

            // put your code here
            return true;
        }
    });
Аяз Алифов
источник
3
Обратите внимание , что return falseсредства для отмены текущего чертежа пасса . Чтобы продолжить, return trueвместо этого.
Пан
9

Расширение Kotlin для наблюдения за глобальным макетом и выполнения заданной задачи, когда высота готова динамически.

Применение:

view.height { Log.i("Info", "Here is your height:" + it) }

Реализация:

fun <T : View> T.height(function: (Int) -> Unit) {
    if (height == 0)
        viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
            override fun onGlobalLayout() {
                viewTreeObserver.removeOnGlobalLayoutListener(this)
                function(height)
            }
        })
    else function(height)
}
Renetik
источник
7

AndroidX имеет несколько функций расширения, которые помогут вам с такой работой, внутри androidx.core.view

Вы должны использовать Kotlin для этого.

То, что лучше всего подходит здесь doOnLayout:

Выполняет данное действие, когда выложено это представление. Если представление было размечено, и оно не запросило макет, действие будет выполнено сразу же, в противном случае действие будет выполнено после следующего разметки представления.

Действие будет вызвано только один раз в следующем макете, а затем удалено.

В вашем примере:

bt.doOnLayout {
    val ra = RotateAnimation(0,360,it.width / 2,it.height / 2)
    // more code
}

Зависимость: androidx.core:core-ktx:1.0.0

Влад
источник
Это единственный правильный ответ. android.googlesource.com/platform/frameworks/support/+/…
hrules6872
1
эти расширения действительно важны и полезны
Ахмед на
5

Один лайнер, если вы используете RxJava & RxBindings . Подобный подход без шаблонного. Это также решает проблему с подавлением предупреждений, как в ответе Тима Аутина .

RxView.layoutChanges(yourView).take(1)
      .subscribe(aVoid -> {
           // width and height have been calculated here
      });

Это оно. Не нужно отписываться, даже если никогда не звонил.

Odys
источник
4

Это происходит потому, что для представления нужно больше времени надувать. Таким образом, вместо вызова view.widthи view.heightв главном потоке, вы должны использовать, view.post { ... }чтобы убедиться, что ваш viewуже раздули. В Котлине:

view.post{width}
view.post{height}

В Java вы также можете позвонить getWidth()и getHeight()методы в Runnableи передать Runnableв view.post()метод.

view.post(new Runnable() {
        @Override
        public void run() {
            view.getWidth(); 
            view.getHeight();
        }
    });
Эхсан Ноканди
источник
Это неправильный ответ, так как представление может не быть измерено и выложено при выполнении размещенного кода. Это был бы крайний случай, но .postне гарантирует, что представление будет размечено.
d.aemon
1
Этот ответ был великолепен и работал для меня. Большое спасибо Ehsan
MMG
3

Высота и ширина равны нулю, поскольку представление не было создано к тому времени, когда вы запрашиваете его высоту и ширину. Одним из самых простых решений является

view.post(new Runnable() {
        @Override
        public void run() {
            view.getHeight(); //height is ready
            view.getWidth(); //width is ready
        }
    });

Этот метод хорош по сравнению с другими методами, поскольку он короткий и четкий.

Шивам Ядав
источник
3

Может быть, это кому-то поможет

Создать функцию расширения для класса View

имя файла: ViewExt.kt

fun View.afterLayout(what: () -> Unit) {
    if(isLaidOut) {
        what.invoke()
    } else {
        viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
            override fun onGlobalLayout() {
                viewTreeObserver.removeOnGlobalLayoutListener(this)
                what.invoke()
            }
        })
    }
}

Это может быть использовано в любом представлении с:

view.afterLayout {
    do something with view.height
}
Pepijn
источник
2

postНеверный ответ , потому что размер не может быть пересчитан.
Еще одна важная вещь заключается в том, что вид и все его предки должны быть видны . Для этого я использую свойство View.isShown.

Вот моя функция kotlin, которую можно разместить где-нибудь в утилитах:

fun View.onInitialized(onInit: () -> Unit) {
    viewTreeObserver.addOnGlobalLayoutListener(object : OnGlobalLayoutListener {
        override fun onGlobalLayout() {
            if (isShown) {
                viewTreeObserver.removeOnGlobalLayoutListener(this)
                onInit()
            }
        }
    })
}

И использование это:

myView.onInitialized {
    Log.d(TAG, "width is: " + myView.width)
}
pavelperc
источник
1

Если вы используете Kotlin

  leftPanel.viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {

            override fun onGlobalLayout() {

                if(android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
                    leftPanel.viewTreeObserver.removeOnGlobalLayoutListener(this)
                }
                else {
                    leftPanel.viewTreeObserver.removeGlobalOnLayoutListener(this)
                }

                // Here you can get the size :)
                leftThreshold = leftPanel.width
            }
        })
Хите саху
источник
0

Прошедшие просмотры возвращают 0 как высоту, если приложение в фоновом режиме. Это мой код (1оо% работает)

fun View.postWithTreeObserver(postJob: (View, Int, Int) -> Unit) {
    viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
        override fun onGlobalLayout() {
            val widthSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
            val heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
            measure(widthSpec, heightSpec)
            postJob(this@postWithTreeObserver, measuredWidth, measuredHeight)
            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
                @Suppress("DEPRECATION")
                viewTreeObserver.removeGlobalOnLayoutListener(this)
            } else {
                viewTreeObserver.removeOnGlobalLayoutListener(this)
            }
        }
    })
}

источник
0

Нужно дождаться, когда вид будет нарисован. Для этого используйте OnPreDrawListener. Пример Kotlin:

val preDrawListener = object : ViewTreeObserver.OnPreDrawListener {

                override fun onPreDraw(): Boolean {
                    view.viewTreeObserver.removeOnPreDrawListener(this)

                    // code which requires view size parameters

                    return true
                }
            }

            view.viewTreeObserver.addOnPreDrawListener(preDrawListener)
Артем Ботнев
источник