Android View的测量、布局、绘制

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

  private void performTraversals() {
        //执行测量
        performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
        //执行布局.
        performLayout(lp, mWidth, mHeight);
        //执行绘制
        performDraw();
  }

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两部分内容。

  public static int makeMeasureSpec(int size,int mode) {
        if (sUseBrokenMakeMeasureSpec) {
            return size + mode;
        } else {
            return (size & ~MODE_MASK) | (mode & MODE_MASK);
        }
    }

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

 public static int getMode(int measureSpec) {
      return (measureSpec & MODE_MASK);
  }


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

 public static int getSize(int measureSpec) {
       return (measureSpec & ~MODE_MASK);
  }

3. View的测量过程 performMeasure方法

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

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

       WRAP_CONTENT对应MeasureSpec.AT_MOST

 childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
 childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
 performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

private static int getRootMeasureSpec(int windowSize, int rootDimension) {
           int measureSpec;
           switch (rootDimension) {
           case ViewGroup.LayoutParams.MATCH_PARENT:
               // Window can't resize. Force root view to be windowSize.
               measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
               break;
           case ViewGroup.LayoutParams.WRAP_CONTENT:
               // Window can resize. Set max size for root view.
               measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
               break;
           default:
               // Window wants to be an exact size. Force root view to be that size.
               measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
               break;
           }
           return measureSpec;
       }

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

    private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
        if (mView == null) {
            return;
        }
        mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

 View.Java中

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

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

  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
        int count = getChildCount();
        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            if (mMeasureAllChildren || child.getVisibility() != GONE) {
                measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
            }
        }

  }

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

 protected void measureChildWithMargins(View child,
          int parentWidthMeasureSpec, int widthUsed,
          int parentHeightMeasureSpec, int heightUsed) {
      final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
       //计算得到childView的MeasureSpec
      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);
      //调用子View的Measure方法,继续遍历
      child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
  }

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

4.ViewGroup performLayout方法执行流程。

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

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

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

@Override
public final void layout(int l, int t, int r, int b) {
    if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) {
        if (mTransition != null) {
            mTransition.layoutChange(this);
        }
        //调用父类View的layout方法
        super.layout(l, t, r, b);
    } else {
        mLayoutCalledWhileSuppressed = true;
    }
}
View#layout
public void layout(int l, int t, int r, int b) {

    //设置界面大小left,top,right,bottom 的值
    boolean changed = isLayoutModeOptical(mParent) ?
                    setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);

    //如果大小发生了变化,或者需要重新layout,则进入到onLayout的逻辑
    if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
        //调用onLayout
        onLayout(changed, l, t, r, b);

        //调用OnLayoutChangeListener.onLayoutChange
        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnLayoutChangeListeners != null) {
            ArrayList<OnLayoutChangeListener> listenersCopy =
                    (ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
            int numListeners = listenersCopy.size();
            for (int i = 0; i < numListeners; ++i) {
                listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
            }
        }
     }
}

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

protected boolean setFrame(int left, int top, int right, int bottom) {
        boolean changed = false;
        int oldWidth = mRight - mLeft;
        int oldHeight = mBottom - mTop;
        int newWidth = right - left;
        int newHeight = bottom - top;

        boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);
         if (sizeChanged) {
            sizeChange(newWidth, newHeight, oldWidth, oldHeight);
        }
        mLeft = left;
        mTop = top;
        mRight = right;
        mBottom = bottom;
}
private void sizeChange(int newWidth, int newHeight, int oldWidth, int oldHeight) {
       onSizeChanged(newWidth, newHeight, oldWidth, oldHeight);
}

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

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

protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    layoutChildren(left, top, right, bottom, false);
}
//通过调用layoutChildren来确定View在屏幕中的位置,
void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
    final int count = getChildCount();
      for (int i = 0; i < count; i++) {
          final View child = getChildAt(i);
          final LayoutParams lp = (LayoutParams) child.getLayoutParams();
          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;

          //计算得到距离左侧的距离
        switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
            case Gravity.CENTER_HORIZONTAL:
                childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
                lp.leftMargin - lp.rightMargin;
                break;
            case Gravity.RIGHT:
                if (!forceLeftGravity) {
                    childLeft = parentRight - width - lp.rightMargin;
                    break;
                }
            case Gravity.LEFT:
            default:
                childLeft = parentLeft + lp.leftMargin;
        }
        //计算得到距离顶部的距离
       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;
          }
          //调用child的layout方法,继续上面的循环遍历
          child.layout(childLeft, childTop, childLeft + width, childTop + height);
       }
}

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

