• Android View的测量、布局、绘制


    1.我们知道在ViewRootImpl中的performTraversals方法中,会执行view的测量、布局、绘制。
     那么具体的执行流程是哪样的,是怎么调用到View中的onMeasure、onLayout、onDraw方法的。

    1.   private void performTraversals() {
    2.         //执行测量
    3.         performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
    4.         //执行布局.
    5.         performLayout(lp, mWidth, mHeight);
    6.         //执行绘制
    7.         performDraw();
    8.   }

    2.在看View测量之前,先看MeasureSpec类中定义的View mode相关的知识。

     view的mode有三种:
         1.UNSPECIFIED:未指定,不明确。
         ViewGroup没有对子View施加任何约束,子View可以是任意大小。这种mode使用的比较少。
         public static final int UNSPECIFIED = 0 << MODE_SHIFT;
         2.EXACTLY:准确的
         该View必须使用父ViewGroup指定尺寸,对应Match_Parent 或者具体的数值如:36dp
         public static final int EXACTLY     = 1 << MODE_SHIFT;
         3.AT_MOST:最多
         该View的大小最大是父ViewGroup给定的尺寸,对应warp_content。
         public static final int AT_MOST     = 2 << MODE_SHIFT;

      再看makeMeasureSpec方法。通过这个方法计算得到的measureSpec。是包含Mode和size两部分内容。

    1.   public static int makeMeasureSpec(int size,int mode) {
    2.         if (sUseBrokenMakeMeasureSpec) {
    3.             return size + mode;
    4.         } else {
    5.             return (size & ~MODE_MASK) | (mode & MODE_MASK);
    6.         }
    7.     }

      通过getMode可以获取View使用的mode。

    1.  public static int getMode(int measureSpec) {
    2.       return (measureSpec & MODE_MASK);
    3.   }


        通过getSize可以获取View的大小。   

    1. public static int getSize(int measureSpec) {
    2.       return (measureSpec & ~MODE_MASK);
    3.   }

    3. View的测量过程 performMeasure方法

        在ViewRootImpl中childWidthMeasureSpec屏幕的宽,childHeightMeasureSpec屏幕的高。

     getRootMeasureSpec这个方法,就是根据穿过来的size大小和view的模式,来计算得到的measureSpec值。
        也能看出MATCH_PARENT对应 MeasureSpec.EXACTLY。

           WRAP_CONTENT对应MeasureSpec.AT_MOST

    1. childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
    2. childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
    3. performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
    4. private static int getRootMeasureSpec(int windowSize, int rootDimension) {
    5. int measureSpec;
    6. switch (rootDimension) {
    7. case ViewGroup.LayoutParams.MATCH_PARENT:
    8. // Window can't resize. Force root view to be windowSize.
    9. measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
    10. break;
    11. case ViewGroup.LayoutParams.WRAP_CONTENT:
    12. // Window can resize. Set max size for root view.
    13. measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
    14. break;
    15. default:
    16. // Window wants to be an exact size. Force root view to be that size.
    17. measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
    18. break;
    19. }
    20. return measureSpec;
    21. }

      mView 就是DecorView。DecoView是继承自FrameLayout-->ViewGruop-->View
        mView.measure()调用的是View的measure方法

    1. private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
    2. if (mView == null) {
    3. return;
    4. }
    5. mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    6. }

     View.Java中

    1. public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
    2. //判断标志位,是否需要重新布局
    3. final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
    4. if (forceLayout || needsLayout) {
    5. //如果forceLayout为true则cacheIndex =-1;如果为false,则从缓存中获取
    6. int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
    7. //cacheIndex<0说明需要重新测量
    8. if (cacheIndex < 0 || sIgnoreMeasureCache) {
    9. onMeasure(widthMeasureSpec, heightMeasureSpec);
    10. } else {
    11. //从缓存中获取
    12. long value = mMeasureCache.valueAt(cacheIndex);
    13. //将从缓存中获取的数据设置进来
    14. setMeasuredDimensionRaw((int) (value >> 32), (int) value);
    15. }
    16. }
    17. //将测量后的宽高,缓存起来
    18. mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
    19. (long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
    20. }
    1. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    2. //将计算后的宽高,赋值给全局变量
    3. setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
    4. getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    5. }
    6. public static int getDefaultSize(int size, int measureSpec) {
    7. int result = size;
    8. int specMode = MeasureSpec.getMode(measureSpec);
    9. int specSize = MeasureSpec.getSize(measureSpec);
    10. switch (specMode) {
    11. case MeasureSpec.UNSPECIFIED:
    12. result = size;
    13. break;
    14. case MeasureSpec.AT_MOST:
    15. case MeasureSpec.EXACTLY:
    16. result = specSize;
    17. break;
    18. }
    19. return result;
    20. }

    在View中,调用oMeasure方法,先调用子类的方法,也就是DecorView的onMeasure方法
      在DecorView中会回调父类FrameLayout方法
      在这个方法中,会不断遍历子View,进行测量

    1. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
    2. int count = getChildCount();
    3. for (int i = 0; i < count; i++) {
    4. final View child = getChildAt(i);
    5. if (mMeasureAllChildren || child.getVisibility() != GONE) {
    6. measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
    7. }
    8. }
    9. }

     measureChildWithMargins方法中,通过  child.measure(childWidthMeasureSpec, childHeightMeasureSpec); 测量子View,如果子View是一个ViewGroup,继续上面的循环遍历,测量每一个View的宽高。

    1. protected void measureChildWithMargins(View child,
    2. int parentWidthMeasureSpec, int widthUsed,
    3. int parentHeightMeasureSpec, int heightUsed) {
    4. final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
    5. //计算得到childView的MeasureSpec
    6. final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
    7. mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
    8. + widthUsed, lp.width);
    9. final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
    10. mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
    11. + heightUsed, lp.height);
    12. //调用子View的Measure方法,继续遍历
    13. child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    14. }

     通过以上循环遍历,每个View的大小已经被测量出来了。但是在屏幕中摆放在什么位置,还得调用layout相关的方法。

    4.ViewGroup performLayout方法执行流程。

     host 就是DecorView
      host.getMeasuredWidth() host.getMeasuredHeight() 得到的是测量后的结果。

    1. private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,int desiredWindowHeight) {
    2. //host.getMeasuredWidth() host.getMeasuredHeight() 得到的是测量后的结果
    3. host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
    4. }

    ViewGroup中的layout方法是final修饰的,在子类中无法被复写。在这调用父类View的layout方法

    1. @Override
    2. public final void layout(int l, int t, int r, int b) {
    3. if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) {
    4. if (mTransition != null) {
    5. mTransition.layoutChange(this);
    6. }
    7. //调用父类View的layout方法
    8. super.layout(l, t, r, b);
    9. } else {
    10. mLayoutCalledWhileSuppressed = true;
    11. }
    12. }
    1. View#layout
    2. public void layout(int l, int t, int r, int b) {
    3. //设置界面大小left,top,right,bottom 的值
    4. boolean changed = isLayoutModeOptical(mParent) ?
    5. setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
    6. //如果大小发生了变化,或者需要重新layout,则进入到onLayout的逻辑
    7. if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
    8. //调用onLayout
    9. onLayout(changed, l, t, r, b);
    10. //调用OnLayoutChangeListener.onLayoutChange
    11. ListenerInfo li = mListenerInfo;
    12. if (li != null && li.mOnLayoutChangeListeners != null) {
    13. ArrayList<OnLayoutChangeListener> listenersCopy =
    14. (ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
    15. int numListeners = listenersCopy.size();
    16. for (int i = 0; i < numListeners; ++i) {
    17. listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
    18. }
    19. }
    20. }
    21. }

    设置界面大小,当大小改变时,会调用onSizeChanged方法

    1. protected boolean setFrame(int left, int top, int right, int bottom) {
    2. boolean changed = false;
    3. int oldWidth = mRight - mLeft;
    4. int oldHeight = mBottom - mTop;
    5. int newWidth = right - left;
    6. int newHeight = bottom - top;
    7. boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);
    8. if (sizeChanged) {
    9. sizeChange(newWidth, newHeight, oldWidth, oldHeight);
    10. }
    11. mLeft = left;
    12. mTop = top;
    13. mRight = right;
    14. mBottom = bottom;
    15. }
    16. private void sizeChange(int newWidth, int newHeight, int oldWidth, int oldHeight) {
    17. onSizeChanged(newWidth, newHeight, oldWidth, oldHeight);
    18. }

    在View中调用onLayout方法,其实是调用的子类的onLayout方法,看FrameLayout中。

    通过layoutChildren循环遍历,调用每一个ViewGroup的onLayout方法,来确定子View在布局中的位置。

    1. protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    2. layoutChildren(left, top, right, bottom, false);
    3. }
    4. //通过调用layoutChildren来确定View在屏幕中的位置,
    5. void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
    6. final int count = getChildCount();
    7. for (int i = 0; i < count; i++) {
    8. final View child = getChildAt(i);
    9. final LayoutParams lp = (LayoutParams) child.getLayoutParams();
    10. final int width = child.getMeasuredWidth();
    11. final int height = child.getMeasuredHeight();
    12. int childLeft;
    13. int childTop;
    14. int gravity = lp.gravity;
    15. if (gravity == -1) {
    16. gravity = DEFAULT_CHILD_GRAVITY;
    17. }
    18. final int layoutDirection = getLayoutDirection();
    19. final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
    20. final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
    21. //计算得到距离左侧的距离
    22. switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
    23. case Gravity.CENTER_HORIZONTAL:
    24. childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
    25. lp.leftMargin - lp.rightMargin;
    26. break;
    27. case Gravity.RIGHT:
    28. if (!forceLeftGravity) {
    29. childLeft = parentRight - width - lp.rightMargin;
    30. break;
    31. }
    32. case Gravity.LEFT:
    33. default:
    34. childLeft = parentLeft + lp.leftMargin;
    35. }
    36. //计算得到距离顶部的距离
    37. switch (verticalGravity) {
    38. case Gravity.TOP:
    39. childTop = parentTop + lp.topMargin;
    40. break;
    41. case Gravity.CENTER_VERTICAL:
    42. childTop = parentTop + (parentBottom - parentTop - height) / 2 +
    43. lp.topMargin - lp.bottomMargin;
    44. break;
    45. case Gravity.BOTTOM:
    46. childTop = parentBottom - height - lp.bottomMargin;
    47. break;
    48. default:
    49. childTop = parentTop + lp.topMargin;
    50. }
    51. //调用child的layout方法,继续上面的循环遍历
    52. child.layout(childLeft, childTop, childLeft + width, childTop + height);
    53. }
    54. }

    通过以上方法,确定了View在屏幕中的位置,接下来执行绘制流程onDraw方法。

    ViewRootImpl中

    1. private void performDraw() {
    2. .....
    3. //fullRedrawNeeded 这个用来判断是否需要绘制全部视图
    4. boolean canUseAsync = draw(fullRedrawNeeded);
    5. }
    1. private boolean draw(boolean fullRedrawNeeded) {
    2. Surface surface = mSurface;
    3. //mDirty表示需要绘制的区域
    4. final Rect dirty = mDirty;
    5. //如果fullRedrawNeeded为true,把dirty大小设置为整个屏幕
    6. if (fullRedrawNeeded) {
    7. dirty.set(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
    8. }
    9. .....
    10. //调用drawSoftware
    11. if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset,
    12. scalingRequired, dirty, surfaceInsets)) {
    13. return false;
    14. }
    15. }
    1. private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
    2. boolean scalingRequired, Rect dirty, Rect surfaceInsets) {
    3. // Draw with software renderer.
    4. final Canvas canvas;
    5. //锁定canvas,画布大小由dirty决定
    6. canvas = mSurface.lockCanvas(dirty);
    7. //设置画布支持的密度
    8. //mDensity = context.getResources().getDisplayMetrics().densityDpi;
    9. canvas.setDensity(mDensity);
    10. //调用DecorView
    11. mView.draw(canvas);
    12. //提交已经绘制好的内容
    13. surface.unlockCanvasAndPost(canvas);
    14. }

    mView.draw(canvas) 是真正的绘制,绘制到画布上。mView就是DecorView

    1. @Override
    2. public void draw(Canvas canvas) {
    3. super.draw(canvas);
    4. if (mMenuBackground != null) {
    5. mMenuBackground.draw(canvas);
    6. }
    7. }
    1. View#draw
    2. public void draw(Canvas canvas) {
    3. /*
    4. * 绘制的6个步骤
    5. * 1. Draw the background. 绘制View的背景
    6. * 2. If necessary, save the canvas' layers to prepare for fading
    7. 如果有必要,会保存canvas的图层信息,可跳过
    8. * 3. Draw view's content. 绘制View的内容
    9. * 4. Draw children.绘制子View
    10. * 5. If necessary, draw the fading edges and restore layers
    11. * 如果有必要,绘制边缘并保存图层,可跳过
    12. * 6. Draw decorations (scrollbars for instance) 绘制View的装饰(例如:滚动条)
    13. */
    14. // Step 1, draw the background, if needed
    15. int saveCount;
    16. //1.如果需要,则绘制背景
    17. if (!dirtyOpaque) {
    18. drawBackground(canvas);
    19. }
    20. // skip step 2 & 5 if possible (common case)
    21. //如果可以跳过第2和第5
    22. final int viewFlags = mViewFlags;
    23. boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
    24. boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
    25. if (!verticalEdges && !horizontalEdges) {
    26. // Step 3, draw the content
    27. //第三步,绘制内容
    28. if (!dirtyOpaque){
    29. onDraw(canvas);
    30. }
    31. // Step 4, draw the children
    32. //第四步,绘制子View
    33. dispatchDraw(canvas);
    34. // Step 6, draw decorations (foreground, scrollbars)
    35. onDrawForeground(canvas);
    36. // Step 7, draw the default focus highlight
    37. drawDefaultFocusHighlight(canvas);
    38. if (debugDraw()) {
    39. debugDrawFocus(canvas);
    40. }
    41. // we're done...
    42. //绘制完成,return。这样就跳过了第2和第5步的绘制。
    43. return;
    44. }
    45. }

    1) 绘制背景:drawBackground(canvas)
      绘制背景调用的是Drawable子类的draw(canvas)。这样就能够把背景绘制在画布上。

    1. private void drawBackground(Canvas canvas) {
    2. final Drawable background = mBackground;
    3. //如果没有background,则不进行绘制
    4. if (background == null) {
    5. return;
    6. }
    7. //如果有偏移量,先偏移画布,然后再draw
    8. final int scrollX = mScrollX;
    9. final int scrollY = mScrollY;
    10. if ((scrollX | scrollY) == 0) {
    11. background.draw(canvas);
    12. } else {
    13. canvas.translate(scrollX, scrollY);
    14. background.draw(canvas);
    15. //画完之后,将画布移回去
    16. canvas.translate(-scrollX, -scrollY);
    17. }
    18. }

    2)绘制内容:onDraw(canvas);
      先调用DecorView#onDraw

    1. @Override
    2. public void onDraw(Canvas c) {
    3. super.onDraw(c);
    4. mBackgroundFallback.draw(this, mContentRoot, c, mWindow.mContentParent,
    5. mStatusColorViewState.view, mNavigationColorViewState.view);
    6. }

    super.onDraw(c)这个调用的是View的onDraw.在View中这个是空实现。
    我们自定义View的时候,复写的就是这个方法。

    1. protected void onDraw(Canvas canvas) {
    2. }

    3)绘制子View:dispatchDraw(canvas);
    DecorView#dispatchDraw

    1. //遍历子View 进行绘制
    2. protected void dispatchDraw(Canvas canvas) {
    3. final int childrenCount = mChildrenCount;
    4. final View[] children = mChildren;
    5. for (int i = 0; i < childrenCount; i++) {
    6. more |= drawChild(canvas, transientChild, drawingTime);
    7. }
    8. }
    9. //drawChild绘制子View child.draw调用View的draw,继续上面的绘制流程
    10. protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
    11. return child.draw(canvas, this, drawingTime);
    12. }

    4)绘制装饰:onDrawForeground
     绘制装饰就是指View除了背景、内容、子View的其他部分,例如滚动条等。

    1. public void onDrawForeground(Canvas canvas) {
    2. onDrawScrollIndicators(canvas);
    3. onDrawScrollBars(canvas);
    4. }

    至此View的测量、布局、绘制流程已经梳理完成。绘制已经把View绘制在了画布canvas上。
    那么画在是如何显示在屏幕上的呢?
    看看 canvas = mSurface.lockCanvas(dirty);
    这是锁定了一个Canvas,并把Java层的mCanvas,传递到了Native层。

    1. public Canvas lockCanvas(Rect inOutDirty){
    2. synchronized (mLock) {
    3. mLockedObject = nativeLockCanvas(mNativeObject, mCanvas, inOutDirty);
    4. return mCanvas;
    5. }
    6. }

    而调用canvas.draw方法时,其实是调用的native方法。在native层会通过SkBitmap数据,这个是通过Skia绘制引擎实现的。
    private static native void nDrawColor(...)
    private static native void nDrawBitmap(...)
    在绘制完成后,java层会调用 surface.unlockCanvasAndPost(canvas);
    这个方法会通知Native层代码,将绘制好的Bitmap发送到一个队列里,然后显示到屏幕上。

  • 相关阅读:
    最近公共祖先(朴素法、倍增法、(递归法))
    谷歌竞价排名,谷歌关键词优化,谷歌搜索引擎优化-大舍传媒
    你需要知道的 12 个常用的 JavaScript 函数
    salesforce是什么
    day41 jdk8新特性Stream流 数据库安装
    今天告诉你界面控件DevExpress WinForms为何弃用经典视觉样式
    带你一文理解JS数组
    【编译原理】-- 第二章(二)(短语、简单短语、句柄、文法二义性、语法树、例题)
    【owt】owt-client-native-p2p-e2e-test vs2017构建 6:修改脚本自动生成vs工程
    3.1 使用点对点信道的数据链路层
  • 原文地址:https://blog.csdn.net/niuyongzhi/article/details/126898060