Android getMeasuredHeight возвращает неверные значения!

Я пытаюсь определить реальный размер некоторых элементов пользовательского интерфейса в пикселях!

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

<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="150dip"
android:orientation="vertical">
    
<ImageView
    android:id="@+id/TlFrame" 
    android:layout_width="110dip" 
    android:layout_height="90dip"
    android:src="@drawable/timeline_nodrawing"
    android:layout_margin="0dip"
    android:padding="0dip"/></LinearLayout>

Этот предыдущий xml представляет собой один кадр. Но я добавляю многие динамически внутри горизонтального макета, описанного здесь:

<HorizontalScrollView 
        android:id="@+id/TlScroller"
        android:layout_width="wrap_content" 
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:layout_alignParentBottom="true"
        android:layout_alignParentLeft="true"
        android:layout_margin="0dip"
        android:padding="0dip"
        android:scrollbars="none"
        android:fillViewport="false"
        android:scrollbarFadeDuration="0"
        android:scrollbarDefaultDelayBeforeFade="0"
        android:fadingEdgeLength="0dip"
        android:scaleType="centerInside">
        
        <!-- HorizontalScrollView can only host one direct child -->
        <LinearLayout 
            android:id="@+id/TimelineContent" 
            android:layout_width="fill_parent" 
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            android:layout_alignParentTop="true"
            android:layout_alignParentLeft="true"
            android:layout_margin="0dip"
            android:padding="0dip"
            android:scaleType="centerInside"/>
   
    </HorizontalScrollView > 

Метод, определенный для добавления одного кадра в мой код Java:

private void addNewFrame()
{       
    LayoutInflater inflater     = (LayoutInflater) _parent.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    ViewGroup root      = (ViewGroup) inflater.inflate(R.layout.tl_frame, null);
    TextView frameNumber = (TextView) root.findViewById(R.id.FrameNumber);
    Integer val = new Integer(_nFramesDisplayed+1); //+1 to display ids starting from one on the user side 
    frameNumber.setText(val.toString());
    
    ++_nFramesDisplayed;
    _content.addView(root);
// _content variable is initialized like this in c_tor
// _content = (LinearLayout) _parent.findViewById(R.id.TimelineContent);
}

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

LayoutInflater inflater     = (LayoutInflater) _parent.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    ViewGroup root      = (ViewGroup) inflater.inflate(R.layout.tl_frame, null);
    ImageView frame = (ImageView) root.findViewById(R.id.TlFrame);
    
    frame.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
    frame.measure(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);

    final int w = frame.getMeasuredWidth();
    final int h = frame.getMeasuredHeight();

Кажется, все работает нормально, за исключением того, что эти значения намного больше, чем фактический размер пикселя ImageView.

Информация, полученная от getWindowManager().getDefaultDisplay().getMetrics(metrics); следующие: плотность = 1,5, плотность Dpi = 240, ширина пикселей = 600, высота пикселей = 1024.

Теперь я знаю правило Android: пиксель = наклон * (dpi / 160). Но ничего не имеет смысла с возвращаемым значением. Для этого ImageView (90dip X 110dip) возвращаемые значения метода Measure() равны (270 x 218), которые, как я предполагал, в пикселях!

Кто-нибудь знает, почему? Возвращается ли значение в пикселях?

Кстати: я тестировал тот же код, но с TextView вместо ImageView, и все работает нормально! Почему !?!?


person oberthelot    schedule 27.05.2011    source источник


Ответы (7)


Вы неправильно звоните measure.

measure принимает значения MeasureSpec, специально упакованные MeasureSpec.makeMeasureSpec. measure игнорирует LayoutParams. Ожидается, что родитель, выполняющий измерение, создаст MeasureSpec на основе своей собственной стратегии измерения и компоновки, а также параметров LayoutParams дочернего элемента.

Если вы хотите измерить, как WRAP_CONTENT обычно работает в большинстве макетов, вызовите measure следующим образом:

frame.measure(MeasureSpec.makeMeasureSpec(maxWidth, MeasureSpec.AT_MOST),
        MeasureSpec.makeMeasureSpec(maxHeight, MeasureSpec.AT_MOST));

Если у вас нет максимальных значений (например, если вы пишете что-то вроде ScrollView с бесконечным пространством), вы можете использовать режим UNSPECIFIED:

frame.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
        MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
