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

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

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

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)有了,自帶了拆分和打包方法。

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大小。
