自定義view有三個(gè)重要的方法,onMeasure债沮,onLayout,onDraw本鸣。今天先從onMeasure開始疫衩。
View層次
首先,先從最簡單的看起永高。我們最常用到的設(shè)置布局就是
setContentView(R.layout.activity_main);```
在Activity中找到它的源碼:
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
public Window getWindow() {
return mWindow;
}```
所以可以看出調(diào)用了Window里的setContentView方法。而Window類是一個(gè)抽象類提针,其唯一實(shí)現(xiàn)類是PhoneWindow命爬,看看PhoneWindow類的setContentView方法。
@Override
public void setContentView(int layoutResID) {
if (mContentParent == null) {
installDecor();
} else {
mContentParent.removeAllViews();
}
mLayoutInflater.inflate(layoutResID, mContentParent);
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
}```
首先判斷mContentParent是否為空辐脖,mContentParent是一個(gè)ViewGroup對(duì)象饲宛,即判定是否第一次調(diào)setContentView方法,如果是就調(diào)用installDecor方法嗜价,如果不是就清除ViewGroup里的View艇抠。
private void installDecor() {
//只展示核心代碼
if (mDecor == null) {
mDecor = generateDecor();
mDecor.setIsRootNamespace(true);
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
}
}
protected DecorView generateDecor() {
return new DecorView(getContext(), -1);
}
private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {
public DecorView(Context context, int featureId) {
super(context);
mFeatureId = featureId;
}
}```
mDecor是DecorView的一個(gè)對(duì)象幕庐,DecorView是PhoneWindow里的一個(gè)內(nèi)部類。首先判斷mDecor是否為空家淤,如果為空就創(chuàng)建一個(gè)DecorView對(duì)象异剥,接下來判斷mContentParent是否為空,如果是絮重,調(diào)用generateLayout冤寿。
protected ViewGroup generateLayout(DecorView decor)
View in = mLayoutInflater.inflate(layoutResource, null);
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
return contentParent;
}
contentParent和mDecor創(chuàng)建完畢后,通過PhoneWindow里的setContentView的mLayoutInflater.inflate(layoutResID,mContentParent)把xml文件傳遞到屏幕青伤。
通過Hierarchy View得到的View層次結(jié)構(gòu):
整個(gè)屏幕是一個(gè)FrameLayout督怜,里面包含一個(gè)LinearLayout和兩個(gè)View,分別是statusBar和nagavitionBar狠角,狀態(tài)欄和導(dǎo)航欄号杠,LinearLayout里是一個(gè)FrameLayout,F(xiàn)rameLayout里又嵌套一個(gè)ViewGroup(普遍當(dāng)成是LinearLayout)丰歌,ViewGroup里有兩個(gè)FrameLayout姨蟋,一個(gè)是content,一個(gè)是title动遭。
至此view的層次介紹完畢芬探。接下來看一下自定義View的onMeasure方法。
onMeasure
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
重寫onMeasure方法厘惦,直接調(diào)用super.onMeasure()調(diào)用父類方法偷仿,或者直接調(diào)用setMeasuredDimension設(shè)置view的寬高。里面有兩個(gè)很重要的參數(shù)宵蕉,widthMeasureSpec和heightMeasureSpec酝静,這兩個(gè)參數(shù)。至于這兩個(gè)參數(shù)的來源羡玛,就要找到view的繪制方法别智。view的繪制是在ViewRootImpl的performTraversals方法實(shí)現(xiàn)的
private void performTraversals() {
// cache mView since it is used so much below...
final View host = mView;
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
case ViewGroup.LayoutParams.MATCH_PARENT:
// Window can't resize. Force root view to be windowSize.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
// Window can resize. Set max size for root view.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
// Window wants to be an exact size. Force root view to be that size.
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
getRootMeasureSpec傳入兩個(gè)參數(shù),mWidth和lp.width稼稿,lp.width和lp.height的值為MATCH_PARENT薄榛。然后把得到的measureSpec傳給performMeasure,performMeasure調(diào)用view的measure方法,measure方法又調(diào)用onMeasure方法让歼,這樣就完成了widthMeasureSpec和heightMeasureSpec從ViewRootImpl到onMeasure的傳遞敞恋。
在這里用到了一個(gè)特殊的類MeasureSpec。
public static int getMode(int measureSpec) {
//noinspection ResourceType
return (measureSpec & MODE_MASK);
}
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
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);
}
}
MeasureSpec中mode為高兩位谋右,size為低30位硬猫。MODE_MASK為11 000...(30個(gè)0),&的規(guī)則是遇0變0,遇1不變啸蜜。所以getMode就取得了高兩位坑雅,getSize對(duì)MODE_MASK取反,變成了00 11111...(30個(gè)1)取得低30位衬横。
在onMeasure方法中裹粤,調(diào)用如下代碼:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
ViewGroup沒有重寫view的onMeasure方法,但是提供了一個(gè)measureChildren方法
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
final int size = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < size; ++i) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
}
}
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
final LayoutParams lp = child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
可以看到冕香,measureChildren方法遍歷view并調(diào)用measureChild方法蛹尝,measureChild方法根據(jù)父類的MeasureSpec和自身的LayoutParam構(gòu)成自身的measureSpec并調(diào)用自身的measure方法測(cè)量。這就是我們常說的view的大小是由父類的大小和自身共同決定的悉尾。