person adamp    schedule 27.05.2011
comment
Кажется, это тоже не работает... Я пробовал каждую комбинацию максимальных значений и MesureSpec, и результат либо такой же, как и раньше, либо поддельные максимальные значения, которые я использовал для теста, или ноль. - person oberthelot; 30.05.2011
comment
Я не знаю, может ли это быть связано с этим... Но LinearLayout выше добавляется динамически (внутри java-кода)... кадр за кадром внутри HorizontalScrollView, внутри LinearLayout.... Цель состоит в том, чтобы создать динамический графический интерфейс с временной шкалой и рисование внутри с помощью openGl! - person oberthelot; 30.05.2011
comment
Есть ли способ динамически установить ширину и высоту ImageView??? Я думаю о том, чтобы сделать расчет самостоятельно в зависимости от размера экрана и dpi ... - person oberthelot; 30.05.2011
comment
О каких значениях сообщается и как выглядит результат? Вы пытаетесь измерить ImageView, но он будет повторно измерен (возможно, с другими MeasureSpecs) его родителем во время компоновки. Если вы хотите, чтобы процесс компоновки выполнялся, а затем анализировался, вы можете post запускать Runnable после завершения стандартного этапа измерения/макета. - person adamp; 30.05.2011
comment
Я уже пробовал это ... Настройка процесса макета долго завершается, когда я пытаюсь получить эти измерения (временная шкала появляется только при действии пользователя, а не при инициализации), публикация отложенного исполняемого файла с 5000 milsec ничего не изменила. Что касается результата, он выглядит нормально, но мне нужно настроить glScissor для моего рендеринга, и ясно, что размер не подходит. Я рассчитал его, и он должен быть 165x135 пикселей, и измерение Android возвращает (270x218 пикселей), фактический размер изображения составляет 180x145, а настройка xml - 110x90. - person oberthelot; 30.05.2011
comment
Можете ли вы опубликовать еще немного окружающего кода? Похоже, здесь не хватает по крайней мере одного кусочка головоломки. (Вы публиковали runnable после добавления поддерева представления в активную иерархию, верно?) - person adamp; 30.05.2011
comment
Да, я был... Сначала весь макет инициализируется из xml (в onCreate действия)... HorizontalScrollView, содержащий imageViews, установлен на скрытый... он появляется при вводе пользователя, и только на этом вводе Я пытаюсь получить измерение, чтобы сделать некоторый рисунок opengl поверх изображений. - person oberthelot; 30.05.2011
comment
Я отредактировал свой основной вопрос, чтобы дать больше окружающего кода, как вы просили :) - person oberthelot; 30.05.2011
comment
Я также заметил, что мой код, кажется, работает с textView... измерения в порядке, но не с ImageView!?!? - person oberthelot; 30.05.2011
comment
Чтобы уточнить, если вы хотите измерить текст, который переносится, вы не должны использовать MeasureSpec.UNSPECIFIED для обоих измерений. По сути, это дало бы вам бесконечную горизонтальную прокрутку и заставило бы текст разворачиваться. Вам нужно установить вертикальный размер на MeasureSpec.UNSPECIFIED, а горизонтальный размер установить на любую максимальную ширину, чтобы вызвать обтекание текстом и получить точное измерение высоты. Не забывайте учитывать отступы по мере необходимости. - person Bob Aman; 31.03.2013
comment
Первый работал для меня. Пытался измерить размеры текстового представления, созданного в коде с неизвестной длиной символа, поэтому я знал, где расположить его на экране относительно верхнего значения некоторых других представлений. Спасибо - person speedynomads; 11.06.2013
comment
@BobAman большое спасибо за ваш комментарий. Это полностью устранило вопросы и проблемы, которые у меня были с getMeasuredHeight/Width. - person Felipe Lima; 24.06.2014
comment
Мне это тоже очень помогло, но не все. Если вы столкнулись с этим и последовали совету Адампа, но он по-прежнему не работает, взгляните на мое решение и пояснения: stackoverflow.com/a /36184454/854396 - person Hermann Klecker; 23.03.2016
comment
@BobAman Большое спасибо за комментарий. Пожалуйста, опубликуйте это как отдельный ответ здесь. Потому что это единственное решение - person Oleksandr Albul; 15.05.2021

Сделай это:

frame.measure(0, 0);

final int w = frame.getMeasuredWidth();
final int h = frame.getMeasuredHeight();

Решено!

person Derzu    schedule 28.06.2012
comment
@ Саймон, какое у тебя устройство? - person CoolMind; 14.02.2017
comment
Для меня эта загадочная мера, которая возвращала разные значения при первом и более поздних вызовах, у меня была, указав MeasureSpec.UNSPECIFIED (каламбур не предназначен), спасибо! - person Pavlus; 04.07.2017
comment
@Pavlus это то же самое, что и вызов с MeasureSpec.UNSPECIFIED, потому что это константа, значение которой равно 0 - person B-GangsteR; 01.09.2018

В порядке ! Отчасти отвечая на мой собственный вопрос здесь... Но не полностью

