View 和ViewGroup 的draw流程

View 的draw流程

View 的3大流程,measure,layout,draw 在上层都是从ViewRootImpl开始的.具体来说是从 performTraversals开始的.
由ViewRoot对象的performTraversals()方法调用draw()方法发起绘制该View树,值得注意的是每次发起绘图时,并不会重新绘制每个View树的视图,而只会重新绘制那些“需要重绘”的视图,View类内部变量包含了一个标志位DRAWN,当该视图需要重绘时,就会为该View添加该标志位。
ViewRootImpl.java
   
   
  1. private void performTraversals() {
  2. 获取Surface对象,用于图形绘制
  3. ....
  4. performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
  5. .........
  6. performLayout(lp, desiredWindowWidth, desiredWindowHeight);
  7. ........
  8. performDraw();
  9. ..........
  10. }
performTraversals 调用了performDraw(), performDraw () 我们暂时需要关注的是它调用了draw(fullRedrawNeeded).
    
    
  1. private void draw(boolean fullRedrawNeeded) {
  2. Surface surface = mSurface; //我们千辛万苦获得的surface
  3. ........
  4. if (!drawSoftware(surface, attachInfo, yoff, scalingRequired, dirty)) {
  5. return;
  6. }
  7. .......
  8. }

draw() 由调用了drawSoftware.
   
   
  1. private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int yoff,
  2. boolean scalingRequired, Rect dirty) {
  3. .......
  4. canvas = mSurface.lockCanvas(dirty); //通过Surface对象获取并锁定Canvas绘图对象
  5. .......
  6. mView.draw(canvas); //从DecorView开始绘制,也就是整个Window的根视图,这会导致整个树的绘制
  7. .......
  8. surface.unlockCanvasAndPost(canvas); //释放Canvas锁,然后通知SurfaceFlinger更新这个区域
  9. .......
  10. }
drawSoftware()里面才会调用mView.draw(canvas),到这里才真正开始使用画笔来绘制.
View.java
   
   
  1. public void draw(Canvas canvas) {
  2. final int privateFlags = mPrivateFlags;
  3. final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
  4. (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
  5. mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
  6. /*
  7. * Draw traversal performs several drawing steps which must be executed
  8. * in the appropriate order:
  9. *
  10. * 1. Draw the background
  11. * 2. If necessary, save the canvas' layers to prepare for fading
  12. * 3. Draw view's content
  13. * 4. Draw children
  14. * 5. If necessary, draw the fading edges and restore layers
  15. * 6. Draw decorations (scrollbars for instance)
  16. */
  17. // Step 1, draw the background, if needed
  18. int saveCount;
  19. if (!dirtyOpaque) {
  20. drawBackground(canvas);//绘制背景
  21. }
  22. // skip step 2 & 5 if possible (common case)
  23. final int viewFlags = mViewFlags;
  24. boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
  25. boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
  26. if (!verticalEdges && !horizontalEdges) {//没有设置fadingEdge 属性,渐变投影效果的
  27. // Step 3, draw the content
  28. if (!dirtyOpaque) {
  29. onDraw(canvas);
  30. }
  31. // Step 4, draw the children
  32. dispatchDraw(canvas);
  33. // Step 6, draw decorations (scrollbars)
  34. onDrawScrollBars(canvas);
  35. if (mOverlay != null && !mOverlay.isEmpty()) {
  36. mOverlay.getOverlayView().dispatchDraw(canvas);
  37. }
  38. // we're done...
  39. return;
  40. }
  41. ...//处理设置了渐变投影效果的
  42. }
上面的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 之间是否头分割线,及开始和结尾是否有分割线.
   
   
  1. protected void onDraw(Canvas canvas) {
  2. if (mDivider == null) {
  3. return;
  4. }
  5. if (mOrientation == VERTICAL) {
  6. drawDividersVertical(canvas);
  7. } else {
  8. drawDividersHorizontal(canvas);
  9. }
  10. }
  11. void drawDividersVertical(Canvas canvas) {
  12. final int count = getVirtualChildCount();
  13. for (int i = 0; i < count; i++) {
  14. final View child = getVirtualChildAt(i);
  15. if (child != null && child.getVisibility() != GONE) {
  16. if (hasDividerBeforeChildAt(i)) {
  17. final LayoutParams lp = (LayoutParams) child.getLayoutParams();
  18. final int top = child.getTop() - lp.topMargin - mDividerHeight;
  19. drawHorizontalDivider(canvas, top);
  20. }
  21. }
  22. }
  23. if (hasDividerBeforeChildAt(count)) {
  24. final View child = getVirtualChildAt(count - 1);
  25. int bottom = 0;
  26. if (child == null) {
  27. bottom = getHeight() - getPaddingBottom() - mDividerHeight;
  28. } else {
  29. final LayoutParams lp = (LayoutParams) child.getLayoutParams();
  30. bottom = child.getBottom() + lp.bottomMargin;
  31. }
  32. drawHorizontalDivider(canvas, bottom);
  33. }
  34. }

这里需要注意的是,在draw 的过程中,视图本身的padding参数会在draw的时候用到.
但是margin参数却不是在视图自己的draw里面用到,而是在其parent 里面已经控制的.(margin参数可以查看LayoutParams)


参考:
View显示流程1-View draw的准备工作
Android中draw过程分析 (结合Android 4.0.4 最新源码)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值