自定义view有三个重要的方法,onMeasure,onLayout,onDraw。今天先从onMeasure开始。
View层次
首先,先从最简单的看起。我们最常用到的设置布局就是
setContentView(R.layout.activity_main);```
在Activity中找到它的源码:
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
public Window getWindow() {
return mWindow;
}```
所以可以看出调用了Window里的setContentView方法。而Window类是一个抽象类,其唯一实现类是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是一个ViewGroup对象,即判定是否第一次调setContentView方法,如果是就调用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的一个对象,DecorView是PhoneWindow里的一个内部类。首先判断mDecor是否为空,如果为空就创建一个DecorView对象,接下来判断mContentParent是否为空,如果是,调用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创建完毕后,通过PhoneWindow里的setContentView的mLayoutInflater.inflate(layoutResID,mContentParent)把xml文件传递到屏幕。
通过Hierarchy View得到的View层次结构:
整个屏幕是一个FrameLayout,里面包含一个LinearLayout和两个View,分别是statusBar和nagavitionBar,状态栏和导航栏,LinearLayout里是一个FrameLayout,FrameLayout里又嵌套一个ViewGroup(普遍当成是LinearLayout),ViewGroup里有两个FrameLayout,一个是content,一个是title。
至此view的层次介绍完毕。接下来看一下自定义View的onMeasure方法。
onMeasure
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
重写onMeasure方法,直接调用super.onMeasure()调用父类方法,或者直接调用setMeasuredDimension设置view的宽高。里面有两个很重要的参数,widthMeasureSpec和heightMeasureSpec,这两个参数。至于这两个参数的来源,就要找到view的绘制方法。view的绘制是在ViewRootImpl的performTraversals方法实现的
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传入两个参数,mWidth和lp.width,lp.width和lp.height的值为MATCH_PARENT。然后把得到的measureSpec传给performMeasure,performMeasure调用view的measure方法,measure方法又调用onMeasure方法,这样就完成了widthMeasureSpec和heightMeasureSpec从ViewRootImpl到onMeasure的传递。
在这里用到了一个特殊的类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个0),&的规则是遇0变0,遇1不变。所以getMode就取得了高两位,getSize对MODE_MASK取反,变成了00 11111...(30个1)取得低30位。
在onMeasure方法中,调用如下代码:
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方法,但是提供了一个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并调用measureChild方法,measureChild方法根据父类的MeasureSpec和自身的LayoutParam构成自身的measureSpec并调用自身的measure方法测量。这就是我们常说的view的大小是由父类的大小和自身共同决定的。