美文网首页view
安卓源码-view的绘制流程

安卓源码-view的绘制流程

作者: isLJli | 来源:发表于2020-06-27 11:06 被阅读0次

view的绘制流程

首先总结一下view的绘制流程,再详细的分析

  • 在#ViewRootImpl的方法中有一个requestLayou()的方法通过层层的调用(requestLayout() -> scheduleTraversals()-> doTraversal() -> performTraversals()),调用到了#ViewRootImpl的performTraversals,这个performTraversals通过调用performMeasure()、performLayout()、performDraw()来完成我们的view的绘制

  • 第一:performMeasure():用于指定和测量layout中所有控件的宽高。先调用meature()方法检测跟之前的Spec是否有改变和requestLayout的标志,再决定是否调用onMeasure()真正开始测量--对于viewGroup控件来讲,它会先计算所有子view的宽高再来计算和指定自己的宽高。对于view控件的宽高则由自己和父布局决定。

  • 第二:performLayout():用于摆放子布局,for循环所有子view,用child.layout摆放所有子view。

  • 第三:performDraw():用于绘制自己还有子view,对于ViewGroup控件首先绘制自己的背景,最多for循环调用所有子view的draw()方法。对于view控件来说,先绘制自己的背景,然后绘制自己的内容。

对requestLayout()方法到performTraversals()的追踪

#在ViewRootImpl类
@Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true; //标志为ture,在meature方法中检查
            scheduleTraversals(); //追踪这个方法
        }
    }

  @UnsupportedAppUsage
    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mChoreographer.postCallback(//追踪一下这个mTraversalRunnable
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
           ...
        }
    }

//mTraversalRunnable的类
final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal(); //继续追踪
        }
    }

  void doTraversal() {
        if (mTraversalScheduled) {
             ...
            performTraversals(); //发现调用了我们的performTraversals方法
            if (mProfile) {
                Debug.stopMethodTracing();
                mProfile = false;
            }
        }
    }

 
 private void performTraversals() {
   //这个方法开始调用我们的performMeasure()、performLayout、performDraw()方法。
//这个方法有6.7百行代码,因此从performMeasure()、performLayout、performDraw()开始分析。
}

分析前必备知识MeasureSpec

  • 模式:
    at_most: warp_parent
    exactly: match_parent,100
  • 如果父布局的模式为exactly,那么当前的view的大小为match_parent,100时模式也为exactly,如果大小是warp_parent则模式为at_most
  • 如果父布局的模式是at_most,那么只有当前的view大小是100(固定值)时是exactly,其他两种都是at_most。
  • 所以view的模式由父布局和自己大小决定,而view得大小得信息存在LayoutParams,每个控件都可以由自己特定得LayoutParams,比如RelativeLayout、LinearLayout,它们都是继承ViewGroup.LayoutParams。

Measure

performTraversals方法说起:

#ViewRootImpl类
  private void performTraversals() {
       ...
    if (!mStopped) {
          //在decorView中mWidth,mHeight是屏幕的高度
         //第一次调用时,从DecorView拿到MeasureSpec开始向下遍历测试大小。
        int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);  
        int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);

        performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);       
        }
      ...
    } 

      private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
      if (mView == null) {
          return;
      }
      Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
      try {
         //开始调用View的measure方法
          mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
      } finally {
          Trace.traceEnd(Trace.TRACE_TAG_VIEW);
      }
  }
}

从上面可知,performMeasure会调用measure()方法:

#View类

/**
* 调用这个方法来算出一个View应该为多大。参数为父View对其宽高的约束信息。
* 实际的测量工作在onMeasure()方法中进行
*/
// 调用requestLayout,view的measureSpec跟原来的不一样,或者模式不为exactly,或者size大小不一样了,需要调用onMeasure()方法。
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
. . . 
// 判断是否需要重新布局

//是否调用requestLayout
final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
//跟原来宽高的MeasureSpec是否一样,只要这个不一样就要重新测量
final boolean specChanged = widthMeasureSpec != mOldWidthMeasureSpec || heightMeasureSpec != mOldHeightMeasureSpec;
//MeasureSpec模式要为exactly
final boolean isSpecExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY && MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;
//跟原来的大小是否一样
final boolean matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec) && getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);
//对上面做三个判断
final boolean needsLayout = specChanged && (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize);

