Как получить размеры вида?

209

У меня есть представление, состоящее из TableLayout, TableRow and TextView. Я хочу, чтобы это выглядело как сетка. Мне нужно получить высоту и ширину этой сетки. Методы getHeight()иgetWidth() всегда возвращают 0. Это происходит, когда я динамически форматирую сетку, а также когда я использую версию XML.

Как получить размеры для вида?


Вот моя тестовая программа, которую я использовал в Debug для проверки результатов:

import android.app.Activity;
import android.os.Bundle;
import android.widget.TableLayout;
import android.widget.TextView;

public class appwig extends Activity {  
    @Override
    public void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.maindemo);  //<- includes the grid called "board"
      int vh = 0;   
      int vw = 0;

      //Test-1 used the xml layout (which is displayed on the screen):
      TableLayout tl = (TableLayout) findViewById(R.id.board);  
      tl = (TableLayout) findViewById(R.id.board);
      vh = tl.getHeight();     //<- getHeight returned 0, Why?  
      vw = tl.getWidth();     //<- getWidth returned 0, Why?   

      //Test-2 used a simple dynamically generated view:        
      TextView tv = new TextView(this);
      tv.setHeight(20);
      tv.setWidth(20);
      vh = tv.getHeight();    //<- getHeight returned 0, Why?       
      vw = tv.getWidth();    //<- getWidth returned 0, Why?

    } //eof method
} //eof class
Сагар Зала
источник
вместо использования getWidth / Height используйте getMeasuredWidth / Height после применения макета к действию.
бхупс
9
Ребята, все, что нужно сделать, это позвонить, getHeight()и getWidth()после того, как жизненный цикл Activity попросил представления измерить себя, другими словами, сделать что-то подобное onResume()и все. Вы не должны ожидать, что еще не выложенный объект будет знать свои размеры, вот и вся хитрость. Нет необходимости в магии, предложенной ниже.
Штабелер класса
2
Вызов getHeight()/Width()в onResume()не поддавались> 0 значения для меня.
Дарпан
@ClassStacker Как насчет фрагмента?
ZDD
@zdd Пожалуйста, задайте совершенно новый вопрос и оставьте ссылку здесь в комментарии.
Штабелер

Ответы:

418

Я считаю, что ОП уже давно нет, но в случае, если этот ответ поможет будущим поисковикам, я подумал, что опубликую найденное мной решение. Я добавил этот код в свойonCreate() метод:

ИЗДАН: 07/05/11, чтобы включить код из комментариев:

final TextView tv = (TextView)findViewById(R.id.image_test);
ViewTreeObserver vto = tv.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {

    @Override
    public void onGlobalLayout() {
        LayerDrawable ld = (LayerDrawable)tv.getBackground();
        ld.setLayerInset(1, 0, tv.getHeight() / 2, 0, 0);
        ViewTreeObserver obs = tv.getViewTreeObserver();

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
            obs.removeOnGlobalLayoutListener(this);
        } else {
            obs.removeGlobalOnLayoutListener(this);
        }
    }

});

Сначала я получаю окончательную ссылку на свой TextView(для доступа в onGlobalLayout()методе). Затем я получаю ViewTreeObserverот my TextViewи добавляю OnGlobalLayoutListenerпереопределение onGLobalLayout(здесь, похоже, нет метода суперкласса для вызова здесь ...) и добавляю свой код, который требует знания измерений представления в этом слушателе. У меня все работает как положено, поэтому надеюсь, что это поможет.

Кевин Коппок
источник
23
@ Джордж Бэйли: Одна вещь, которую я недавно видел, кто-то еще пост (я сам не тестировал, поэтому YMMV), чтобы преодолеть множественные вызовы, была (в onGlobalLayout ()): tv.getViewTreeObserver().removeGlobalOnLayoutListener(this);таким образом, слушатель должен быть удален после первого раза такое случается.
Кевин Коппок
3
Я тоже хочу помочь. Вам может потребоваться сделать что-то вроде: mView.getViewTreeObserver (). RemoveGlobalOnLayoutListener (globalLayoutListener); если вы хотите изменить свой вид только один раз. Надеюсь, что это поможет
Генри Су
7
Вопрос очень старый, но использование addOnLayoutChangeListenerработает также! :)
FX
7
Также обратите внимание, что начиная с API 16 вам необходимо: if (android.os.Build.VERSION.SDK_INT> = 16) {view.getViewTreeObserver (). RemoveOnGlobalLayoutListener (this); } else {view.getViewTreeObserver (). removeGlobalOnLayoutListener (this); }
Эдисон
2
Так как removeGlobalOnLayoutListener устарел, он по-прежнему выдает предупреждения в Eclipse. Это нормально? Со временем устарелые предупреждения о кодах будут затоплены повсюду ...
Джонни
82

