Android getMeasuredHeight returns wrong values !
Asked Answered
A

7

44

I'm trying to determine the real dimension in pixels of some UI elements !

Those elements are inflated from a .xml file and are initialized with dip width and height so that the GUI will eventually support multiple screen size and dpi (as recommended by android specs).

<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>

This previous xml represent one frame. But I do add many dynamically inside a horizontal layout describe here :

<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 > 

The method defined to add one frame inside my java code :

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

Then inside my code, I try to get the actual real size in pixel because I need this to draw some opengl stuff over it.

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();

Everything seems to work fine except that those values are way bigger than the actual pixel size of the ImageView.

Reported infos from getWindowManager().getDefaultDisplay().getMetrics(metrics); are the following : density = 1,5 densityDpi = 240 widthPixel = 600 heightPixel = 1024

Now, I know the rule from android is : pixel = dip * (dpi /160). But nothing makes any sense with the value returned. For that ImageView of (90dip X 110dip), the returned values of the measure() method is (270 x 218) which I assumed is in pixel !

Anyone has any idea why ? Is the value returned in pixel ?

By the way : I've been testing the same code but with a TextView instead than an ImageView and everything seems to be working fine ! Why !?!?

Alake answered 27/5, 2011 at 21:2 Comment(0)
P
60

You're calling measure incorrectly.

measure takes MeasureSpec values which are specially packed by MeasureSpec.makeMeasureSpec. measure ignores LayoutParams. The parent doing the measuring is expected to create a MeasureSpec based on its own measurement and layout strategy and the child's LayoutParams.

If you want to measure the way that WRAP_CONTENT usually works in most layouts, call measure like this:

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

If you don't have max values (for example if you're writing something like a ScrollView that has infinite space) you can use the UNSPECIFIED mode:

frame.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
        MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
Panjabi answered 27/5, 2011 at 21:21 Comment(14)
Doesn't seems to be working either....I've tried every combinaison of max values and MesureSpec and the result are either the same as before or the "fake" maxValues I've used for test or zero.Alake
I don't know if it might be related to that...But the LinearLayout above is added dynamically (inside java code)...frame by frame inside a HorizontalScrollView, inside a LinearLayout....The goal is to create a dynamic timeline GUI and draw inside with openGl !Alake
Is there a way to dynamically set the ImageView width and height ??? I'm thinking about doing the calculation myself depending on screen size and dpi...Alake
What values are being reported, and what does the result look like? You're trying to measure the ImageView, but it's going to be remeasured (possibly with different MeasureSpecs) by its parent during layout. If you'd like the layout process to run its course and examine afterwards, you could post a Runnable to execute after the standard measure/layout pass is complete.Panjabi
I've aleady tried that...The Layout process setup is long finish when I try to get those measures (timeline appears on user action only, not on initialization), posting a delayed runnable with 5000 milsec didn't change a thing. As for the result, it "looks" ok, but I have to setup a glScissor for my rendering and it's clear that the size doesn't fit. I've calculated it and it should be 165x135 pixels and android measurement returns (270x218 pixels) the actual image size is 180x145 and the xml setup is 110x90 dip.Alake
Can you post some more of the surrounding code? It sounds like there's at least one piece of the puzzle missing here. (You were posting the runnable after adding the view subtree to the active hierarchy, right?)Panjabi
Yes I was...All the layout is initialized from xml at first (in the onCreate of the activity)...The HorizontalScrollView containing the imageViews is set to hidden...it appears on user input and it's only on that input that I try to get the measurement in order to do some opengl drawing over the imageviews.Alake
I've edited my main question to give more surrounding code as you requested :)Alake
I've also notice that my code seems to be working with the textView...the measurements are fine but not with the ImageView !?!?Alake
To clarify, if you want to measure text that's wrapping, you shouldn't use MeasureSpec.UNSPECIFIED for both dimensions. That would essentially give you infinite horizontal scrolling, and cause text to go unwrapped. You need to make the vertical dimension set to MeasureSpec.UNSPECIFIED and the horizontal dimension set to whatever the maximum width is in order to trigger text wrapping and get an accurate height measurement. Remember to account for padding as necessary.Adcock
The first one worked for me. Was trying to measure the dimensions of a text view created in code with an unknown character length, so i knew where to position it on screen relative to the top value of some other views. ThanksNecolenecro
@BobAman thanks a lot for your comment. That completely cleared out the questions and problems I had with getMeasuredHeight/Width.Rockandroll
That was very helpful for me too, but it was not everything. If you run into this and followed adamp's advice and it still does not work then have a look at my solution and explanations: https://mcmap.net/q/82161/-android-get-height-of-a-view-before-it-180-s-drawnPentathlon
@BobAman Thanks a lot for the comment. Please post it as a separate answer here. Because it is the only solutionBahena
S
19