// 调用了requesLayout或跟原来的MeasureSpec不一样
if (forceLayout || needsLayout) {
  . . .
  // 从forceLayout 判断是否有缓存
  int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
  if (cacheIndex < 0 || sIgnoreMeasureCache) {
    // 没有缓存,调用onMeasure精确测量view,每个控件和自定义view通过重写的onMesaure()方法确定测量大小。
    onMeasure(widthMeasureSpec, heightMeasureSpec);
    . . .
  } else {
    // 缓存命中,直接从缓存中取值即可,不必再测量
    long value = mMeasureCache.valueAt(cacheIndex);
    // Casting a long to int drops the high 32 bits, no mask needed
    setMeasuredDimensionRaw((int) (value >> 32), (int) value);
    . . .
  }. . .  }
//更换新的MeasureSpec
mOldWidthMeasureSpec = widthMeasureSpec;
mOldHeightMeasureSpec = heightMeasureSpec;
mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 | (long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
}

从上面得出,meature方法调用了onMeasure()方法进行真正的测量。我们看一下继承ViewGroup的LineaLayout的onMeasure怎么写?

@Override
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
      if (mOrientation == VERTICAL) {
         //我们看一下垂直模式怎么测量
          measureVertical(widthMeasureSpec, heightMeasureSpec);
      } else {
          measureHorizontal(widthMeasureSpec, heightMeasureSpec);
      }
  }
 void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
      // 看看.
      for (int i = 0; i < count; ++i) {
          final View child = getVirtualChildAt(i);
           //如果子孩子为null或Gone时不测量
          // 测量子孩子
          measureChildBeforeLayout(child, i, widthMeasureSpec, 0,
                      heightMeasureSpec, usedHeight);

          final int childHeight = child.getMeasuredHeight();

          final int totalLength = mTotalLength;
          // 高度是子View的高度不断的叠加
          mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +
                     lp.bottomMargin + getNextLocationOffset(child));
      }
      int heightSize = mTotalLength;
      // Check against our minimum height
      heightSize = Math.max(heightSize, getSuggestedMinimumHeight());
      
      // Reconcile our calculated size with the heightMeasureSpec
      int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);
      // 设置宽高,这里resolveSizeAndState()就是根据VeiwGroup的模式和子view叠加的最大值,来决定取哪个?
      setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
              heightSizeAndState);
  }

LinearLayout的onMeasure方法不断的测量子view,如果是垂直方向,不断的叠加height高度。我们看看怎么测量子view。

void measureChildBeforeLayout(View child, int childIndex,
          int widthMeasureSpec, int totalWidth, int heightMeasureSpec,
          int totalHeight) {
      measureChildWithMargins(child, widthMeasureSpec, totalWidth,
              heightMeasureSpec, totalHeight);
  }
protected void measureChildWithMargins(View child,
          int parentWidthMeasureSpec, int widthUsed,
          int parentHeightMeasureSpec, int heightUsed) {
     //MarginLayoutParams才可以拿到margin
      final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
     //这个方法很重要,它生成子view的MessureSpec
     //传进去父MessureSpec,padding,lp.width 来确定模式
      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);

       //生成MeasureSpec,调用measure然后调用view.onMeasure来测量大小
      child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
  }

getChildMeasureSpec()这个方法很重要,它可以确定view的MeasureSpec。具体的生成模式已经在上面写过了,看看大小怎么确定

public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
      //父view的模式和大小
      int specMode = MeasureSpec.getMode(spec);
      int specSize = MeasureSpec.getSize(spec);
      //父view-padding 确定width最大值
      int size = Math.max(0, specSize - padding);

      int resultSize = 0;
      int resultMode = 0;

      //父view的模式
      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) {
              resultSize = size; //大小为width最大值
              resultMode = MeasureSpec.EXACTLY;
          } else if (childDimension == LayoutParams.WRAP_CONTENT) {
              resultSize = size; //大小为width最大值
              resultMode = MeasureSpec.AT_MOST;
          }
          break;

      // Parent has imposed a maximum size on us
      case MeasureSpec.AT_MOST:
          if (childDimension >= 0) {
              resultSize = childDimension; //大小为固定值
              resultMode = MeasureSpec.EXACTLY;
          } else if (childDimension == LayoutParams.MATCH_PARENT) {
              resultSize = size; //大小为width最大值
              resultMode = MeasureSpec.AT_MOST;
          } else if (childDimension == LayoutParams.WRAP_CONTENT) {
              resultSize = size;//大小为width最大值
              resultMode = MeasureSpec.AT_MOST;
          }
          break;
         ...
      //根据大小的模式生成MeasureSpec
      return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
  }

