您的位置:首页 > 其它

View的工作流程-measure、layout、draw三大流程

2017-09-13 16:44 337 查看

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:绘制。

4000
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  measure layout draw 源码