ViewRootImpl中

private void performDraw() {
      .....
     //fullRedrawNeeded 这个用来判断是否需要绘制全部视图
      boolean canUseAsync = draw(fullRedrawNeeded);

 }
private boolean draw(boolean fullRedrawNeeded) {

       Surface surface = mSurface;
       //mDirty表示需要绘制的区域
       final Rect dirty = mDirty;
       //如果fullRedrawNeeded为true,把dirty大小设置为整个屏幕
      if (fullRedrawNeeded) {
            dirty.set(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
       }
       .....
       //调用drawSoftware
        if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset,
                 scalingRequired, dirty, surfaceInsets)) {
             return false;
         }
 }
 private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
            boolean scalingRequired, Rect dirty, Rect surfaceInsets) {

    // Draw with software renderer.
      final Canvas canvas;
      //锁定canvas,画布大小由dirty决定
      canvas = mSurface.lockCanvas(dirty);
      //设置画布支持的密度
      //mDensity = context.getResources().getDisplayMetrics().densityDpi;
      canvas.setDensity(mDensity);
      //调用DecorView
      mView.draw(canvas);
     //提交已经绘制好的内容
     surface.unlockCanvasAndPost(canvas);
 }

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

@Override
public void draw(Canvas canvas) {
    super.draw(canvas);

    if (mMenuBackground != null) {
        mMenuBackground.draw(canvas);
    }
}
View#draw
public void draw(Canvas canvas) {
     /*
     *  绘制的6个步骤
     *  1. Draw the background. 绘制View的背景
     *  2. If necessary, save the canvas' layers to prepare for fading
           如果有必要,会保存canvas的图层信息,可跳过
     *  3. Draw view's content. 绘制View的内容
     *  4. Draw children.绘制子View
     *  5. If necessary, draw the fading edges and restore layers
     *     如果有必要,绘制边缘并保存图层,可跳过
     *  6. Draw decorations (scrollbars for instance) 绘制View的装饰(例如:滚动条)
     */
    // Step 1, draw the background, if needed
        int saveCount;
        //1.如果需要,则绘制背景
        if (!dirtyOpaque) {
            drawBackground(canvas);
        }
        // skip step 2 & 5 if possible (common case)
        //如果可以跳过第2和第5步
        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
            //第三步,绘制内容
            if (!dirtyOpaque){
                onDraw(canvas);
            }
            // Step 4, draw the children
            //第四步,绘制子View
            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。这样就跳过了第2和第5步的绘制。
            return;
        }
}

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

 private void drawBackground(Canvas canvas) {
    final Drawable background = mBackground;
    //如果没有background,则不进行绘制
     if (background == null) {
            return;
      }
     //如果有偏移量,先偏移画布,然后再draw
    final int scrollX = mScrollX;
    final int scrollY = mScrollY;
    if ((scrollX | scrollY) == 0) {
        background.draw(canvas);
    } else {
        canvas.translate(scrollX, scrollY);
        background.draw(canvas);
        //画完之后,将画布移回去
        canvas.translate(-scrollX, -scrollY);
    }
 }

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

  @Override
public void onDraw(Canvas c) {
    super.onDraw(c);

    mBackgroundFallback.draw(this, mContentRoot, c, mWindow.mContentParent,
            mStatusColorViewState.view, mNavigationColorViewState.view);
}

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

 protected void onDraw(Canvas canvas) {

 }

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

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

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

public void onDrawForeground(Canvas canvas) {
     onDrawScrollIndicators(canvas);
     onDrawScrollBars(canvas);
}

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

 public Canvas lockCanvas(Rect inOutDirty){
        synchronized (mLock) {
            mLockedObject = nativeLockCanvas(mNativeObject, mCanvas, inOutDirty);
            return mCanvas;
        }
 }

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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

niuyongzhi

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值