一些疑问
- Android 窗口的层次结构是怎样的?
- View绘制涉及到哪些方法,它们之间的调用关系是怎样的?
- requestLayout干了些什么?
- invalidate干了些什么?
- draw 和 onDraw方法的区别?
- diapatchDraw的作用?
Activity View 层次结构
View 结构
绘制流程
先看下measure流程
看下View的onMeasure实现
//这里注意下widthMeasureSpec和heightMeasureSpec的来源,后面会讲到
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 该方法用于设置mMeasuredWidth 和 mMeasuredHeight
setMeasuredDimension(
getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)
);
}
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:
// 用specSize
result = specSize;
break;
}
return result;
}
// minWitdth 或者 背景drawable的宽度
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
上面有提到onMeasure(int widthMeasureSpec, int heightMeasureSpec) 方法的参数来源,下面看下ViewGroup测量child view的方法:
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
// 重点
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
+ widthUsed, lp.width);
// 重点
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
+ heightUsed, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
// This method figures out the right MeasureSpec for one dimension (height or width) of one child view.
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
// 内容省略, 主要就是根据view自己的layout_width和layout_height,
// 在加上parent view(也就是当前ViewGroup)的measureSpec计算出子View的measureSpec
}
Measure的结果
- 遍历view层级进行measure
- 设置各个View 的 mMeasuredWidth 和 mMeasuredHeight
layout
搞清楚measure过程,layout就简单了,流程都大同小异,
ViewRootImpl调用DoctorView的layout方法也就是View的layout方法
public void layout(int l, int t, int r, int b) {
...
// 重点
boolean changed = isLayoutModeOptical(mParent) ? setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
// 重点
onLayout(changed, l, t, r, b);
...
}
...
}
最终都是调用了setFrame方法
protected boolean setFrame(int left, int top, int right, int bottom)
设置下面四个变量,决定了view在parent中的位置,
同时调用onLayout方法,ViewGroup子类都需要实现该方法,完成对自己的子View的layout方法的调用,从而对所有view进行layout
protected int mLeft;
protected int mRight;
protected int mTop;
protected int mBottom;
// View宽度
public final int getWidth() {
return mRight - mLeft;
}
// View高度
public final int getHeight() {
return mBottom - mTop;
}
measure 和 layout之间的关系?
Draw流程
View.draw()方法
public void draw(Canvas canvas) {
/*
* Draw traversal performs several drawing steps which must be executed
* in the appropriate order:
*
* 1. Draw the background
* 2. If necessary, save the canvas' layers to prepare for fading
* 3. Draw view's content
* 4. Draw children
* 5. If necessary, draw the fading edges and restore layers
* 6. Draw decorations (scrollbars for instance)
*/
// Step 1, draw the background, if needed
if (!dirtyOpaque) {
drawBackground(canvas);
}
// skip step 2 & 5 if possible (common case)
...
if (!verticalEdges && !horizontalEdges) {
// Step 3, draw the content
if (!dirtyOpaque) onDraw(canvas);
// Step 4, draw the children
dispatchDraw(canvas);
// Step 5, draw the fade effect and restore layers
drawAutofilledHighlight(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);
// Step 7, draw the default focus highlight
drawDefaultFocusHighlight(canvas);
}
}
ViewGroup.dispatchDraw()
protected void dispatchDraw(Canvas canvas) {
...省略代码...
while (transientIndex >= 0) {
// there may be additional transient views after the normal views
final View transientChild = mTransientViews.get(transientIndex);
if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
transientChild.getAnimation() != null) {
more |= drawChild(canvas, transientChild, drawingTime);
}
transientIndex++;
if (transientIndex >= transientCount) {
break;
}
}
if (preorderedList != null) preorderedList.clear();
// Draw any disappearing views that have animations
if (mDisappearingChildren != null) {
final ArrayList<View> disappearingChildren = mDisappearingChildren;
final int disappearingCount = disappearingChildren.size() - 1;
// Go backwards -- we may delete as animations finish
for (int i = disappearingCount; i >= 0; i--) {
final View child = disappearingChildren.get(i);
more |= drawChild(canvas, child, drawingTime);
}
}
...省略代码...
}
View.draw(Canvas canvas, ViewGroup parent, long drawingTime)
/**
* This method is called by ViewGroup.drawChild() to have each child view draw itself.
*
* This is where the View specializes rendering behavior based on layer type,
* and hardware acceleration.
*/
这里有个疑问,从这个方法会调用View.draw(Canvas canvas),
这里传递过来canvas已经不是父View的canvas了?
答案就在上面的方法View.draw(Canvas canvas, ViewGroup parent, long drawingTime)中,会根据子View的frame值计算出新的canvas,传递给View.draw(Canvas canvas)使用!
最后看下invalidate和requestLayout的调用流程
invalidate()流程
view调用invalidate()方法,通过一层一层向上调用mParent.invalidateChildInParent方法,最终调用DocorView.invalidateChildInparent方法,而内部调用ViewRootImpl的scheduleTraversals()方法,从而又回到上面的performTraversals()流程
requestLayout() 流程
requestLayout流程跟上面invalidate流程基本一致