Android getMeasuredHeight returns wrong values !
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.
Related videos on Youtube
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, 2020Comments
-
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 almost 13 yearsManifest 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 almost 13 yearsand where you run the application on emu, on which device?
-
Damian Kołakowski almost 13 yearswhat about scale type for the ImageView ?
-
oberthelot almost 13 yearsDoesn'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 almost 13 yearsScaleType was set to "centerInside"...I've tried removing it and the result is the same...
-
oberthelot almost 13 yearsI 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 almost 13 yearsIs 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 almost 13 yearsWhat 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 almost 13 yearsI'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 almost 13 yearsCan 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 almost 13 yearsYes 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 almost 13 yearsI've edited my main question to give more surrounding code as you requested :)
-
oberthelot almost 13 yearsI'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 almost 13 years"Nothing related to screen size or support." that is not true
-
oberthelot almost 13 yearsI meant in "my" manifest...There's nothing related to screen size or support....Not about manifests in general !
-
Bob Aman about 11 yearsTo 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 toMeasureSpec.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 almost 11 yearsThe 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 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 about 8 yearsThat 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 over 7 years@Simon, what is your device?
-
CoolMind over 7 yearsThanks for a
measure
method. When creating a view withnew TextView()
it doesn't contain LayoutParams but has a width. So you will get a null-pointer exception onswitch (layoutParams.width)
. -
CoolMind over 7 yearsSorry, it is a wrong method, after it view.getMeasuredWidth() gives 0 in many sutuations.
-
CoolMind over 7 yearsUse a method with getViewTreeObserver to obtain a size.
-
Travis about 7 years@CoolMind, the
measure
method I provided accounts for many common layouts but unfortunately not all. So, as you mentioned, using theViewTreeObserver
is a more reliable solution. Themeasure
method was provided since there are times that relying on theViewTreeObserver
can get a bit messy. -
CoolMind about 7 yearsyes, 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 almost 7 yearsFor 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 over 5 years@Pavlus it is the same as calling with
MeasureSpec.UNSPECIFIED
, because it's a constant which value is 0 -
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