1. Кажется, что на некоторых устройствах измерение ImageView не дает точных значений. Я видел много отчетов об этом, например, на устройствах Nexus и Galaxy.

2 - обходной путь, который я придумал:

Установите ширину и высоту вашего ImageView на «wrap_content» внутри кода xml.

Раздуйте макет внутри вашего кода (как правило, я полагаю, при инициализации пользовательского интерфейса).

LayoutInflater inflater     = (LayoutInflater) 
_parent.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
ViewGroup root      = (ViewGroup) inflater.inflate(R.layout.tl_frame, null);
ImageView frame = (ImageView) root.findViewById(R.id.TlFrame);

Рассчитайте собственное соотношение для просмотра изображения на основе типичного расчета Android.

//ScreenDpi can be acquired by getWindowManager().getDefaultDisplay().getMetrics(metrics);
 pixelWidth = wantedDipSize * (ScreenDpi / 160)

Используйте рассчитанный размер для динамической установки вашего ImageView внутри вашего кода.

frame.getLayoutParams().width = pixeWidth;

И вуаля! ваш ImageView теперь имеет желаемый размер Dip;)

person oberthelot    schedule 30.05.2011

view.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
 @SuppressLint("NewApi")
 @SuppressWarnings("deprecation")
 @Override
  public void onGlobalLayout() {
   //now we can retrieve the width and height
   int width = view.getWidth();
   int height = view.getHeight();


   //this is an important step not to keep receiving callbacks:
   //we should remove this listener
   //I use the function to remove it based on the api level!

    if(android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN){
        view.getViewTreeObserver().removeOnGlobalLayoutListener(this);
    }else{
        view.getViewTreeObserver().removeGlobalOnLayoutListener(this);
    }
  }
 });

Следует использовать Как получить ширину/высоту представление

person Amit Yadav    schedule 01.02.2015

Вы должны создать Custom Textview и использовать его в своих макетах, а также использовать функцию высоты getActual для установки высоты во время выполнения.

public class TextViewHeightPlus extends TextView {
    private static final String TAG = "TextViewHeightPlus";
    private int actualHeight=0;


    public int getActualHeight() {
        return actualHeight;
    }

    public TextViewHeightPlus(Context context) {
        super(context);
    }

    public TextViewHeightPlus(Context context, AttributeSet attrs) {
        super(context, attrs);
        setCustomFont(context, attrs);
    }

    public TextViewHeightPlus(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);

    }

   @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        actualHeight=0;

        actualHeight=(int) ((getLineCount()-1)*getTextSize());

    }

}
person Pramod    schedule 24.10.2013

К сожалению, в методах жизненного цикла Activity, таких как Activity#onCreate(Bundle), проход макета имеет еще не выполнено, поэтому вы еще не можете получить размер представлений в вашей иерархии представлений. Однако вы можете явно попросить Android измерить представление, используя View#measure(int, int).

Как указывает ответ @adamp, вы должны предоставить View#measure(int, int) с MeasureSpec, но определить правильный MeasureSpec.

Следующий метод пытается определить правильное MeasureSpec значения и измерения, переданные в view:

public class ViewUtil {

    public static void measure(@NonNull final View view) {
        final ViewGroup.LayoutParams layoutParams = view.getLayoutParams();

        final int horizontalMode;
        final int horizontalSize;
        switch (layoutParams.width) {
            case ViewGroup.LayoutParams.MATCH_PARENT:
                horizontalMode = View.MeasureSpec.EXACTLY;
                if (view.getParent() instanceof LinearLayout
                        && ((LinearLayout) view.getParent()).getOrientation() == LinearLayout.VERTICAL) {
                    ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) view.getLayoutParams();
                    horizontalSize = ((View) view.getParent()).getMeasuredWidth() - lp.leftMargin - lp.rightMargin;
                } else {
                    horizontalSize = ((View) view.getParent()).getMeasuredWidth();
                }
                break;
            case ViewGroup.LayoutParams.WRAP_CONTENT:
                horizontalMode = View.MeasureSpec.UNSPECIFIED;
                horizontalSize = 0;
                break;
            default:
                horizontalMode = View.MeasureSpec.EXACTLY;
                horizontalSize = layoutParams.width;
                break;
        }
        final int horizontalMeasureSpec = View.MeasureSpec
                .makeMeasureSpec(horizontalSize, horizontalMode);

