View 的draw流程
View 的3大流程,measure,layout,draw 在上层都是从ViewRootImpl开始的.具体来说是从
performTraversals开始的.
由ViewRoot对象的performTraversals()方法调用draw()方法发起绘制该View树,值得注意的是每次发起绘图时,并不会重新绘制每个View树的视图,而只会重新绘制那些“需要重绘”的视图,View类内部变量包含了一个标志位DRAWN,当该视图需要重绘时,就会为该View添加该标志位。
ViewRootImpl.java
private void performTraversals() {
获取Surface对象,用于图形绘制
....
- performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
- .........
- performLayout(lp, desiredWindowWidth, desiredWindowHeight);
- ........
- performDraw();
..........
}
performTraversals
调用了performDraw(),
performDraw
()
我们暂时需要关注的是它调用了draw(fullRedrawNeeded).
private void draw(boolean fullRedrawNeeded) {
Surface surface = mSurface; //我们千辛万苦获得的surface
........
if (!drawSoftware(surface, attachInfo, yoff, scalingRequired, dirty)) {
return;
}
.......
}
在
draw()
由调用了drawSoftware.
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int yoff,
boolean scalingRequired, Rect dirty) {
.......
canvas = mSurface.lockCanvas(dirty); //通过Surface对象获取并锁定Canvas绘图对象
.......
mView.draw(canvas); //从DecorView开始绘制,也就是整个Window的根视图,这会导致整个树的绘制
.......
surface.unlockCanvasAndPost(canvas); //释放Canvas锁,然后通知SurfaceFlinger更新这个区域
.......
}
drawSoftware()里面才会调用mView.draw(canvas),到这里才真正开始使用画笔来绘制.
View.java
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;
/*
* 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
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) {//没有设置fadingEdge 属性,渐变投影效果的
// Step 3, draw the content
if (!dirtyOpaque) {
onDraw(canvas);
}
// Step 4, draw the children
dispatchDraw(canvas);
// Step 6, draw decorations (scrollbars)
onDrawScrollBars(canvas);
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}
// we're done...
return;
}
...//处理设置了渐变投影效果的
}
上面的view.draw()方法分为6步:
1.绘制背景.(drawBackground)
2.为第五步绘制fadin投影渐变效果做准备
3.绘制id=content部分,也就是DecorView.(onDraw(canvas);),可以理解绘制视图本身.
4.绘制child view(dispatchDraw(canvas);)
5.绘制fadin投影渐变效果
6.绘制滑动条,(onDrawScrollBars(canvas);)
其中,常见的流程只有1-3-4-6这4部.
其中上面的第3步中的
onDraw()
没有在View 里面具体实现,只是一个空方法.ViewGroup 也没有去重写这个方法.因为每个视图都不一样,需要自己单独去绘制.
其中第4步的
dispatchDraw
没有
在View 里面具体实现,只是一个空方法.ViewGroup重写了这个方法.这个方法是用来遍历调用dispatchDraw()来转调child
的draw()方法,这样draw 事件就一层层传递下去了.
下面来说一下ViewGroup 的大致流程,ViewGroup并没有重写View的draw()方法.而是重写了dispatchDraw().其实ViewGroup的这个方法就和View中提到的一样就是用来绘制child view的.实际查看dispatchDraw()也会发现他是通过转调drawChild()来实现调用child 的draw().
实际在查看LinearLayout的源码是发现它虽然实现了onDraw方法,但是至少绘制了Divider,也就是各个child view 之间是否头分割线,及开始和结尾是否有分割线.
protected void onDraw(Canvas canvas) {
if (mDivider == null) {
return;
}
if (mOrientation == VERTICAL) {
drawDividersVertical(canvas);
} else {
drawDividersHorizontal(canvas);
}
}
void drawDividersVertical(Canvas canvas) {
final int count = getVirtualChildCount();
for (int i = 0; i < count; i++) {
final View child = getVirtualChildAt(i);
if (child != null && child.getVisibility() != GONE) {
if (hasDividerBeforeChildAt(i)) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final int top = child.getTop() - lp.topMargin - mDividerHeight;
drawHorizontalDivider(canvas, top);
}
}
}
if (hasDividerBeforeChildAt(count)) {
final View child = getVirtualChildAt(count - 1);
int bottom = 0;
if (child == null) {
bottom = getHeight() - getPaddingBottom() - mDividerHeight;
} else {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
bottom = child.getBottom() + lp.bottomMargin;
}
drawHorizontalDivider(canvas, bottom);
}
}
这里需要注意的是,在draw 的过程中,视图本身的padding参数会在draw的时候用到.
但是margin参数却不是在视图自己的draw里面用到,而是在其parent 里面已经控制的.(margin参数可以查看LayoutParams)
参考:
View显示流程1-View draw的准备工作
Android中draw过程分析 (结合Android 4.0.4 最新源码)