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 neededint 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 contentif (!dirtyOpaque) {onDraw(canvas);}// Step 4, draw the childrendispatchDraw(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 最新源码)
本文详细解析了Android中View的绘制流程,包括从ViewRootImpl的performTraversals开始的draw流程,以及View和ViewGroup在绘制过程中的关键步骤。特别关注了如何仅重绘需要更新的部分,以及具体的绘制步骤。
449

被折叠的 条评论
为什么被折叠?



