鸽了好久还是弄完了,相当于看书记的笔记了......
1、基础概念
- ViewRoot: ViewRootImpl 类,连接 WindowManager 和 DecorView,三大流程均通过 ViewRoot 完成。
- decorView:本质上是一个FrameLayout,是Activity中所有View祖先。
View绘制流程是从ViewRoot的performTraversals方法开始,经过measure,layout,draw三步完成。
- measure 用于测量 View 的宽高
- layout 用于确定 View 在父容器中的位置‘
- draw 用于绘制
performTraverslas会依次调用performMeasure,performLayout,performDraw三个方法,分别完成顶级View的三大流程。在performMeasure中调用measure方法,在measure中调用onMeasure对所有子元素测量,完成对View所有子元素的测量。performLayout和performDraw与performMeasure类似。
2、MeasureSpec
MeasureSpec决定了一个View的尺寸规格,但还受父容器影响。在测量过程中,系统会将View的LayoutParams根据父容器施加的规则转化成对应的MeasureSpec,然后再测量View的宽高。
MeasureSpec代表了一个32位int值,高2位代表SpecMode,即测量模式,低30位代表SpecSize,规格大小。
SpecMode有三类,如下:
- UNSPECIFIED:父容器不对View有任何限制,一般用于系统内部
- EXACTLY:父容器已经检测出View的精确大小,此时最终大小就是SpecSize的值,对应LayoutParams中的match_parent和具体数值两种情况。
- AT_MOST:父容器指定SpecSize,View的大小不能大于这个值,对应于LayoutParams中的wrap_content。
3、View的工作流程
View的工作流程主要指measure,layout,draw这三大流程。
3.1、measure
measure的测量分为对ViewGroup的测量和对View的测量,对View测量时,通过measure即可完成,如果是一个ViewGroup时,除了完成自己的测量,还会遍历所有子元素发的measure方法。
3.1.1、View
view的measure由其measure方法完成,在View的measure方法中会去调用onMeasure方法。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
setMeasureDimension(
getDefaultSize(getSuggestedMinimumWidth(),widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(),heightMeasureSpec));
}
setMeasureDimension会设置View的宽高测量值,需要调用getDefaultSize方法
- getgetDefaultSize
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;
}
在getDefaultSize方法中,在AT_MOST和EXACTLY状态,getDefaultSize返回MeasureSpec中的SpecSize,即View测量后的大小。在UNSPECIFIED状态下,返回值为size的值,即宽高分别为getSuggestedMinimumWidth和getSuggestedMinimumHeight的返回值。
- getSuggestedMinimumWidth & getSuggestedMinimumHeight
protected int getSuggestedMinimumWidth(){
return (mBackground==null)? mMinWidth : max(mMinWidth,mBackground.getMinimumWidth());
}
protected int getSuggestedMinimumHeight(){
return (mBackground==null)? mMinHeight : max(mMinHeight,mBackground.getMinimumHeight());
}
在上述代码中,如果mBackground是null,即View没有设置背景,那么宽度为mMinWidth,高度为mMinHeight。而mminWidth对应android:minWidth属性对应的值,默认为0,如果View指定了背景,则View的宽度为max(mMinWidth,mBackground.getMinimumWidth()),即mMinWidth和mBackground.getMinimumWidth()中较大的值。
- mBackground.getMinimumWidth
public int getMinimumWidth(){
final int intinsicWidth=getIntrinsicWidth();
return intrinsicWidth > 0 ? intrinsicWidth :0 ;
}
则getMinimumWidth返回的是Drawable的原始宽度。
由此可见,View的宽高有SpecSize决定。直接继承View的空间需要重写onMeasure方法并设置wrap_content时的自身大小,否则View的SpecSize是parentSize。
3.1.2、ViewGroup
对于ViewGroup来说,除了完成自己的measure过程以外,还需要遍历所有子元素的measure,子元素递归执行。ViewGroup没有重写inMeasure方法,同时它提供了一个measureChildren的方法。
- measureChildren
protected void measureChildren(int widthMeasureSprc,int heightMeasureSpec){
final int size = mChildrenCount;
final View[] children = mChildren;
for(int i=0;i
在measureChildren方法中,会遍历所有子元素,并测量所有子元素的大小。
- measureChild
protect 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);
}
在measureChild中取出子元素的LayoutParams,之后通过getChildMeasureSpec创建子元素的MeasureSpec,之后将MeasureSpec传递给View的measure方法测量。
ViewGroup是一个抽象类,不同的布局有不同的测量方法,所以其测量过程onMeasure需要有各个子类的实现。
分析LinearLayout的onmeasure方法
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
if(mOrientation == VERTICAL){
measureVertical(widthMeasureSpec, heightMeasureSpec);
}
else{
measureHerizontal(widthMeasureSpec, heightMeasureSpec);
}
}
在LinearLayout的measure方法中会根据orientation选择是measureVertical还是measureHerizontal方法,在相应方法中会遍历子元素执行measureChildBeforeLayout方法,该方法内部会调用子元素的measure方法,并且通过mTotalLength来储存LinearLayout在数值方向的初步高度,在MTotalLength中包含了子元素的高度和在竖直方向上的margin,子元素测量完毕后,LinearLayout会测量自己的大小。
3.2、Layout
Layout是ViewGroup用来确定子元素的位置,当ViewGroup的位置被确定以后,会在onLayout中遍历所有子元素并调用layout方法,在了layout方法中onLayout方法又会被调用,layout方法确定View本身的位置,而onLayout方法确定所有子元素的位置。
- layout
public void layout(int l,int t,int r.int b){
if((mPrivateFlags3 & PFLAGS3_MEASURE_NEEDED_BEFORE_LAYOUT)!+0){
onMeasure(mOldWidthMeasureSpec,mOldHeightMeasureSpec);
mPrivateFlags3 &= ~PFLAGS3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
int oldL=mLeft;
int oldT=mTop;
int oldB=mButtom;
int oldR=mRight;
boolean changed=isLayoutModeOptical(mParent)? setOpticalFrame(l,t,r,b): setFrame(l,t,r,b);
if(changed || (mPrivateFlags & PLAG_LAYOUT_REQUIRED)==PFLAG_LAYOUT_REQUIRED){
onLayout(changed,l,t,r,b);
mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
ListenerInfo li=mListenerInfo;
if(li != null && li.mOnLayoutChangedListeners != null){
ArrayList listenersCopy = (ArrayList)li.mOnLayoutChangeListeners.clone();
int numListeners=listenersCopy.size();
for(int i=0;i
layout方法的流程如下,首先通过setFrame方法设定四个顶点的位置,初始化mLeft,MRight,mTop,mBottom四个值,之后调用onLayout方法,父容器确定子元素的位置,和onMeasure方法类似,onLayout具体的实现取决于布局。
示例LinearLayout的onLayout方法。
- onLayout
protected void onLayout(boolean changed,int l,int t,int r,int b){
if(mOrientation == VERTICAL){
layoutVertical(l,t,r,b);
}else {
layoutHorizontal(l,t,r,b);
}
}
在onLayout中,依旧会根据不同的布局(vertical和horizontal)选择不同的方法。
- layoutVertical
void layoutVretical(int left,int top,int right,int bottom){
...
final int count=getVerticalChildCount();
for(int i=0;i
layoutVertical会遍历所有子元素,并且会调用getChildFrame方法位子元素指定相应的位置,其中childTop会逐渐增大,即后面的子元素会放在靠下的位置。setChildFrame调用了元素的layout方法,这样父元素在layout方法中完成自己的定位以后,通过onLayout方法调用子元素的layout方法,子元素又通过自己的layout方法来确定自己的位置。
- setChildFrame
private void setChildFrame(View child,int left,int top,int width,int height){
child.layout(left,top,left+width,top+height);
}
在setChildFrame中的width和height实际就是子元素的测量的宽高。
而在layout方法中会通过setFrame去设置子元素四个顶点的位置。
3.3、Darw
Draw的绘制步骤如下:
- 绘制背景 background.draw(canvas)
- 绘制自己 onDraw
- 绘制children dispatchDraw
- 绘制装饰 onDrawScrollBars
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;
int saveCount;
//绘制背景
if(!dirtyOpaque){
drawBackground(canvas);
}
final int viewFlags=mViewFlags;
boolean horizontalEdges=(viewFlags & FADING_EDGE_HORIZONTAL) !=0;
boolean verticalEdges=(viewFlags & FADING_EDGE_VERTICAL) !=0;
if(!verticalEdges && !horizontalEdges){
if(!dirtyOpaque) onDraw(canvas);
dispatchDraw(canvas);
onDrawScrollBars(canvas);
if(mOverlay != null && ! mOverlayl.isEmpty()){
mOverlay.getOverlayView().dispatchDraw(canvas);
}
return;
}
...
}
View的绘制是通过dispatchDraw实现的,dispatchDraw会遍历所有子元素的draw方法。
参考来源:
《Android开发艺术探索》