Я просто добавлю альтернативное решение, переопределю метод onWindowFocusChanged вашей деятельности, и вы сможете получать значения getHeight (), getWidth () оттуда.

@Override
public void onWindowFocusChanged (boolean hasFocus) {
        // the height will be set at this point
        int height = myEverySoTallView.getMeasuredHeight(); 
}
JustDanyul
источник
4
Цитирую документацию: «Это лучший показатель того, видна ли эта активность пользователю».
Кэмерон Лоуэлл Палмер
6
Это не работает в случае фрагментов, которые вы должны использовать ViewTreeObserver.
Михир
ОП специально спросил, как получить его для просмотра, хотя
JustDanyul
общая высота scrollview и максимальная Scrollview.getScrolly () будут одинаковыми? т.е. в моем случае я получил Высота с вышеупомянутым методом, равный 702, и я получил максимальное значение от getScrolly (),
равное
Этот метод также не работает, когда ваш вид включен из другого файла XML.
Джей Донга
19

Вы пытаетесь получить ширину и высоту элементов, которые еще не были нарисованы.

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

И, возможно, я ошибаюсь, но setWidth()не всегда соблюдаю, Layoutвыкладываю свои дочерние элементы и решаю, как их измерить (вызывая child.measure()), поэтому, если вы установите setWidth(), вы не гарантированно получите эту ширину после отрисовки элемента.

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

Посмотрите в Activityжизненный цикл, чтобы найти лучший момент.

http://developer.android.com/reference/android/app/Activity.html#ActivityLifecycle

Я считаю, что хорошей практикой является использование OnGlobalLayoutListenerтак:

yourView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
        @Override
        public void onGlobalLayout() {
            if (!mMeasured) {
                // Here your view is already layed out and measured for the first time
                mMeasured = true; // Some optional flag to mark, that we already got the sizes
            }
        }
    });

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

Алекс Орлов
источник
К сожалению, он не работает и в OnResume. Окончательные размеры задаются где-то после вызова OnResume, по крайней мере, в эмуляторе.
JKK
Это ужасная практика !!! Этот слушатель будет вызываться снова и снова и убьет ваше выступление. Вместо использования флагов, отмените регистрацию слушателя, когда закончите.
Bluewhile
15

Используйте метод сообщения View, как этот

post(new Runnable() {   
    @Override
    public void run() {
        Log.d(TAG, "width " + MyView.this.getMeasuredWidth());
        }
    });
Борис Русев
источник
1
Это работает, но на самом деле, нет обсуждения почему? Кстати, это работает, потому что исполняемый файл не запускается, пока не возникнет макет.
Норман Х
7

Я пытался использовать onGlobalLayout()для некоторого пользовательского форматирования a TextView, но, как заметил @George Bailey, onGlobalLayout()он действительно вызывается дважды: один раз на начальном пути макета и второй раз после изменения текста.

View.onSizeChanged()работает лучше для меня, потому что, если я изменю текст там, метод вызывается только один раз (во время прохождения макета). Это требует подкласса TextView, но на уровне API 11+ View. addOnLayoutChangeListener()можно использовать, чтобы избежать подкласса.

Еще одна вещь, чтобы получить правильную ширину вида View.onSizeChanged(), layout_widthдолжна быть установлена match_parent, а не wrap_content.

Y2i
источник
2

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

Вы не получите никаких измерений, пока все компоненты не будут измерены (так как ваш xml не знает о размере вашего дисплея, родительских позициях и т. Д.)

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

Алекс Орлов
источник
Я обновил свой оригинальный вопрос и код, чтобы быть более понятным. Спасибо.
2

Как уже упоминалось FX, вы можете использовать OnLayoutChangeListenerдля представления, что вы хотите отслеживать себя

view.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
    @Override
    public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
        // Make changes
    }
});

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

Zeophlite
источник
2

Я думаю, это то, на что вам нужно обратить внимание: использование onSizeChanged()вашего взгляда. Вот расширенный фрагмент кода о том, как использовать onSizeChanged()для динамического получения высоты и ширины макета или представления http://syedrakibalhasan.blogspot.com/2011/02/how-to-get-width-and-height-dimensions.html

Ракиб
источник
1

ViewTreeObserverи onWindowFocusChanged()вовсе не нужны.