LinearLayout的onMeasure()就分析完了。我们可以看见这是一个递归的过程,先遍历所有的子view的大小,然后根据所有的子view的大小确定自己的大小。从代码我们可以知道,decorView先自己生成MeasureSpec,调用performMeasure()方法->meature()方法,再调用onMeasure,在这里decorView所有的子view都通过measure->onMeasure来测量大小,不会调用performMeasure()方法。


我们来看一下继承view的控件在没有重写onMeasure方法时,源码会怎么代替测量?

#View类

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 直接调用setMeasuredDimension 确定宽高setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
            getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}

//view里面的默认的根据模式确定大小
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.AT_MOST:(这种默认宽高最大值,我们重写时就是为了自己确定它的大小)
case MeasureSpec.EXACTLY:
    result = specSize;
    break;
case MeasureSpec.UNSPECIFIED:
    result = size; //最小值
    break;
}
return result;
}

最后measure的方法就分析完了。在总结一遍:

  • DecorView调用performTraversals的performMeasure(),然后调用measure方法来查看一下是否跟之前MeasureSpec和是否调用了requestLayout,来决定是否调用onMeasure进行真正的测量。在onMeasure中ViewGroup会先遍历所有的子view并测量它的大小,然后递归算出最大值等,再根据自己模式来确定大小。view的onMeasure方法则根据自己的模式是来确定自己大小,并最终调用setMeasuredDimension来确定最终大小。

Layout

从performTraversals开始

#ViewRootImpl
private void performTraversals() {
        ...
    if (didLayout) {
        performLayout(lp, desiredWindowWidth, desiredWindowHeight);
        ...
    }
}

rivate void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
          int desiredWindowHeight) {
      final View host = mView; //这个view就是DecorView
      // 调用layout()方法,一个view知道左上顶点坐标和宽高就可以确定位置
      host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
  }

#View
public void layout(int l, int t, int r, int b) {
      //setFrame方法是将四个顶点保存在变量中,方便以后取用
      boolean changed = isLayoutModeOptical(mParent) ?
              setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);

      if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
         //把四个顶点传给onLayout
          onLayout(changed, l, t, r, b);
}

接下来应该到了LinearLayout和FrameLayout中的onLayout()方法

#LinearLayout
  @Override
  protected void onLayout(boolean changed, int l, int t, int r, int b) {
      if (mOrientation == VERTICAL) {
          // 以垂直为例
          layoutVertical(l, t, r, b);
      } else {
          layoutHorizontal(l, t, r, b);
      }
  }

void layoutVertical(int left, int top, int right, int bottom) {
      final int paddingLeft = mPaddingLeft;

      int childTop;
      int childLeft;

      // Where right end of child should go
      //计算父窗口推荐的子View宽度
      final int width = right - left;
      //计算父窗口推荐的子View右侧位置
      int childRight = width - mPaddingRight;

      // Space available for child
      //child可使用空间大小
      int childSpace = width - paddingLeft - mPaddingRight;
      //通过ViewGroup的getChildCount方法获取ViewGroup的子View个数
      final int count = getVirtualChildCount();
      //获取Gravity属性设置
      final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
      final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
      //依据majorGravity计算childTop的位置值
      switch (majorGravity) {
         case Gravity.BOTTOM:
             // mTotalLength contains the padding already
             childTop = mPaddingTop + bottom - top - mTotalLength;
             break;

             // mTotalLength contains the padding already
         case Gravity.CENTER_VERTICAL:
             childTop = mPaddingTop + (bottom - top - mTotalLength) / 2;
             break;

         case Gravity.TOP:
         default:
             childTop = mPaddingTop;
             break;
      }
      //重点!!!开始遍历
      for (int i = 0; i < count; i++) {
          final View child = getVirtualChildAt(i);
          if (child == null) {
              childTop += measureNullChild(i);
          } else if (child.getVisibility() != GONE) {
              //LinearLayout中其子视图显示的宽和高由measure过程来决定的,因此measure过程的意义就是为layout过程提供视图显示范围的参考值
              final int childWidth = child.getMeasuredWidth();
              final int childHeight = child.getMeasuredHeight();
              //获取子View的LayoutParams
              final LinearLayout.LayoutParams lp =
                      (LinearLayout.LayoutParams) child.getLayoutParams();

              int gravity = lp.gravity;
              if (gravity < 0) {
                  gravity = minorGravity;
              }
              final int layoutDirection = getLayoutDirection();
              final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
              //依据不同的absoluteGravity计算childLeft位置
              switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
                  case Gravity.CENTER_HORIZONTAL:
                      childLeft = paddingLeft + ((childSpace - childWidth) / 2)
                              + lp.leftMargin - lp.rightMargin;
                      break;

                  case Gravity.RIGHT:
                      childLeft = childRight - childWidth - lp.rightMargin;
                      break;

                  case Gravity.LEFT:
                  default:
                      childLeft = paddingLeft + lp.leftMargin;
                      break;
              }

              if (hasDividerBeforeChildAt(i)) {
                  childTop += mDividerHeight;
              }

              childTop += lp.topMargin;
              //通过垂直排列计算调运child的layout设置child的位置
              setChildFrame(child, childLeft, childTop + getLocationOffset(child),
                      childWidth, childHeight);
              childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);

              i += getChildrenSkipCount(child, i);
          }
      }
  }

