学习过window编程的人都知道,在屏幕上绘制视图控件很原理很简单,指定屏幕上一块区域,在里面去绘制你想要的内容,其他的系统实现原理也大同小异,但真正实现起来,还是相当复杂的。这几天看了下Android 的View的代码,觉得里面内容还是相当多的,把自己理解学习的再此罗列一下,大家共同交流学习。
屏幕的绘制可以理解为由类ViewRoot(4.0版本以前)或ViewRootImpl(4.0版本以后)的方法performTraversals()触发。在这个方法内主要依次做了三件事:
1. measure 计算自己大小尺寸,如果为ViewGroup,递归调用容器内view的measure。
2. layout 设置自己在父容器内的位置,即设置成员变量mLeft,mTop,mRight,mBottom,如果为ViewGroup,依次调用容器内的View的layout
3.draw 在自己的区域内绘制自己
private void performTraversals(){ final View host = mView;//mView为PhoneWindow.DecorView ...... childWidthMeasureSpec = getRootMeasureSpect(desiredWindowWidth, lp.width); childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height); host.measure(childWidthMeasureSpec, childHeightMeasureSpec); ...... host.layout(0, 0, host.mMeasureWidth, host.mMeasureHeight); ...... draw(fullRedrawNeeded); ...... }
下面分别介绍measure(),layout(),draw()三个过程的实现。
一、measure
public final void measure(int , int )方法主要是计算View的宽和高,通过方法setMeasuredDimension(int, int)给成员变量mMeasuredWidth和mMeasuredHeight赋值。如果View是ViewGroup类型,则分别调用它容器内View的measure方法,递归的计算出每个View的宽和高。
measure的运算结果由父容器和本身决定,下面的部分代码显示ViewRoot中measure(int, int)两个参数值的运算过程
childWidthMeasureSpec = getRootMeasureSpect(desiredWindowWidth, lp.width); childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height); host.measure(childWidthMeasureSpec, childHeightMeasureSpec);
private int getRootMeasureSpec(int windowSize, int rootDimension){ int measureSpec; switch(rootDimension){ case ViewGroup.LayoutParams.MATCH_PARENT: measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY); break; case ViewGroup.LayoutParams.WRAP_CONTENT: measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST); break; default: measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY); break; } return measureSpec; }
public static int makeMeasureSpec(int size, int mode) { return size + mode; }
而一个View的尺寸最终是由setMeasuredDimension(int, int)决定的,也就是说如果你在这里传了(100,100),无论使用view的xml中或者代码中怎么给它传值,它的尺寸都是100x100。
这里联想到一个有趣的问题:ViewGroup的值设为wrap_content,而它里面的view设置为match_parent,那么它们measure后的值应该是怎么样的?看了下FrameLayout的measure实现,容器在算它的尺寸时,会先把里面match_parent的View缓存起来,根据非match_parent的View计算出它的尺寸,然后再把它的尺寸做为参数,让match_parent的view算自己的尺寸。
二、layout
layout主要是给view的成员变量mLeft,mTop,mRight,mBottom赋值,这里表示相对于父容器的位置。ViewGroup通过onLayout方法,给它容器内的子view调用layout()方法赋值,而view通常不需要实现onLayout。
三、draw
draw同样是个比较复杂的过程,但原理和上面差不多,每个view会通过draw在自己的区域画出自己,而ViewGroup会通过方法dispatchDraw()遍历所有子view,使子view调用自己的draw()方法绘制自己。