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

424

Я создаю все элементы в моем проекте 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);
}

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

Теги:
android-layout
getter

12 ответов

179
Лучший ответ

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

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

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

  • 0
    Спасибо за ответ. На самом деле я нашел другое решение, которое сработало для меня, но это хорошая деталь, которую нужно знать.
  • 77
    ngreenwood6, какое у вас другое решение?
Показать ещё 5 комментариев
703

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


1. Слушайте события Draw/Layout: ViewTreeObserver

A 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. Добавить runnable в очередь макета: View.post()

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

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

Пример:

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

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

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

Литература:


3. Перезаписать Views onLayout Method

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

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. Проверьте, прошла ли фаза компоновки

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

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

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


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

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

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

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

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

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

  • 0
    onLayout был для меня ключом, чтобы обнаружить отображение моего вида и установить высоту его на основе его новой ширины :) Я только что добавил флаг, чтобы отключить вызываемый, если он больше не нужен
  • 1
    Я использую view.post во фрагменте. Мне нужно измерить вид, чей рост у родителей равен 0dp и у него установлен вес. Все, что я пробую, возвращает высоту ноль (я также пробовал глобальный слушатель расположения). Когда я помещаю код view.post в onViewCreated с задержкой 125, он работает, но не без задержки. Идеи?
Показать ещё 14 комментариев
244

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

@Override
 public void onWindowFocusChanged(boolean hasFocus) {
  super.onWindowFocusChanged(hasFocus);
  //Here you can get the size!
 }
  • 10
    так как мы можем работать во фрагментах? это работает только в Activity?
  • 7
    Это должно сделать вещь, но это не должно быть ответом. Пользователь хочет получить размеры в любой момент времени вместо того, чтобы рисовать в окне. Кроме того, этот метод вызывается несколько раз или каждый раз, когда в окне есть изменения (View скрыть, пропали, добавить новые представления и т. Д.). Так что используйте его осторожно :)
Показать ещё 4 комментария
96

Я использовал это решение, которое, я думаю, лучше, чем 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 :)
}
  • 3
    это, безусловно, лучшее решение! потому что он работает также на динамических решениях, где ширина и высота не известны и не рассчитаны. то есть в относительном расположении, основанном на положениях / размерах других элементов. Отлично сработано!
  • 2
    Это требует API 16 или выше (Jelly Bean)
Показать ещё 8 комментариев
77

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

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

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

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

  • 8
    onSizeChanged работал для меня. onWindowFocusedChanged не вызывается в моем пользовательском представлении.
  • 0
    это выглядит хорошим решением, но что я должен всегда создавать свой собственный вид для переопределения метода onSizeChanged?
Показать ещё 2 комментария
57

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

myImage.measure(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
int width = myImage.getMeasuredWidth();
int height = myImage.getMeasuredHeight();
  • 2
    Умное решение.
  • 4
    По некоторым причинам, это был единственный ответ, который работал для меня - спасибо!
Показать ещё 5 комментариев
13

Я бы предпочел использовать 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 false;
        }
    });
  • 1
    это работает лучше, чем view.post
  • 0
    Огромное спасибо!! :)
Показать ещё 1 комментарий
4

Расширение для получения высоты в Котлине динамически.

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

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

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

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

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

  • 1
    должен быть принятым ответом в нашем поколении
0

Если вы используете 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

После некоторых тестов на Android 7.1.2 я нашел только эту опцию действительно полезной, все остальные вернули 0

    view.postDelayed({
        height = view.height
    },100)

Самое смешное, что я заметил, это результат зависит от времени задержки. Когда я установил аргумент delayMillis в 60, я получил height= 0, а delayMillis= 61 height уже имел значение. Поэтому я думаю, delayMillis= 100 должно быть достаточно.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню