Android getMeasuredHeight returns wrong values !

筅森魡賤 提交于 2019-11-27 12:19:43

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

Do that:

frame.measure(0, 0);

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

Solved!

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

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

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

    }

}

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.

Travis

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.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!