Android getMeasuredHeight returns wrong values !

46,408

Solution 1

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

Solution 2

Do that:

frame.measure(0, 0);

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

Solved!

Solution 3

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

Solution 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

Solution 5

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.

Share:
46,408

Related videos on Youtube

oberthelot
Author by

oberthelot

Software developper and engineer, mostly C++, Java. Mobile software development Android, Apple. Fairly new to this community (stackoverflow)...and looooving it ! This system is just great :)

Updated on October 13, 2020

Comments

  • oberthelot
    oberthelot over 3 years

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

  • oberthelot
    oberthelot almost 13 years
    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...
  • Damian Kołakowski
    Damian Kołakowski almost 13 years
    and where you run the application on emu, on which device?
  • Damian Kołakowski
    Damian Kołakowski almost 13 years
    what about scale type for the ImageView ?
  • oberthelot
    oberthelot almost 13 years
    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.
  • oberthelot
    oberthelot almost 13 years
    ScaleType was set to "centerInside"...I've tried removing it and the result is the same...
  • oberthelot
    oberthelot almost 13 years
    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 !
  • oberthelot
    oberthelot almost 13 years
    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...
  • adamp
    adamp almost 13 years
    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.
  • oberthelot
    oberthelot almost 13 years
    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.
  • adamp
    adamp almost 13 years
    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?)
  • oberthelot
    oberthelot almost 13 years
    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.
  • oberthelot
    oberthelot almost 13 years
    I've edited my main question to give more surrounding code as you requested :)
  • oberthelot
    oberthelot almost 13 years
    I've also notice that my code seems to be working with the textView...the measurements are fine but not with the ImageView !?!?
  • Damian Kołakowski
    Damian Kołakowski almost 13 years
    "Nothing related to screen size or support." that is not true
  • oberthelot
    oberthelot almost 13 years
    I meant in "my" manifest...There's nothing related to screen size or support....Not about manifests in general !
  • Bob Aman
    Bob Aman about 11 years
    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.
  • speedynomads
    speedynomads almost 11 years
    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. Thanks
  • Felipe Lima
    Felipe Lima almost 10 years
    @BobAman thanks a lot for your comment. That completely cleared out the questions and problems I had with getMeasuredHeight/Width.
  • Hermann Klecker
    Hermann Klecker about 8 years
    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: stackoverflow.com/a/36184454/854396
  • CoolMind
    CoolMind over 7 years
    @Simon, what is your device?
  • CoolMind
    CoolMind over 7 years
    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).
  • CoolMind
    CoolMind over 7 years
    Sorry, it is a wrong method, after it view.getMeasuredWidth() gives 0 in many sutuations.
  • CoolMind
    CoolMind over 7 years
    Use a method with getViewTreeObserver to obtain a size.
  • Travis
    Travis about 7 years
    @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.
  • CoolMind
    CoolMind about 7 years
    yes, you are right, probably ViewTreeObserver is not 100% reliable method. It is better to check isAlive(), see stackoverflow.com/questions/12867760/… (but I could make a bug).
  • Pavlus
    Pavlus almost 7 years
    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!
  • B-GangsteR
    B-GangsteR over 5 years
    @Pavlus it is the same as calling with MeasureSpec.UNSPECIFIED, because it's a constant which value is 0
  • Oleksandr Albul
    Oleksandr Albul about 3 years
    @BobAman Thanks a lot for the comment. Please post it as a separate answer here. Because it is the only solution