GetWidth () и getHeight () вида возвращают 0 - PullRequest
468 голосов
/ 28 августа 2010

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

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

Ответы [ 13 ]

723 голосов
/ 04 июня 2014

Основная проблема заключается в том, что вам нужно дождаться фазы рисования для фактических измерений (особенно с динамическими значениями, такими как 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 </em>, используйте это, чтобы отменить регистрацию слушателя:

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 отлично описывает разницу:

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

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

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

260 голосов
/ 12 апреля 2012

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

@Override
 public void onWindowFocusChanged(boolean hasFocus) {
  super.onWindowFocusChanged(hasFocus);
  //Here you can get the size!
 }
180 голосов
/ 29 августа 2010

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

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

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

106 голосов
/ 22 марта 2013

Я использовал это решение, которое, я думаю, лучше, чем 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 :)
}
75 голосов
/ 25 декабря 2010

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

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

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

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

62 голосов
/ 29 апреля 2014

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

myImage.measure(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
int width = myImage.getMeasuredWidth();
int height = myImage.getMeasuredHeight();
17 голосов
/ 22 марта 2017

Я бы лучше использовал 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;
        }
    });
6 голосов
/ 15 августа 2018

Расширение 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)
}
3 голосов
/ 30 декабря 2017

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

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

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

1 голос
/ 07 марта 2019

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

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

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

...