Android View的測量模式

onMeasure講解

View繪制出來需要知道自己的寬高是多少,所以要先進(jìn)行測量尺寸。
從門縫里面看世界,那就從View的內(nèi)部類MeasureSpec測量類去學(xué):

public static class MeasureSpec {
        private static final int MODE_SHIFT = 30;
        private static final int MODE_MASK  = 0x3 << MODE_SHIFT;

      /** @hide */
        @IntDef({UNSPECIFIED, EXACTLY, AT_MOST})
        @Retention(RetentionPolicy.SOURCE)
        public @interface MeasureSpecMode {}

        /**
         * Measure specification mode: The parent has not imposed any constraint
         * on the child. It can be whatever size it wants.
         */
        public static final int UNSPECIFIED = 0 << MODE_SHIFT;

        /**
         * Measure specification mode: The parent has determined an exact size
         * for the child. The child is going to be given those bounds regardless
         * of how big it wants to be.
         */
        public static final int EXACTLY     = 1 << MODE_SHIFT;

        /**
         * Measure specification mode: The child can be as large as it wants up
         * to the specified size.
         */
        public static final int AT_MOST     = 2 << MODE_SHIFT;

    
        public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
                                          @MeasureSpecMode int mode) {
            if (sUseBrokenMakeMeasureSpec) {
                return size + mode;
            } else {
                return (size & ~MODE_MASK) | (mode & MODE_MASK);
            }
        }

        public static int makeSafeMeasureSpec(int size, int mode) {
            if (sUseZeroUnspecifiedMeasureSpec && mode == UNSPECIFIED) {
                return 0;
            }
            return makeMeasureSpec(size, mode);
        }

        @MeasureSpecMode
        public static int getMode(int measureSpec) {
            //noinspection ResourceType
            return (measureSpec & MODE_MASK);
        }

        public static int getSize(int measureSpec) {
            return (measureSpec & ~MODE_MASK);
        }
    }

測量模式

UNSPECIFIED
EXACTLY
AT_MOST

為了認(rèn)準(zhǔn)測量模式的對應(yīng)方式,我寫了一個(gè)簡單測試類:

public class CustomView extends View{

    public CustomView(Context context) {
        super(context);
    }

    public CustomView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public CustomView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);

        switch (widthMode){
            case MeasureSpec.UNSPECIFIED:
                Log.e("TAG","widthMode " + "UNSPECIFIED");
                break;
            case MeasureSpec.AT_MOST:
                Log.e("TAG","widthMode " + "AT_MOST");
                break;
            case MeasureSpec.EXACTLY:
                Log.e("TAG","widthMode " + "EXACTLY");
                break;
        }
        Log.e("TAG","widthSize " + MeasureSpec.getSize(widthMeasureSpec));

        switch (heightMode){
            case MeasureSpec.UNSPECIFIED:
                Log.e("TAG","heightMode " + "UNSPECIFIED");
                break;
            case MeasureSpec.AT_MOST:
                Log.e("TAG","heightMode " + "AT_MOST");
                break;
            case MeasureSpec.EXACTLY:
                Log.e("TAG","heightMode " + "EXACTLY");
                break;
        }
        Log.e("TAG","heightSize " + MeasureSpec.getSize(heightMeasureSpec));
    }

}
測試結(jié)果:

布局中寬高均為match_parent: 測量模式為EXACTLY

image.png

布局中寬高均為wrap_content: 測量模式為AT_MOST

image.png

布局中寬高均為200dp(固定數(shù)值): 測量模式為EXACTLY

image.png

  • UNSPECIFIED 表示子布局想要多大就多大,父view不限制子view大小。一般出現(xiàn)在AadapterView的item的heightMode中、ScrollView的childView的heightMode中;此種模式比較少見。

  • EXACTLY 父容器測量的值是多少,那么這個(gè)view的大小就是這個(gè)specSize,毫不討價(jià)還價(jià)

  • AT_MOST 父容器給定一個(gè)子view的最大尺寸,大小在這個(gè)值范圍以內(nèi),具體是多少看子view的表現(xiàn)

測量完成:

測量完成回調(diào)onMeasure(int widthMeasureSpec, int heightMeasureSpec)方法。
那么這兩個(gè)名字長長的變量是什么呢?就是測量出的寬和高的信息。

回到MeasureSpec類分析,一個(gè)Int有32位,用前2位表示SpecMode ,2位數(shù)有四種表示方法了,00,01,11分別表示上面的模式順序。后30位表示SpecSize。那我們是不是獲取測量模式和尺寸都要自己使用位移計(jì)算呢?不用的,MeasureSpec類已經(jīng)有了,自帶了拆分和打包方法。

image.png
public static int makeMeasureSpec(int size, int mode) {
            if (sUseBrokenMakeMeasureSpec) {
                return size + mode;
            } else {
                return (size & ~MODE_MASK) | (mode & MODE_MASK);
            }
        }

public static int getMode(int measureSpec) {
            return (measureSpec & MODE_MASK);
        }

public static int getSize(int measureSpec) {
            return (measureSpec & ~MODE_MASK);
        }

獲取測量模式和測量大?。?/p>

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
}

現(xiàn)在,我們要寫一個(gè)正方形ImageView,使用setMeasuredDimension()自己重設(shè)測量值,讓高度值也等于寬度值:

public class SquareImageView extends AppCompatImageView{

    public SquareImageView(Context context) {
        super(context);
    }

    public SquareImageView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(widthMeasureSpec,widthMeasureSpec);
    }
}
getChildMeasureSpec()代碼里的生成規(guī)則:

1.當(dāng)子View的寬高設(shè)置的是具體數(shù)值時(shí),顯然我們可以直接拿到子View的寬高,則子View寬高就確定了,不用再去考慮父容器的SpecMode了,此時(shí)子View的SpecMode為EXACTLY,SpecSize就是設(shè)置的寬高。

2.當(dāng)子View的寬高設(shè)置的是match_parent, 則不管父容器的SpecMode是什么模式,子View的SpecSize就等于父容器的寬高,而子View的SpecMode隨父容器的SpecMode。

3.當(dāng)子View的寬高設(shè)置的是wrap_content,因?yàn)檫@種情況父容器實(shí)在不知道子View應(yīng)該多寬多高,所以子View的SpecSize給的是父容器的寬高,也就是說只是給子View限制了一個(gè)最大寬高,而子View的SpecMode是AT_MOST模式。

如果你設(shè)置的是wrap_content,則默認(rèn)顯示出來是其父容器的大小,如果你想要它正常的顯示為wrap_content,則你就要自己重寫onMeasure()來自己計(jì)算它的寬高度并設(shè)置。

示例:

public class MyView extends View {
    public MyView(Context context) {
        super(context);
    }

    public MyView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public MyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }
}

父view的大小是200dp,子view的寬高是wrap_content,父view的背景是紫色,子view背景是黑色。

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <RelativeLayout
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:background="@color/purple_200">

        <com.example.performanceopt.MyView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="@color/black"/>
    </RelativeLayout>
</RelativeLayout>

結(jié)果是子view的顯示出來是其父容器的200dp大小。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容