View绘制
ViewRoot 和 DecorView
ViewRoot即ViewRootImpl,是连接WindowManager和DecorView的纽带。
View的绘制流程是从ViewRoot的performTraversals
开始的,依次进过measure
、layout
、draw
完成最终的View的绘制。
performTraversals
会依次调用performMeasure
、performLayout
、performDraw
方法。
DecorView是顶级View,它是一个LinearLayout的布局,上面就是actionbar,下面就是内容栏。内容栏的id为content。 我们可以通过如下代码得到内容栏的view:
ViewGroup content = (ViewGroup)findViewById(android.R.id.content);
MeassureSpec
MeassureSpec是一个32位的int,分别记录了尺寸(SpecSize)和计算尺寸的测量模式(SpecMode)。
- UNSPECIFIED:一般是系统使用,父容器不对View做限制,要多大给多大。
- EXACTLY:对应具体的数值或LayoutParams的
match_parent
。 - AT-MOST:对应LayoutParams的
wrap_content
。父容器指定了一个SpecSize的大小,View的大小不能大于这个值。
View的计算MeassureSpec的过程
顶级view——DecorView的过程
DecorView的MeassureSpec是由窗口大小和其自身的LayoutParams来决定的。
ViewRoot的measureHierarchy
描述了这个过程,这里还区分了dialog和一般activity页面的差异。
具体实现:
/**
* Figures out the measure spec for the root view in a window based on it's
* layout params.
*
* @param windowSize
* The available width or height of the window
*
* @param rootDimension
* The layout params for one dimension (width or height) of the
* window.
*
* @return The measure spec to use to measure the root view.
*/
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;
}
普通View
普通View的MeassureSpec是由父容器的MeasureSpec和自身的LayoutParams来决定的。(这个其实跟DecorView逻辑上是一致的)。
主要看ViewGroup中的getChildMeasureSpec
方法。
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
int size = Math.max(0, specSize - padding);
int resultSize = 0;
int resultMode = 0;
switch (specMode) {
// Parent has imposed an exact size on us
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size. So be it.
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent has imposed a maximum size on us
case MeasureSpec.AT_MOST:
if (childDimension >= 0) {
// Child wants a specific size... so be it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size, but our size is not fixed.
// Constrain child to not be bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent asked to see how big we want to be
case MeasureSpec.UNSPECIFIED:
... ...
}
View工作流程-measure
view的measure过程
view的measure是从measure
方法开始的,这个是不可被继承的,但是实际的测量是在onMeasure
里完成的,这个方法子类可以重写。
viewGroup的measure过程
measureChildren
-> measureChild
(只有在子view不为gone时才会调用) -> measure
(这里就进入到view的流程了)
如何准确的获得view的宽高?
- onWindowFocusChanged:窗口获取光标
- view.post:这个的原理是把runnable放到消息队列的尾部,因为这时前面的都完成了。
- ViewTreeObserver:
- 手动读view进行measure:
- match_parent: 无法计算
- 具体的值:
int width = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY); int height = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY); view.measure(width, height);
- wrap_content:
int width = View.MeasureSpec.makeMeasureSpec((1<<30) - 1, View.MeasureSpec.AT_MOST); int height = View.MeasureSpec.makeMeasureSpec((1<<30) - 1, View.MeasureSpec.AT_MOST); view.measure(width, height);
View工作流程-layout
getMeasuredWidth/getMeasuredHeight 和 getwidth/getHeight
getwidth/getHeight形成于layout过程; getMeasured*形成于meassure过程; 两者一般情况下是相等的。如果重写了layout方法,并且修改了值就不对等了。
View工作流程-draw
- 绘制背景 --- drawBackground
- 绘制自己 --- onDraw
- 绘制子View --- dispatchDraw
- 绘制装饰等其他(foreground, scrollbars) --- onDrawForeground
特殊方法:setWillNotDraw()
,标记此View是否需要绘制。
自定义View的注意点
- 重写View或ViewGroup,必须在
onMeasure
里对wrap_content
做特殊处理,未处理时,则wrap_content
的效果和match_parent
是一样的。 - 重写View,需要在draw中对padding属性进行处理,否则不起作用;重写ViewGroup,需要在
onMeasure
和onLayout
里对padding和子元素的margin进行处理。 - View中有线程或者动画,要及时停止,否则会有可能会造成内存泄露。可在
onDetachedFromWindow()
和onAttachedToWindow()
触发线程或动画的停止和启动。