• android: View的getWidth() 和 getMeasureWidth()方法的区别


    View的高宽是由View本身和Parent容器共同决定的。
     getMeasuredWidth() 和 getWidth() 分别对应于视图绘制的 measure 和 layout 阶段。getMeasuredWidth()获取的是View原始的大小,也就是这个View在XML文件中配置或者是代码中设置的大小。getWidth()获取的是这个View最终显示的大小,这个大小有可能等于原始的大小,也有可能不相等。比如说,在父布局的onLayout()方法或者该View的onDraw()方法里调用measure(0, 0),二者的结果可能会不同(measure中的参数可以自己定义)。

    getWidth()

    /**
         * Return the width of the your view.
         * @return The width of your view, in pixels.
         */
        @ViewDebug.ExportedProperty(category = "layout")
        public final int getWidth() {
            return mRight - mLeft;
        }

    从源码上看, getWidth() 是根据 mRight 和 mLeft 之间的差值计算出来的,需要在布局之后才能确定它们的坐标,也就是说布局后在onLayout()方法里才能调用getWidth()来获取。因此,getWidth()获取的宽度是在View设定好布局后整个View的宽度。

    getMeasuredWidth()

    /**
         * Like {@link #getMeasuredWidthAndState()}, but only returns the
         * raw width component (that is the result is masked by
         * {@link #MEASURED_SIZE_MASK}).
         *
         * @return The raw measured width of this view.
         */
        public final int getMeasuredWidth() {
            return mMeasuredWidth & MEASURED_SIZE_MASK;
        }

    从源码上看, getMeasuredWidth() 获取的是 mMeasuredWidth 的这个值。这个值是一个8位的十六进制的数字,高两位表示的是这个measure阶段的Mode的值,具体可以查看MeasureSpec的原理。这里 mMeasuredWidth & MEASURED_SIZE_MASK 表示的是测量阶段结束之后,View真实的值。而且这个值会在调用了 setMeasuredDimensionRaw() 函数之后会被设置。所以 getMeasuredWidth() 的值是measure阶段结束之后得到的View的原始的值。

        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                    getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
        }
    
        protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
            boolean optical = isLayoutModeOptical(this);
            if (optical != isLayoutModeOptical(mParent)) {
                Insets insets = getOpticalInsets();
                int opticalWidth  = insets.left + insets.right;
                int opticalHeight = insets.top  + insets.bottom;
    
                measuredWidth  += optical ? opticalWidth  : -opticalWidth;
                measuredHeight += optical ? opticalHeight : -opticalHeight;
            }
            setMeasuredDimensionRaw(measuredWidth, measuredHeight);
        }
    
        private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
            mMeasuredWidth = measuredWidth;
            mMeasuredHeight = measuredHeight;
    
            mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
        }

    总结一下, getMeasuredWidth 是measure阶段获得的View的原始宽度, getWidth 是layout阶段完成后,其在父容器中所占的最终宽度。

    什么时候不同呢?

    首先自定义一个父布局: CustomView

    package com.yongdaimi.android.androidapitest.view;
    
    import android.content.Context;
    import android.util.AttributeSet;
    import android.util.Log;
    import android.view.View;
    import android.widget.LinearLayout;
    
    import androidx.annotation.Nullable;
    
    public class CustomView extends LinearLayout {
    
        public static final String TAG = "xp";
    
        private View mFirstView;
    
        private int mFirstViewWidth;
        private int mFirstViewHeight;
    
        private int mFirstViewMeasureWidth;
        private int mFirstViewMeasureHeight;
    
        public CustomView(Context context) {
            this(context, null);
        }
    
        public CustomView(Context context, @Nullable AttributeSet attrs) {
            this(context, attrs, 0);
        }
    
        public CustomView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            init();
        }
    
        private void init() {
            setOrientation(LinearLayout.HORIZONTAL);
        }
    
    
        @Override
        protected void onFinishInflate() {
            super.onFinishInflate();
            mFirstView = getChildAt(0);
        }
    
        @Override
        protected void onSizeChanged(int w, int h, int oldw, int oldh) {
            super.onSizeChanged(w, h, oldw, oldh);
    
            mFirstViewWidth = mFirstView.getWidth();
            mFirstViewHeight = mFirstView.getHeight();
    
            mFirstViewMeasureWidth = mFirstView.getMeasuredWidth();
            mFirstViewMeasureHeight = mFirstView.getMeasuredHeight();
    
    
            Log.i(TAG, "onSizeChanged: mFirstViewWidth: " + mFirstViewWidth + ", mFirstViewHeight: " + mFirstViewHeight
                    + ", mFirstViewMeasureWidth: " + mFirstViewMeasureWidth + ", mFirstViewMeasureHeight: " + mFirstViewMeasureHeight);
        }
    
        @Override
        protected void onLayout(boolean changed, int l, int t, int r, int b) {
            super.onLayout(changed, l, t, r, b);
            mFirstView.layout(-100, 0, -50, 50);
    
            mFirstViewWidth = mFirstView.getWidth();
            mFirstViewHeight = mFirstView.getHeight();
    
            mFirstViewMeasureWidth = mFirstView.getMeasuredWidth();
            mFirstViewMeasureHeight = mFirstView.getMeasuredHeight();
    
            Log.i(TAG, "onLayout: mFirstViewWidth: " + mFirstViewWidth + ", mFirstViewHeight: " + mFirstViewHeight
                    + ", mFirstViewMeasureWidth: " + mFirstViewMeasureWidth + ", mFirstViewMeasureHeight: " + mFirstViewMeasureHeight);
        }
    
    }

    然后在activity的布局文件使用这个布局并添加一个新的控件:

    <?xml version="1.0" encoding="utf-8"?>
    <com.yongdaimi.android.androidapitest.view.CustomView xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    
        <TextView
            android:id="@+id/tv_first"
            android:layout_width="60dip"
            android:layout_height="60dip"
            android:background="@android:color/holo_green_light"
            />
    
    </com.yongdaimi.android.androidapitest.view.CustomView>

    运行:

    2020-09-04 10:56:51.202 30999-30999/com.yongdaimi.android.androidapitest I/xp: onSizeChanged: mFirstViewWidth: 0, mFirstViewHeight: 0, mFirstViewMeasureWidth: 180, mFirstViewMeasureHeight: 180
    2020-09-04 10:56:51.204 30999-30999/com.yongdaimi.android.androidapitest I/xp: onLayout: mFirstViewWidth: 50, mFirstViewHeight: 50, mFirstViewMeasureWidth: 180, mFirstViewMeasureHeight: 180

    可以看到,只要在代码里重新修改了子控件的摆放位置,getWidth和getMeasureWidth的值就会不同。

    如何在onCreate中拿到View的宽度和高度?

    在onCreate()中获取View的高宽有三种方法:

    1. View.post(runnable)

    view.post(new Runnable() {
                @Override
                public void run() {
                    int width = view.getWidth();
                    int measuredWidth = view.getMeasuredWidth();
                    Log.i(TAG, " " + width);
                    Log.i(TAG, "measuredWidth: " + measuredWidth);
                }
            });

    利用Handler通信机制,发送一个Runnable到MessageQueue中,当View布局处理完成时,自动发送消息,通知UI进程。借此机制,巧妙获取View的高宽属性,代码简洁,相比ViewTreeObserver监听处理,还不需要手动移除观察者监听事件。

    2. ViewTreeObserver.addOnGlobalLayoutListener()

    监听View的 onLayout() 绘制过程,一旦layout触发变化,立即回调 onLayoutChange 方法。
    注意,使用完也要主要调用 removeOnGlobalListener() 方法移除监听事件。避免后续每一次发生全局View变化均触发该事件,影响性能。

    ViewTreeObserver vto = view.getViewTreeObserver();
            vto.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
                @Override
                public void onGlobalLayout() {
                    view.getViewTreeObserver().removeGlobalOnLayoutListener(this);
                    Log.i(TAG, " " + view.getWidth());
                    Log.i(TAG, "height: " + view.getHeight());
                }
            });

    3. View.measure(int widthMeasureSpec, int heightMeasureSpec)

    除了在onCreate()中获得View的高宽,还可以在Activity的onWindowFocusChanged() 方法中获得高宽。

  • 相关阅读:
    Mysql之锁、事务绝版详解干货!
    SSL基础知识(比较清楚)
    数字证书使用流程指南
    HTTPS与SSL
    IIS 6.0 Web园(Web Garden)
    验证数字可以有千分符可以有0个1个小数点小数点后输入后四舍五入保留两位(写个js验证真费劲)
    线程安全
    利用SSL给IIS加把锁(转)
    弹出div后其它不能操作(小技巧)
    关于反射
  • 原文地址:https://www.cnblogs.com/yongdaimi/p/13612622.html
Copyright © 2020-2023  润新知