Android getMeasuredHeight returns wrong values !

后端 未结 7 677
野的像风
野的像风 2020-12-02 20:13

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

相关标签:
7条回答
  • 2020-12-02 20:35

    Do that:

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

    Solved!

    0 讨论(0)
  • 2020-12-02 20:37

    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.

    0 讨论(0)
  • 2020-12-02 20:42

    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));
    
    0 讨论(0)
  • 2020-12-02 20:48

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

    0 讨论(0)
  • 2020-12-02 20:51
    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

    0 讨论(0)
  • 2020-12-02 20:51

    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.

    0 讨论(0)
提交回复
热议问题