private void setChildFrame(View child, int left, int top, int width, int height) {
      child.layout(left, top, left + width, top + height);
  }

接下来看一下FrameLayout的onLayout方法

@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {

  //把父容器的位置参数传递进去
  layoutChildren(left, top, right, bottom, false /* no force left gravity */);
}

void layoutChildren(int left, int top, int right, int bottom,
                                boolean forceLeftGravity) {
  final int count = getChildCount();

  //以下四个值会影响到子View的布局参数
  //parentLeft由父容器的padding和Foreground决定
  final int parentLeft = getPaddingLeftWithForeground();
  //parentRight由父容器的width和padding和Foreground决定
  final int parentRight = right - left - getPaddingRightWithForeground();

  final int parentTop = getPaddingTopWithForeground();
  final int parentBottom = bottom - top - getPaddingBottomWithForeground();

  for (int i = 0; i < count; i++) {
      final View child = getChildAt(i);
      if (child.getVisibility() != GONE) {
          final LayoutParams lp = (LayoutParams) child.getLayoutParams();

          //获取子View的测量宽高
          final int width = child.getMeasuredWidth();
          final int height = child.getMeasuredHeight();

          int childLeft;
          int childTop;

          int gravity = lp.gravity;
          if (gravity == -1) {
              gravity = DEFAULT_CHILD_GRAVITY;
          }

          final int layoutDirection = getLayoutDirection();
          final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
          final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;

          //当子View设置了水平方向的layout_gravity属性时,根据不同的属性设置不同的childLeft
          //childLeft表示子View的 左上角坐标X值
          switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {

              /* 水平居中,由于子View要在水平中间的位置显示,因此,要先计算出以下:
               * (parentRight - parentLeft -width)/2 此时得出的是父容器减去子View宽度后的
               * 剩余空间的一半,那么再加上parentLeft后,就是子View初始左上角横坐标(此时正好位于中间位置),
               * 假如子View还受到margin约束,由于leftMargin使子View右偏而rightMargin使子View左偏,所以最后
               * 是 +leftMargin -rightMargin .
               */
              case Gravity.CENTER_HORIZONTAL:
                  childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
                  lp.leftMargin - lp.rightMargin;
                  break;

              //水平居右,子View左上角横坐标等于 parentRight 减去子View的测量宽度 减去 margin
              case Gravity.RIGHT:
                  if (!forceLeftGravity) {
                      childLeft = parentRight - width - lp.rightMargin;
                      break;
                  }

              //如果没设置水平方向的layout_gravity,那么它默认是水平居左
              //水平居左,子View的左上角横坐标等于 parentLeft 加上子View的magin值
              case Gravity.LEFT:
              default:
                  childLeft = parentLeft + lp.leftMargin;
          }

          //当子View设置了竖直方向的layout_gravity时,根据不同的属性设置同的childTop
          //childTop表示子View的 左上角坐标的Y值
          //分析方法同上
          switch (verticalGravity) {
              case Gravity.TOP:
                  childTop = parentTop + lp.topMargin;
                  break;
              case Gravity.CENTER_VERTICAL:
                  childTop = parentTop + (parentBottom - parentTop - height) / 2 +
                  lp.topMargin - lp.bottomMargin;
                  break;
              case Gravity.BOTTOM:
                  childTop = parentBottom - height - lp.bottomMargin;
                  break;
              default:
                  childTop = parentTop + lp.topMargin;
          }

          //对子元素进行布局,左上角坐标为(childLeft,childTop),右下角坐标为(childLeft+width,childTop+height)
          child.layout(childLeft, childTop, childLeft + width, childTop + height);
      }
  }
}