Если вы надуете TextViewкак макет и / или поместите в него какой-то контент и установите, LayoutParamsто вы можете использовать getMeasuredHeight()и getMeasuredWidth().

НО вы должны быть осторожныLinearLayouts (может быть, и другие ViewGroups). Проблема в том, что вы можете получить ширину и высоту после этого, onWindowFocusChanged()но если вы попытаетесь добавить в него некоторые виды, то вы не сможете получить эту информацию, пока все не будет нарисовано. Я пытался добавить несколько TextViewsк, LinearLayoutsчтобы имитировать FlowLayout(стиль упаковки) и поэтому не мог использовать Listeners. Как только процесс запущен, он должен продолжаться синхронно. Поэтому в таком случае вы можете захотеть сохранить ширину в переменной, чтобы использовать ее позже, так как при добавлении видов в макет она может понадобиться.

Genom
источник
1

Несмотря на то, что предлагаемое решение работает, оно может быть не лучшим решением для каждого случая, потому что на основе документации для ViewTreeObserver.OnGlobalLayoutListener

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

это означает, что он вызывается много раз, и не всегда измеряется представление (для него определены высота и ширина)

Альтернативой является использование, ViewTreeObserver.OnPreDrawListenerкоторое вызывается только тогда, когда представление готово к рисованию и имеет все свои измерения.

final TextView tv = (TextView)findViewById(R.id.image_test);
ViewTreeObserver vto = tv.getViewTreeObserver();
vto.addOnPreDrawListener(new OnPreDrawListener() {

    @Override
    public void onPreDraw() {
        tv.getViewTreeObserver().removeOnPreDrawListener(this);
        // Your view will have valid height and width at this point
        tv.getHeight();
        tv.getWidth();
    }

});
Кайван Н
источник
0

Вам лучше взглянуть на View жизненный цикл: http://developer.android.com/reference/android/view/View.html Как правило, вы не должны знать ширину и высоту наверняка, пока ваша активность не перейдет в состояние onResume.

Андрей Новиков
источник
0

Вы можете использовать трансляцию, которая вызывается в OnResume ()

Например:

int vh = 0;   
int vw = 0;

public void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.maindemo);  //<- includes the grid called "board"

      registerReceiver(new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                TableLayout tl = (TableLayout) findViewById(R.id.board);  
                tl = (TableLayout) findViewById(R.id.board);
                vh = tl.getHeight();       
                vw = tl.getWidth();               
            }
      }, new IntentFilter("Test"));
}

protected void onResume() {
        super.onResume();

        Intent it = new Intent("Test");
        sendBroadcast(it);

}

Вы не можете получить высоту представления в OnCreate (), onStart () или даже в onResume () по причине того, что kcoppock ответил

Лукас Феррейра Батиста
источник
0

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

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

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

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

Простой ответ: Это сработало для меня без проблем. Кажется, ключ состоит в том, чтобы убедиться, что представление имеет фокус, прежде чем вы получите getHeight и т. Д. Сделайте это с помощью метода hasFocus (), а затем с помощью метода getHeight () в этом порядке. Требуется всего 3 строки кода.

ImageButton myImageButton1 =(ImageButton)findViewById(R.id.imageButton1); myImageButton1.hasFocus();

int myButtonHeight = myImageButton1.getHeight();

Log.d("Button Height: ", ""+myButtonHeight );//Not required

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

Monk4D
источник
-3

Используйте getMeasuredWidth () и getMeasuredHeight () для вашего представления.

Руководство разработчика: Просмотреть

Владимир Иванов
источник
4
Как указывалось ранее, эти методы возвращают действительный результат только после того, как размер был измерен. Внутри Activity это верно, когда вызывается метод onWindowFocusChanged.
slott
-8

ИСПРАВЛЕНИЕ: Я обнаружил, что вышеуказанное решение ужасно. Особенно, когда ваш телефон работает медленно. И здесь я нашел другое решение: вычислить значение px элемента, включая поля и отступы: dp to px: https://stackoverflow.com/a/6327095/1982712

или измерения от .xml до px: https://stackoverflow.com/a/16276351/1982712

sp to px: https://stackoverflow.com/a/9219417/1982712 (отменить решение)

или размеры в px: https://stackoverflow.com/a/16276351/1982712

и это все.

pimkle
источник
10
Обычно плохая идея надеяться, что все будет установлено правильно после некоторого произвольно выбранного числа миллисекунд. Устройство может работать медленно, другие процессы / потоки могут работать, могут не работать в отладчике, могут не работать в следующей версии ОС, ...
Кристофер Джонсон