measure过程
- View:直接完成其测量的过程
ViewGroup:测量自己和它的子元素
View的measure过程:
- 最开始是View的measure方法
- measure方法里面调用了onMeasure方法,onMeasure方法的源码为:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
它是设置View的宽高测量值。其中的getDefaultSize方法返回的是measureSpec中的SpecSize。可参考MeasureSpec的理解了解一下MeasureSpec。
getSuggestedMinimumWidth与getSuggestedMinimumHeight返回的规则是:如果View有设置背景,则返回该背景对应Drawable的原始宽高;否则返回指定的android:minWidth和android:minHeight.
getDefaultSize方法源码:
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;
}
从中和集合MeasureSpec的SpecMode可以注意到,如果直接继承View来自定义控件,如果没有重写onMeasure方法并设置wrap_content的自身大小,则wrap_content会默认是match_parent。
解决(套路):
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthSpecMode=MeasureSpec.getMode(widthMeasureSpec);
int heightSpecMode=MeasureSpec.getMode(heightMeasureSpec);
int widthSpecSize=MeasureSpec.getSize(widthMeasureSpec);
int heightSpecSize=MeasureSpec.getSize(heightMeasureSpec);
if (widthSpecMode==MeasureSpec.AT_MOST && heightSpecMode==MeasureSpec.AT_MOST){
setMeasuredDimension(默认的width,默认height);
}else if (widthSpecMode==MeasureSpec.AT_MOST){
setMeasuredDimension(默认的width,heightSpecSize);
}else if (heightSpecMode==MeasureSpec.AT_MOST){
setMeasuredDimension(widthSpecSize,默认的height);
}
}
ViewGroup的measure过程:
- measureChildren方法,在此中遍历子View,
- 调用measureChild方法对每个子View进行测量
- measureChild:获取子View的MeasureSpec,然后调用每个子View的measure方法
注意:很多时候我们不知道View到底在何时测量完毕,所有在获取宽高时要采用特定的办法。
- 采用onWindowFocusChanged:View已经测量完毕,当Activity的窗口得到焦点或者失去焦点时均会被调用。
- view.post(runnable) : 通过消息机制去获取。
- ViewTreeObserver:使用它的OnGlobalLayoutListener接口,可监听View树的变化,当View的可见性发生变化时,onGlobalLayout会被调用,这时的View也是测量好的了。
- 自己手动调用:view.measure(int widthMeasureSpec, int heightMeasureSpec)。
Layout过程
Layout过程是用来确定View的具体位置的。
1.View的layout过程
- 在layout方法中通过setFrame方法设定View的四个定点的位置
调用onLayout方法:确定本身的位置,与onMeasure相似,都是与具体的布局有关,在View中没有具体的实现。
2.ViewGroup的layout过程
没有具体实现onLayout方法。具体的View有着不同的实现
draw过程
public void draw(Canvas canvas) {
final int privateFlags = mPrivateFlags;
final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
(mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
// Step 1, draw the background, if needed
int saveCount;
if (!dirtyOpaque) {
drawBackground(canvas);
}
// skip step 2 & 5 if possible (common case)
final int viewFlags = mViewFlags;
boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
if (!verticalEdges && !horizontalEdges) {
// Step 3, draw the content
if (!dirtyOpaque) onDraw(canvas);
// Step 4, draw the children
dispatchDraw(canvas);
// Overlay is part of the content and draws beneath Foreground
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}
// Step 6, draw decorations (foreground, scrollbars)
onDrawForeground(canvas);
// we're done...
return;
}
......
根据注释的步骤:
- drawBackground(canvas):绘制背景
- onDraw(canvas):绘制自己
- dispatchDraw(canvas):绘制children
- onDrawForeground(canvas):绘制装饰
特殊方法:setWillNotDraw(boolean willNotDraw)
该方法用来设置是否绘制内容。
true:不绘制,然后系统还会进行优化。
false:绘制。