文字总结:

  • 确定一个view位置只需要知道左上标和宽高就可以确定,所以DecorView调用performLayout并把自己的左上标(0,0)和宽高传递给了layout()方法,layout把传进来的信息进行保存,并调用onLayout()方法--ViewGroup控件在onLayout中遍历所有的子view,并把所有的子view找到左上标,就是确定top和left的位置,然后调用子view的layout确定位置,就此结束,此不像onMeasure还要往上传。

Draw

从performDraw()看起:

private void performTraversals() {
     ...
     performDraw();
     ...
}

private void performDraw() {
      final boolean fullRedrawNeeded = mFullRedrawNeeded || mReportNextDraw;
       try {
           //传一个是否需要全部绘制的参数到darw()方法
           boolean canUseAsync = draw(fullRedrawNeeded);
        }finally {
           mIsDrawing = false;
           Trace.traceEnd(Trace.TRACE_TAG_VIEW);
       }
   }

//此方法在ViewRootImpl类中
private void draw(boolean fullRedrawNeeded) {
   ...
   //获取mDirty,该值表示需要重绘的区域
    final Rect dirty = mDirty;
       if (mSurfaceHolder != null) {
           // The app owns the surface, we won't draw.
           dirty.setEmpty();
           if (animating && mScroller != null) {
               mScroller.abortAnimation();
           }
           return false;
       }

       //绘制区域设置为全屏
       if (fullRedrawNeeded) {
           dirty.set(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
       }

   //drawSoftware方法中调用View.draw()
   if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
               return;
       }
}

private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
           boolean scalingRequired, Rect dirty) {
       // Draw with software renderer.
       final Canvas canvas;
       final int left = dirty.left;
       final int top = dirty.top;
       final int right = dirty.right;
       final int bottom = dirty.bottom;
        //锁定canvas区域,由dirty区域决定
       canvas = mSurface.lockCanvas(dirty);
       // ... ...
       mView.draw(canvas);
   }

public void draw(Canvas canvas) {
      final int privateFlags = mPrivateFlags;
      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 绘制view的内容
       *      4. Draw children 绘制子view的内容
       *      5. If necessary, draw the fading edges and restore layers 绘制View的褪色的边缘,类似于阴影效果
       *      6. Draw decorations (scrollbars for instance) 绘制View的装饰
       */

      // Step 1, draw the background, if needed
      int saveCount;
     //此方法先判断是否由背景色,然后拿到layout确定的坐标值,然后移动画布canvas到坐标的地方。
      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) {
          // Step 3, draw the content
          onDraw(canvas);//空方法,由控件继承实现,画里面的内容。

          // Step 4, draw the children
          dispatchDraw(canvas); //绘制子view,是个空方法,不过在viewGroup中实现:主要是遍历所有子view然后判断是否有缓存然后在调用darw()方法

          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);

          if (debugDraw()) {
              debugDrawFocus(canvas);
          }

          // we're done...
          return;
      }

draw的总结

  • 对于ViewGoup来说是先绘制自己的背景,然后调用onDraw方法遍历所有子view,并给子view调用draw()。对于View来说,在draw先绘制背景,然后调用onDraw方法借助传进来的Canvas类来绘制内容。
  • View默认不会绘制任何内容,真正的绘制都需要自己在子类中实现。

View的requestLayout、invalidate与postInvalidate

先总结,再详细分析

  • requestLayout:子view调用requestLayout,会标记当前view和父容器,同时逐层向上提交,直到viewRootImpl处理该事件,ViewRootImpl调用三大流程,从measure开始对含有标记位的view和子view进行测量、布局、绘制
  • invalidate: 子view调用invalidate,会标记该view,同时不断地向父容器请求刷新,父容器经过计算得出自己需要重绘的地方,直到调用viewRootImpl中,最终触发performTraversals方法,不过并不会调用measure、layout方法进行测量、布局,只会调用draw进行重绘制。
  • postinvalidate:是在子线程中使用,主要是通过Handler切换到主线程调用invalidate。

requestLayout