        final int verticalMode;
        final int verticalSize;
        switch (layoutParams.height) {
            case ViewGroup.LayoutParams.MATCH_PARENT:
                verticalMode = View.MeasureSpec.EXACTLY;
                if (view.getParent() instanceof LinearLayout
                        && ((LinearLayout) view.getParent()).getOrientation() == LinearLayout.HORIZONTAL) {
                    ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) view.getLayoutParams();
                    verticalSize = ((View) view.getParent()).getMeasuredHeight() - lp.topMargin - lp.bottomMargin;
                } else {
                    verticalSize = ((View) view.getParent()).getMeasuredHeight();
                }
                break;
            case ViewGroup.LayoutParams.WRAP_CONTENT:
                verticalMode = View.MeasureSpec.UNSPECIFIED;
                verticalSize = 0;
                break;
            default:
                verticalMode = View.MeasureSpec.EXACTLY;
                verticalSize = layoutParams.height;
                break;
        }
        final int verticalMeasureSpec = View.MeasureSpec.makeMeasureSpec(verticalSize, verticalMode);

        view.measure(horizontalMeasureSpec, verticalMeasureSpec);
    }

}

Затем вы можете просто позвонить:

ViewUtil.measure(view);
int height = view.getMeasuredHeight();
int width = view.getMeasuredWidth();

В качестве альтернативы, как @Amit Yadav предложил, вы можете использовать OnGlobalLayoutListener для вызова прослушивателя после выполнения прохода макета. Ниже приведен метод, который обрабатывает отмену регистрации прослушивателя и изменения именования методов в разных версиях:

public class ViewUtil {

    public static void captureGlobalLayout(@NonNull final View view,
                                           @NonNull final ViewTreeObserver.OnGlobalLayoutListener listener) {
        view.getViewTreeObserver()
                .addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
                    @Override
                    public void onGlobalLayout() {
                        final ViewTreeObserver viewTreeObserver = view.getViewTreeObserver();
                        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
                            viewTreeObserver.removeOnGlobalLayoutListener(this);
                        } else {
                            //noinspection deprecation
                            viewTreeObserver.removeGlobalOnLayoutListener(this);
                        }
                        listener.onGlobalLayout();
                    }
                });
    }

}

Тогда ты можешь:

ViewUtil.captureGlobalLayout(rootView, new ViewTreeObserver.OnGlobalLayoutListener() {
    @Override
    public void onGlobalLayout() {
        int width = view.getMeasureWidth();
        int height = view.getMeasuredHeight();
    }
});

Где rootView может быть корневым представлением вашей иерархии представлений, а view может быть любым представлением в вашей иерархии, измерения которого вы хотите знать.

person Travis    schedule 13.10.2016
comment
Спасибо за метод measure. При создании представления с помощью new TextView() оно не содержит LayoutParams, но имеет ширину. Таким образом, вы получите исключение с нулевым указателем на switch (layoutParams.width). - person CoolMind; 14.02.2017
comment
Извините, это неправильный метод, после него view.getMeasuredWidth() дает 0 во многих случаях. - person CoolMind; 14.02.2017
comment
Используйте метод с getViewTreeObserver для получения размера. - person CoolMind; 15.02.2017
comment
@CoolMind, метод measure, который я предоставил, учитывает многие распространенные макеты, но, к сожалению, не все. Итак, как вы упомянули, использование ViewTreeObserver является более надежным решением. Метод measure был предоставлен, поскольку иногда использование ViewTreeObserver может привести к путанице. - person Travis; 16.02.2017
comment
да, вы правы, возможно, ViewTreeObserver не является 100% надежным методом. Лучше проверить isAlive(), см. stackoverflow.com/questions/12867760/ (но я мог сделать ошибку). - person CoolMind; 16.02.2017

Вероятно, из-за того, что у вас в AndroidManifest.xml (ссылка) и из какого каталога drawable-XXX исходит файл xml, Android загружает ресурсы с операцией масштабирования. Вы решили использовать виртуальную единицу измерения "dip" (link). реальное значение (px) может быть разным.

person Damian Kołakowski    schedule 27.05.2011
comment
Манифест указывает только активность, намерение и версию SDK. Ничего, связанного с размером экрана или поддержкой. Кроме того, все мои чертежи находятся в основной папке res/drawable... без префикса/суффикса/подпапки mdpi или hdpi... - person oberthelot; 28.05.2011
comment
а где вы запускаете приложение на emu, на каком устройстве? - person Damian Kołakowski; 28.05.2011
comment
как насчет типа шкалы для ImageView? - person Damian Kołakowski; 28.05.2011
comment
ScaleType был установлен в centerInside... Я попытался удалить его, и результат тот же... - person oberthelot; 30.05.2011
comment
Ничего, связанного с размером экрана или поддержкой. это неправда - person Damian Kołakowski; 31.05.2011
comment
Я имел в виду в своем манифесте... Там нет ничего, связанного с размером экрана или поддержкой.... Не о манифестах вообще! - person oberthelot; 31.05.2011