Do that:

frame.measure(0, 0);

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

Solved!

Sensitize answered 28/6, 2012 at 22:46 Comment(3)
@Simon, what is your device?Howbeit
For me, this solved mysterious measure that returned different values at first and later calls I had by specifying MeasureSpec.UNSPECIFIED (pun not intended), thanks!Delindadelineate
@Delindadelineate it is the same as calling with MeasureSpec.UNSPECIFIED, because it's a constant which value is 0Theron
A
5

Ok ! Kind of Answering my own question here...But not completly

1 - It seems that on some devices, The ImageView measuring do not provide with exact values. I've seen lots of reports on this happenning on Nexus and Galaxy devices for example.

2 - A work around that I've come up with :

Set the width and height of your ImageView to "wrap_content" inside xml code.

Inflate the layout inside your code (generally in the UI initialization I suppose).

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);

Calculate your own ratio for your image view, based on the typical Android calculation

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

Use the calculated size to set your ImageView dynamycally inside your code

frame.getLayoutParams().width = pixeWidth;

And voila ! your ImageView has now the wanted Dip size ;)

Alake answered 30/5, 2011 at 15:42 Comment(0)
P
4
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);
    }
  }
 });

One should go with How to get width/height of a View

Piperonal answered 1/2, 2015 at 14:2 Comment(0)
J
3

Unfortunately, in Activity lifecycle methods such as Activity#onCreate(Bundle), a layout pass has not yet been performed, so you can't yet retrieve the size of views in your view hierarchy. However, you can explicitly ask Android to measure a view using View#measure(int, int).

As @adamp's answer points out, you have to provide View#measure(int, int) with MeasureSpec values, but it can be a bit daunting figuring out the correct MeasureSpec.

The following method tries to determine the correct MeasureSpec values and measures the passed in 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);
    }

}

Then you can simply call:

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

Alternatively, as @Amit Yadav suggested, you can use OnGlobalLayoutListener to have a listener called after the layout pass has been performed. The following is a method that handles unregistering the listener and method naming changes across versions:

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();
                    }
                });
    }

}

Then you can:

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

Where rootView can be the root view of your view hierarchy and view can be any view within your hierarchy that you want to know the dimensions of.

Jampan answered 13/10, 2016 at 5:52 Comment(5)
Thanks for a measure method. When creating a view with new TextView() it doesn't contain LayoutParams but has a width. So you will get a null-pointer exception on switch (layoutParams.width).Howbeit
Sorry, it is a wrong method, after it view.getMeasuredWidth() gives 0 in many sutuations.Howbeit
Use a method with getViewTreeObserver to obtain a size.Howbeit
@CoolMind, the measure method I provided accounts for many common layouts but unfortunately not all. So, as you mentioned, using the ViewTreeObserver is a more reliable solution. The measure method was provided since there are times that relying on the ViewTreeObserver can get a bit messy.Jampan
yes, you are right, probably ViewTreeObserver is not 100% reliable method. It is better to check isAlive(), see #12868260 (but I could make a bug).Howbeit
R
2

You have to create Custom Textview and use it in your layouts and use getActual height function to set the height at runtime

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());

    }

}
Radiosonde answered 24/10, 2013 at 11:42 Comment(0)
L
1

Probably, because of what you have in AndroidManifest.xml (link) file and from which drawable-XXX directory the xml file comes, Android loads resources with scaling operation. You decide to use "dip" (link) dimension unit which is virtual and the real value (px) can be different.

Lapham answered 27/5, 2011 at 21:5 Comment(6)
Manifest only specifies activity, intent and sdk version. Nothing related to screen size or support. Plus all my drawables are in the basic res/drawable folder...no mdpi or hdpi prefix/suffix/subfolder...Alake
and where you run the application on emu, on which device?Castiglione
what about scale type for the ImageView ?Castiglione
ScaleType was set to "centerInside"...I've tried removing it and the result is the same...Alake
"Nothing related to screen size or support." that is not trueCastiglione
I meant in "my" manifest...There's nothing related to screen size or support....Not about manifests in general !Alake

© 2022 - 2024 — McMap. All rights reserved.