// View类:
//首先先判断当前View树是否正在布局流程,接着为当前子View设置标记位
//接着调用mParent.requestLayout方法
@CallSuper
public void requestLayout() {
  if (mMeasureCache != null) mMeasureCache.clear();

  if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {
      // Only trigger request-during-layout logic if this is the view requesting it,
      // not the views in its parent hierarchy
      ViewRootImpl viewRoot = getViewRootImpl();
      if (viewRoot != null && viewRoot.isInLayout()) {
          if (!viewRoot.requestLayoutDuringLayout(this)) {
              return;
          }
      }
      mAttachInfo.mViewRequestingLayout = this;
  }

  //为当前view设置标记位 PFLAG_FORCE_LAYOUT
  mPrivateFlags |= PFLAG_FORCE_LAYOUT;
  mPrivateFlags |= PFLAG_INVALIDATED;

  if (mParent != null && !mParent.isLayoutRequested()) {
      //向父容器请求布局,一直到viewRootImpl的requestLayout
      mParent.requestLayout();
  }
  if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
      mAttachInfo.mViewRequestingLayout = null;
  }
}

invalidate

public void invalidate() {
  invalidate(true);
}
void invalidate(boolean invalidateCache) {
  invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
}
void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
      boolean fullInvalidate) {
  if (mGhostView != null) {
      mGhostView.invalidate(true);
      return;
  }

  //这里判断该子View是否可见或者是否处于动画中
  if (skipInvalidate()) {
      return;
  }

  //根据View的标记位来判断该子View是否需要重绘,假如View没有任何变化,那么就不需要重绘
  if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)
          || (invalidateCache && (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID)
          || (mPrivateFlags & PFLAG_INVALIDATED) != PFLAG_INVALIDATED
          || (fullInvalidate && isOpaque() != mLastIsOpaque)) {
      if (fullInvalidate) {
          mLastIsOpaque = isOpaque();
          mPrivateFlags &= ~PFLAG_DRAWN;
      }

      //设置PFLAG_DIRTY标记位
      mPrivateFlags |= PFLAG_DIRTY;

      if (invalidateCache) {
          mPrivateFlags |= PFLAG_INVALIDATED;
          mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
      }

      // Propagate the damage rectangle to the parent view.
      //把需要重绘的区域传递给父容器
      final AttachInfo ai = mAttachInfo;
      final ViewParent p = mParent;
      if (p != null && ai != null && l < r && t < b) {
          final Rect damage = ai.mTmpInvalRect;
          damage.set(l, t, r, b);
          //调用父容器的方法,向上传递事件
          p.invalidateChild(this, damage);
      }
      ...
  }
}

接下来调用viewGroup的invalidateChild()

public final void invalidateChild(View child, final Rect dirty) {
      ViewParent parent = this;
      final AttachInfo attachInfo = mAttachInfo;
      ......
      do {
          ......
          //循环层层上级调运,直到ViewRootImpl会返回null
          parent = parent.invalidateChildInParent(location, dirty);
          ......
      } while (parent != null);
  }
#ViewRootImpl
@Override
  public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
      ......
      //View调运invalidate最终层层上传到ViewRootImpl后最终触发了该方法
      scheduleTraversals();
      ......
      return null;
  }

postInvalidate

public void postInvalidate() {
  postInvalidateDelayed(0);
}

public void postInvalidateDelayed(long delayMilliseconds) {
 
  // 只有view加载后才能调用
  final AttachInfo attachInfo = mAttachInfo;
  if (attachInfo != null) {
    //调用ViewRootImpl的dispatchInvalidateDelayed方法
    attachInfo.mViewRootImpl.dispatchInvalidateDelayed(this, delayMilliseconds);
  }
}
public void dispatchInvalidateDelayed(View view, long delayMilliseconds) {
      Message msg = mHandler.obtainMessage(MSG_INVALIDATE, view);
      mHandler.sendMessageDelayed(msg, delayMilliseconds);
  }


final ViewRootHandler mHandler = new ViewRootHandler();

final class ViewRootHandler extends Handler {
      @Override
      public String getMessageName(Message message) {
          ....
      }

      @Override
      public void handleMessage(Message msg) {
          switch (msg.what) {
          case MSG_INVALIDATE:
              ((View) msg.obj).invalidate();
              break;
          ...
      }
  }
}

相关文章

网友评论

    本文标题:安卓源码-view的绘制流程

    本文链接:https://www.haomeiwen.com/subject/gdkqvhtx.html