GVKun编程网logo

从 Swift 中的闭包返回多个 View 对象(swift 闭包作为函数入参)

3

对于从Swift中的闭包返回多个View对象感兴趣的读者,本文将提供您所需要的所有信息,我们将详细讲解swift闭包作为函数入参,并且为您提供关于ABAPwebdynpro的viewnavigatio

对于从 Swift 中的闭包返回多个 View 对象感兴趣的读者,本文将提供您所需要的所有信息,我们将详细讲解swift 闭包作为函数入参,并且为您提供关于ABAP webdynpro 的 view navigation 和 WebUI 的 view navigation、Android View 源码解析 (三) - View 的绘制过程、Android View 的绘制流程之 Layout 和 Draw 过程详解 (二) Android View 绘制流程之 DecorView 与 ViewRootImplAndroid View 的绘制流程之 Measure 过程详解 (一)Android View 的绘制流程之 Layout 和 Draw 过程详解 (二)Android View 的事件分发原理解析Android 自定义 View 详解Android View 的绘制流程之 Measure 过程详解 (一)、Android View 的绘制流程之 Measure 过程详解 (一) Android View 绘制流程之 DecorView 与 ViewRootImplAndroid View 的绘制流程之 Measure 过程详解 (一)Android View 的绘制流程之 Layout 和 Draw 过程详解 (二)Android View 的事件分发原理解析Android 自定义 View 详解Android View 绘制流程之 DecorView 与 ViewRootImp的宝贵知识。

本文目录一览:

从 Swift 中的闭包返回多个 View 对象(swift 闭包作为函数入参)

从 Swift 中的闭包返回多个 View 对象(swift 闭包作为函数入参)

如何解决从 Swift 中的闭包返回多个 View 对象

我经常看到这样的代码:

vstack {
  Text("A")
  Text("B")
}

通过查看 swift 教程,我知道这是将闭包指定为函数最后一个参数的简写。但是,如果未指定 return 关键字,闭包将隐式返回内部表达式的结果。

在本例中,有两个 Text 对象。它们是作为元组还是列表返回?我知道它有效,但我不清楚,关闭返回的究竟是什么。

解决方法

在 Swift Evolution 255 中引入了省略 return 语句的初始能力 - 此更改仅允许单个返回值。事实上,这个提议中考虑了返回多个值的能力并被丢弃了。

您所询问的结构最初是由 Apple 作为支持 SwiftUI 的私有实现引入的,但现在已在 Swift Evolution 289 - Function Builders 中正式采用。

通过参考该文档的 introduction 部分,您可以看到这些值确实作为元组返回。

// Original source code:
@TupleBuilder
func build() -> (Int,Int,Int) {
  1
  2
  3
}

// This code is interpreted exactly as if it were this code:
func build() -> (Int,Int) {
  let _a = TupleBuilder.buildExpression(1)
  let _b = TupleBuilder.buildExpression(2)
  let _c = TupleBuilder.buildExpression(3)
  return TupleBuilder.buildBlock(_a,_b,_c)
}
,

当我们看到这段代码时:

VStack {
  Text("A")
  Text("B")
}

不清楚返回什么,除非我们使用VStack,然后SwiftUI知道我们想要什么,代码可能是这样的:

HStack {
  Text("A")
  Text("B")
}

这里也不清楚我们想要什么,除非我们对 SwiftUI 说我们想要 HStack,但是如果你这样使用:

CustomView {
  Text("A")
  Text("B")
}

SwiftUI 始终将其返回为 VStack 内部内容!同样在我所说的所有这些情况下,Swift 都与 @ViewBuilder 合作来实现这一目标。它们是元组。

enter image description here

,

SwiftUI 会自动返回一个类型为 TupleView 的两个 Text。 Swift 的 resultBuilder 很神奇,它是 SE0289:https://github.com/apple/swift-evolution/blob/main/proposals/0289-result-builders.md

ABAP webdynpro 的 view navigation 和 WebUI 的 view navigation

虽然 ABAP webdynpro 已经不是 SAP 未来主流的 UX 技术,但是在很多老的模块里使用还是很广泛。

在 [SAP help] 里有关于这个应用的详细介绍:

而 ABAP webdynpro 的技术很多地方实现都和 WebUI 非常类似.
下图是 ABAP webdynpro 的 navigate 方法,实际操作是 delegate 到 view 的 view manager:

WebUI 类似:

Webdynprode navigate 方法是把和 navigation 相关的数据填到一个 line structure 里,再 insert 到一个叫做 navigation queue 的 internal table:

WebUI 同样有这个 navigation queue:

Webdynpro 的 navigation queue 在此处逐一被执行:

WebUI 的 navigation queue 在此处逐一被执行:

要获取更多 Jerry 的原创文章,请关注公众号 "汪子熙":

本文分享 CSDN - 汪子熙。
如有侵权,请联系 support@oschina.cn 删除。
本文参与 “OSC 源创计划”,欢迎正在阅读的你也加入,一起分享。

Android View 源码解析 (三) - View 的绘制过程

Android View 源码解析 (三) - View 的绘制过程

OSC 请你来轰趴啦!1028 苏州源创会,一起寻宝 AI 时代

Android View 源码解析 (一) - setContentView Android View 源码解析 (二) - LayoutInflater

现在开始分析 View 的绘制机制

View 的测量 布局 绘制过程

测量之前的事情

View 的整个绘制流程是开始于 ViewRootImpl 类的 performTraversals 方法 (1k 行) 根据相关设置来觉得十分要重新执行相关功能

  private void performTraversals() {
    // cache mView since it is used so much below...
    final View host = mView;
    ...
    int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
    int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
    ...
    //measure
    mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    ...
    //layout
    mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
    ...
    //draw
    mView.draw(canvas);
    ...
  }
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;
      ...
      }
      return measureSpec;
  }

View 绘制流程图如下

measure 源码分析

结论:

  • measure 的过程就是父 View 向子 View 递归调用 view.measure 方法 (measure 中回调 onMeasure 方法) 的过程

  • measure 方法是 final 的 只能重载 onMeasure 方法

  • 最顶层的 DocerView 的 MeasureSpec 由 ViewRootImpl 的 getRootMeasureSpec 方法提供 LayoutParams 的参数为 MATCH_PARENT specMode 是 EXACTLY,specSize 为物理屏幕大小

  • 只要是 ViewGroup 的子类就必须要求 LayoutParams 继承子 MarginLayoutParams 否则无法使用 layout_margin 参数

  • View 的 getMeasuredWidth () 和 getMeasuredHeight () 方法来获取 View 测量的宽高,要必须保证这两个方法在 onMeasure 流程之后被调用才能返回有效值。

View measure过程

/**
 * <p>
 * This is called to find out how big a view should be. The parent supplies constraint information in the width and height parameters.
 * </p>
 *
 * <p>
 * The actual measurement work of a view is performed in
 * {@link #onMeasure(int, int)}, called by this method. Therefore, only
 * {@link #onMeasure(int, int)} can and must be overridden by subclasses.
 * </p>
 *
 *
 * @param widthMeasureSpec Horizontal space requirements as imposed by the
 *        parent
 * @param heightMeasureSpec Vertical space requirements as imposed by the
 *        parent
 *
 * @see #onMeasure(int, int)
 */
 //没舍得删这些注释  感觉重要的事情都说了   为了计算整个View树的实际大小 设置实际的高和宽 每个子View都是根据父视图和自身决定实际宽高的 在onMeasure()方法中进行实际测量.传入widthMeasureSpec和heightMeasureSpec参数来表示了父View的规格 不但传入了模式 还传入了size 而对于DecorView来说 传入的模式一般为EXACTLY模式 size对应屏幕的宽高. 所以说子View的大小是父子View共同决定的
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {

               // measure ourselves, this should set the measured dimension flag back
               onMeasure(widthMeasureSpec, heightMeasureSpec);
   }

MeasureSpec 内部类

MeasureSpec 是 View 的内部类 int 型,由高 2 位规格模式 specMode 和低 30 位具体尺寸 specSize 组成 其中 specMode 只有三种

  • MeasureSpec.EXACTLY// 确定模式,父 View 希望子 View 的大小是确定的,由 specSize 决定;
  • MeasureSpec.AT_MOST// 最多模式,父 View 希望子 View 的大小最多是 specSize 指定的值;
  • MeasureSpec.UNSPECIFIED// 未指定模式,父 View 完全依据子 View 的设计值来决定;

onMeasure () 方法

    /**
     * <p>
     * Measure the view and its content to determine the measured width and the
     * measured height. This method is invoked by {@link #measure(int, int)} and
     * should be overridden by subclasses to provide accurate and efficient
     * measurement of their contents.
     * </p>
     *
     * <p>
     * <strong>CONTRACT:</strong> When overriding this method, you
     * <em>must</em> call {@link #setMeasuredDimension(int, int)} to store the
     * measured width and height of this view. Failure to do so will trigger an
     * <code>IllegalStateException</code>, thrown by
     * {@link #measure(int, int)}. Calling the superclass''
     * {@link #onMeasure(int, int)} is a valid use.
     * </p>
     *
     * <p>
     * The base class implementation of measure defaults to the background size,
     * unless a larger size is allowed by the MeasureSpec. Subclasses should
     * override {@link #onMeasure(int, int)} to provide better measurements of
     * their content.
     * </p>
     *
     * <p>
     * If this method is overridden, it is the subclass''s responsibility to make
     * sure the measured height and width are at least the view''s minimum height
     * and width ({@link #getSuggestedMinimumHeight()} and
     * {@link #getSuggestedMinimumWidth()}).
     * </p>
     *
     * @param widthMeasureSpec horizontal space requirements as imposed by the parent.
     *                         The requirements are encoded with
     *                         {@link android.view.View.MeasureSpec}.
     * @param heightMeasureSpec vertical space requirements as imposed by the parent.
     *                         The requirements are encoded with
     *                         {@link android.view.View.MeasureSpec}.
     *
     * @see #getMeasuredWidth()
     * @see #getMeasuredHeight()
     * @see #setMeasuredDimension(int, int)
     * @see #getSuggestedMinimumHeight()
     * @see #getSuggestedMinimumWidth()
     * @see android.view.View.MeasureSpec#getMode(int)
     * @see android.view.View.MeasureSpec#getSize(int)
     */
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }

getDefaultSize 方法相关

  public static int getDefaultSize(int size, int measureSpec) {
    int result = size;
    //通过measureSpec得到mode和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的Background尺寸和View的minXXX共同决定
  protected int getSuggestedMinimumHeight() {
      return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());

  }
  protected int getSuggestedMinimumWidth() {
      return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
  }

setMeasuredDimension 方法 对 View 的成员变量 measuredWidth 和 measuredHeight 变量赋值 也就是说该方法最终决定了 View 的大小

  protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
      boolean optical = isLayoutModeOptical(this);
      if (optical != isLayoutModeOptical(mParent)) {
          Insets insets = getOpticalInsets();
          int opticalWidth  = insets.left + insets.right;
          int opticalHeight = insets.top  + insets.bottom;

          measuredWidth  += optical ? opticalWidth  : -opticalWidth;
          measuredHeight += optical ? opticalHeight : -opticalHeight;
      }
      setMeasuredDimensionRaw(measuredWidth, measuredHeight);
  }

  public boolean isLayoutRequested() {
    return (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
  }

  private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
    mMeasuredWidth = measuredWidth;
    mMeasuredHeight = measuredHeight;

    mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
  }

至此一次最基础的 View 的 measure 过程就完成了 但是由于 View 可以嵌套 所以 measure 是递归传递的所以 ViewGroup 中需要对其子类进行 measure 过程 measureChildren 方法实质为循环调用 measureChild 方法

而 measureChild 和 measureChildWithMargins 的区别是后者将 margin 和 padding 也作为了子视图的大小

一下分析 measureChildWithMargins 方法

  protected void measureChildWithMargins(View child,
          int parentWidthMeasureSpec, int widthUsed,
          int parentHeightMeasureSpec, int heightUsed) {
      //获取当前子视图的LayoutParams
      final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
      //设定子View的测量规格
      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的继续调用
      child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
  }

  //在getChildMeasureSpec中通过父View和本身的模式共同决定当前View的size
  public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
        //获取当前父View的mode和size
        int specMode = MeasureSpec.getMode(spec);
        int specSize = MeasureSpec.getSize(spec);
        //获取父View的的剩余大小
        int size = Math.max(0, specSize - padding);
        //定义结果变量
        int resultSize = 0;
        int resultMode = 0;
        //根据对应的mode做处理
        //通过父View和本身的模式共同决定当前View的size
        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) {
                // Child wants to be our size. So be it.
                resultSize = size;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size. It can''t be
                // bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        // Parent has imposed a maximum size on us
        case MeasureSpec.AT_MOST:
            if (childDimension >= 0) {
                // Child wants a specific size... so be it
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size, but our size is not fixed.
                // Constrain child to not be bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size. It can''t be
                // bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        // Parent asked to see how big we want to be
        case MeasureSpec.UNSPECIFIED:
            if (childDimension >= 0) {
                // Child wants a specific size... let him have it
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size... find out how big it should
                // be
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size.... find out how
                // big it should be
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            }
            break;
        }
        //将size和mode整合为MeasureSpec模式后返回
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }

layout 源码分析

View layout 整体流程与 measure 过程基本一样

结论:

  • 需要根据 ViewGroup 本身的情况讨论 LinearLayout 下会更看重子 View 的 height 和 width 来安排对应位置 而 RelativeLayout 则更加关注子 View 的 left right top bottom 值 并且优先级高于 width 和 height 甚至在部分自定义 ViewGroup 中 measure 可能是无用的 直接使用 layout 方法来设置子 View 的位置也可以
  • ViewGroup 需要实现自己的 layout 逻辑
  • layout_XXX 中的各个熟悉都是针对子 View 的父 ViewGroup 的
  • 同样使用 View 的 getWidth () 和 getHeight () 方法来获取 View 测量的宽高 必须保证这两个方法在 onLayout 流程之后被调用才能返回有效值
  /**
     * Assign a size and position to a view and all of its
     * descendants
     *
     * <p>This is the second phase of the layout mechanism.
     * (The first is measuring). In this phase, each parent calls
     * layout on all of its children to position them.
     * This is typically done using the child measurements
     * that were stored in the measure pass().</p>
     *
     * <p>Derived classes should not override this method.
     * Derived classes with children should override
     * onLayout. In that method, they should
     * call layout on each of their children.</p>
     *
     * @param l Left position, relative to parent
     * @param t Top position, relative to parent
     * @param r Right position, relative to parent
     * @param b Bottom position, relative to parent
     */

     //同样注解写的很好了  分派给他和他的所有的子视图大小和位置
    @SuppressWarnings({"unchecked"})
    public void layout(int l, int t, int r, int b) {
        if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
            onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
            mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
        }
         //调用setFrame方法把参数分别赋值于
        int oldL = mLeft;
        int oldT = mTop;
        int oldB = mBottom;
        int oldR = mRight;
        //判断view的位置是否发生过变化 , 确定是否对当前view重新layout
        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);
            mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
            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);
                }
            }
        }

        mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
        mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
    }

onLyayout 方法

  View中
  protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
  }
  ViewGroup中
  protected abstract void onLayout(boolean changed,
        int l, int t, int r, int b);

均是空方法 后面会就 LinearLayout 和 RelativeLayout 源码进行分析

draw 源码分析

View 的 draw 流程图如下

View draw流程

结论:

  • View 需要在子类中实现 onDraw 的过程
  • 在 ViewGroup 中 会调用其子 View 的方法 顺序与子 view 的添加顺序一致

draw 的源码也很长 但是官方也给出给出了 draw 的过程

  public void draw(Canvas canvas) {
     ...
     /*
      * 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 needed
     ...
     if (!dirtyOpaque) {
         drawBackground(canvas);
     }

     // skip step 2 & 5 if possible (common case)
     ...

     // Step 2, save the canvas'' layers
     ...
         if (drawTop) {
             canvas.saveLayer(left, top, right, top + length, null, flags);
         }
     ...

     // Step 3, draw the content
     if (!dirtyOpaque) onDraw(canvas);

     // Step 4, draw the children
     dispatchDraw(canvas);

     // Step 5, draw the fade effect and restore layers
     ...
     if (drawTop) {
         matrix.setScale(1, fadeHeight * topFadeStrength);
         matrix.postTranslate(left, top);
         fade.setLocalMatrix(matrix);
         p.setShader(fade);
         canvas.drawRect(left, top, right, top + length, p);
     }
     ...

     // Step 6, draw decorations (scrollbars)
     onDrawScrollBars(canvas);
     ...
 }

Step 1, draw the background, if needed

  // Step 1, draw the background, if needed
  //如果需要的话绘制背景

  if (!dirtyOpaque) {
      drawBackground(canvas);
  }
  private void drawBackground(Canvas canvas) {

        //通过xml中属性background或者代码中setBackGroundColor\setBackgroundResource等方法赋值的背景drawable
        final Drawable background = mBackground;
        if (background == null) {
            return;
        }

        //根据layout中确定的view位置来设置背景的绘制区域
        setBackgroundBounds();

        // 如果需要的话使用显示列表
        //canvas.isHardwareAccelerated() 硬件加速判定
        //硬件加速时会将图层缓存到GPU上 而不是重绘View的每一层
        if (canvas.isHardwareAccelerated() && mAttachInfo != null
                && mAttachInfo.mHardwareRenderer != null) {
            mBackgroundRenderNode = getDrawableRenderNode(background, mBackgroundRenderNode);

            final RenderNode renderNode = mBackgroundRenderNode;
            if (renderNode != null && renderNode.isValid()) {
                setBackgroundRenderNodeProperties(renderNode);
                ((DisplayListCanvas) canvas).drawRenderNode(renderNode);
                return;
            }
        }

        final int scrollX = mScrollX;
        final int scrollY = mScrollY;
        //调用Drawable的draw方法来完成背景的绘制工作
        if ((scrollX | scrollY) == 0) {
            background.draw(canvas);
        } else {
            canvas.translate(scrollX, scrollY);
            background.draw(canvas);
            canvas.translate(-scrollX, -scrollY);
        }
    }

    void setBackgroundBounds() {
    if (mBackgroundSizeChanged && mBackground != null) {
        mBackground.setBounds(0, 0,  mRight - mLeft, mBottom - mTop);
        mBackgroundSizeChanged = false;
        rebuildOutline();
    }
  }

Step 2, save the canvas'' layers

  // Step 2, save the canvas'' layers
  //保存绘制图层

         if (drawTop) {
             canvas.saveLayer(left, top, right, top + length, null, flags);
         }

Step 3, draw the content

  // Step 3, draw the content
  //对View的内容进行绘制
  if (!dirtyOpaque) onDraw(canvas);
  /**
  * Implement this to do your drawing.
  *
  * @param canvas the canvas on which the background will be drawn
  */
  //onDraw也是空方法需要子类根据自身去实现相应的
  protected void onDraw(Canvas canvas) {
  }

Step 4, draw the children

  // Step 4, draw the children
  //绘制其子View
  dispatchDraw(canvas);
  /**
   * Called by draw to draw the child views. This may be overridden
   * by derived classes to gain control just before its children are drawn
   * (but after its own view has been drawn).
   * @param canvas the canvas on which to draw the view
   */
  protected void dispatchDraw(Canvas canvas) {
  //dispatchDraw同样空方法 与onDraw不同的是dispatchDraw在ViewGroup中被重写
  }

ViewGroup

  //dispatchDraw方法中根据子View的不同情况 包括但不只包括该View是否显示 是否有进入或消失动画等进行了部分的调整
  protected void dispatchDraw(Canvas canvas) {
      ...
        more |= drawChild(canvas, transientChild, drawingTime);
      ...    
  }

  protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
    return child.draw(canvas, this, drawingTime);
  }

Step 5, draw the fade effect and restore layers

  // Step 5, draw the fade effect and restore layers
  //绘制过度效果和恢复图层
  if (drawTop) {
      matrix.setScale(1, fadeHeight * topFadeStrength);
      matrix.postTranslate(left, top);
      fade.setLocalMatrix(matrix);
      p.setShader(fade);
      canvas.drawRect(left, top, right, top + length, p);
  }

Step 6, draw decorations (scrollbars)

  // Step 6, draw decorations (scrollbars)
  //对滚动条进行绘制
  onDrawScrollBars(canvas);

至此 View 的绘制过程全部分析完了

自己是从事了七年开发的 Android 工程师,不少人私下问我,2019 年 Android 进阶该怎么学,方法有没有?

没错,年初我花了一个多月的时间整理出来的学习资料,希望能帮助那些想进阶提升 Android 开发,却又不知道怎么进阶学习的朋友。【包括高级 UI、性能优化、架构师课程、NDK、Kotlin、混合式开发(ReactNative+Weex)、Flutter 等架构技术资料】,希望能帮助到您面试前的复习且找到一个好的工作,也节省大家在网上搜索资料的时间来学习。

资料获取方式:加入 Android 架构交流 QQ 群聊:513088520 ,进群即领取资料!!!

点击链接加入群聊【Android 移动架构总群】:加入群聊

资料大全

Android View 的绘制流程之 Layout 和 Draw 过程详解 (二) Android View 绘制流程之 DecorView 与 ViewRootImplAndroid View 的绘制流程之 Measure 过程详解 (一)Android View 的绘制流程之 Layout 和 Draw 过程详解 (二)Android View 的事件分发原理解析Android 自定义 View 详解Android View 的绘制流程之 Measure 过程详解 (一)

Android View 的绘制流程之 Layout 和 Draw 过程详解 (二) Android View 绘制流程之 DecorView 与 ViewRootImplAndroid View 的绘制流程之 Measure 过程详解 (一)Android View 的绘制流程之 Layout 和 Draw 过程详解 (二)Android View 的事件分发原理解析Android 自定义 View 详解Android View 的绘制流程之 Measure 过程详解 (一)

View 的绘制系列文章:

  • Android View 绘制流程之 DecorView 与 ViewRootImpl

  • Android View 的绘制流程之 Measure 过程详解 (一)

  • Android View 的绘制流程之 Layout 和 Draw 过程详解 (二)

  • Android View 的事件分发原理解析

  • Android 自定义 View 详解

 

在上一篇 Android View 的绘制流程之 Measure 过程详解 (一),已经详细的分析了 DecorView 和其子 View 的测量过程,接下去就要开始讲  layout 和 draw 流程。下面开始进入分析:

DecorView Layout 阶段

在 ViewRootImpl 中,调用 performlayout 方法来确定 DecorView 在屏幕中的位置,下面看下具体的代码逻辑:

// ViewRootImpl 
private void performlayout(WindowManager.LayoutParams lp,int desiredWindowWidth, desiredWindowHeight) { mLayoutRequested = false; mScrollMayChange = true; mInLayout = ; final View host = mView; if (host == null) { return; } if (DEBUG_ORIENTATION || DEBUG_LAYOUT) { Log.v(mTag,"Laying out " + host + " to (" + host.getMeasuredWidth() + "," + host.getMeasuredHeight() + ")"); } Trace.traceBegin(Trace.TRACE_TAG_VIEW,"layout"); try {
       // 根据测量结果进行绘制 host.layout(
0,0,host.getMeasuredWidth(),host.getMeasuredHeight()); mInLayout = ; int numViewsRequestingLayout = mLayoutRequesters.size(); if (numViewsRequestingLayout > 0) { // requestLayout() was called during layout. If no layout-request flags are set on the requesting views,there is no problem. If some requests are still pending,then we need to clear those flags and do a full request/measure/layout pass to handle this situation. ArrayList<View> validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters,1)">); if (validLayoutRequesters != ) { Set this flag to indicate that any further requests are happening during the second pass,which may result in posting those requests to the next frame instead mHandlingLayoutInLayoutRequest = ; Process fresh layout requests,then measure and layout int numValidRequests = validLayoutRequesters.size(); for (int i = 0; i < numValidRequests; ++i) { final View view = validLayoutRequesters.get(i); Log.w("View","requestLayout() improperly called by " + view + " during layout: running second layout pass"); view.requestLayout(); } measureHierarchy(host,lp,mView.getContext().getResources(),desiredWindowWidth,desiredWindowHeight); mInLayout = true; host.layout(0,host.getMeasuredHeight()); mHandlingLayoutInLayoutRequest = Check the valid requests again,this time without checking/clearing the layout flags,since requests happening during the second pass get noop''d validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters,1)">); ) { final ArrayList<View> finalRequesters = validLayoutRequesters; Post second-pass requests to the next frame getRunQueue().post(new Runnable() { @Override public void run() { finalRequesters.size(); i) { finalRequesters.get(i); Log.w("View","requestLayout() improperly called by " + view + " during second layout pass: posting in next frame"); view.requestLayout(); } } }); } } } } finally { Trace.traceEnd(Trace.TRACE_TAG_VIEW); } mInLayout = ; }

当在 layout 绘制过程中,收到了关于重新 layout 的请求,会先判断这些请求里面哪些是有效的,如果是有效的,那么就必须先处理,清除 layout-request flags (View.PFLAG_FORCE_LAYOUT)这些标记,再做一次彻底的重绘工作:重新测量,layout。

那么对于 DecorView 来说,调用 layout 方法,就是对它自身进行布局,注意到传递的参数分别是 0,0, host.getMeasuredWidth,host.getMeasuredHeigh,它们分别代表了一个  View 的上下左右四个位置,显然,DecorView 的左上位置为 0,然后宽高为它的测量宽高,下面来看 layout 的具体代码:

 ViewGroup 
final void layout(int l,1)">int t,1)">int r,1)"> b) {
        if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) {
            if (mTransition != ) {
                mTransition.layoutChange(this);
            }
            super.layout(l,t,r,b);
        } else {
             record the fact that we noop''d it; request layout when transition finishes
            mLayoutCalledWhileSuppressed = ;
        }
    }

 由于 ViewGroup 的 layout 方法是 final 类型,子类不能重写,这里调用了父类的  View#layout 方法,下面看看该方法是如何操作的:

// View   
if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) { onMeasure(mOldWidthMeasureSpec,mOldHeightMeasureSpec); mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT; } int oldL = mLeft; int oldT = mTop; int oldB = mBottom; int oldR = mRight;      // 设置界面的显示大小 boolean changed = isLayoutModeOptical(mParent) ? setopticalFrame(l,b) : setFrame(l,b);      // 发生了改变,或者是需要重新 layout,那么就会进入 onLayout 逻辑 if (changed || (mPrivateFlags & PFLAG_LAYOUT_required) == PFLAG_LAYOUT_required) {
// 开始 onLayout(changed,l,b);
if (shouldDrawRoundScrollbar()) { if(mRoundScrollbarRenderer == ) { mRoundScrollbarRenderer = new RoundScrollbarRenderer(); } } { mRoundScrollbarRenderer = ; }        // 清除请求 layout 的标记 mPrivateFlags &= ~PFLAG_LAYOUT_required; ListenerInfo li = mListenerInfo; if (li != null && li.mOnLayoutchangelisteners != ) { ArrayList<OnLayoutchangelistener> listenerscopy = (ArrayList<OnLayoutchangelistener>)li.mOnLayoutchangelisteners.clone(); int numListeners = listenerscopy.size(); int i = 0; i < numListeners; ++i) { listenerscopy.get(i).onLayoutChange(boolean wasLayoutValid = isLayoutValid(); mPrivateFlags &= ~PFLAG_FORCE_LAYOUT; mPrivateFlags3 |= PFLAG3_IS_LAID_OUT; if (!wasLayoutValid && isFocused()) { mPrivateFlags &= ~PFLAG_WANTS_FOCUS; (canTakeFocus()) { We have a robust focus,so parents should no longer be wanting focus. clearParentsWantFocus(); } else if (getViewRootImpl() == getViewRootImpl().isInLayout()) { This is a weird case. Most-likely the user,rather than ViewRootImpl,called layout. In this case,there''s no guarantee that parent layouts will be evaluated and thus the safest action is to clear focus here. clearFocusInternal(null,1)">/* propagate */ true,1)"> refocus ); clearParentsWantFocus(); } if (!hasParentWantsFocus()) { original requestFocus was likely on this view directly,so just clear focus clearFocusInternal( otherwise,we let parents handle re-assigning focus during their layout passes. } if ((mPrivateFlags & PFLAG_WANTS_FOCUS) != 0) { mPrivateFlags &= ~PFLAG_WANTS_FOCUS; View focused = findFocus(); if (focused != Try to restore focus as close as possible to our starting focus. if (!restoreDefaultFocus() && !hasParentWantsFocus()) { Give up and clear focus once we''ve reached the top-most parent which wants focus. focused.clearFocusInternal(); } } } if ((mPrivateFlags3 & PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT) != 0) { mPrivateFlags3 &= ~PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT; notifyEnterOrExitForAutoFillIfNeeded(); } }

 调用了 setFrame 方法,并把四个位置信息传递进去,这个方法用于确定 View 的四个顶点的位置,看下具体的代码:

protected boolean setFrame(int left,1)">int top,1)">int right,1)"> bottom) {
        boolean changed =  (DBG) {
            Log.d(VIEW_LOG_TAG,1)">this + " View.setFrame(" + left + "," + top + ","
                    + right + "," + bottom + ")");
        }
     // 有一个不一样说明发生了改变
        if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
            changed = ;

             Remember our drawn bit
            int drawn = mPrivateFlags & PFLAG_DRAWN;

            int oldWidth = mRight - mLeft;
            int oldHeight = mBottom - mTop;
            int newWidth = right - left;
            int newHeight = bottom - top;
            boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);

             Invalidate our old position
            invalidate(sizeChanged);

            mLeft = left;
            mTop = top;
            mRight = right;
            mBottom = bottom;
            mRenderNode.setLeftTopRightBottom(mLeft,mTop,mRight,mBottom);
       // 做好标记
            mPrivateFlags |= PFLAG_HAS_BOUNDS;

       // size 改变的回调
             (sizeChanged) {
                sizeChange(newWidth,newHeight,oldWidth,oldHeight);
            }

            if ((mViewFlags & VISIBILITY_MASK) == VISIBLE || mGhostView !=  If we are visible,force the DRAWN bit to on so that
                 this invalidate will go through (at least to our parent).
                 This is because someone may have invalidated this view
                 before this call to setFrame came in,thereby clearing
                 the DRAWN bit.
                mPrivateFlags |= PFLAG_DRAWN;
                invalidate(sizeChanged);
                 parent display list may need to be recreated based on a change in the bounds
                 of any child
                invalidateParentCaches();
            }

             Reset drawn bit to original value (invalidate turns it off)
            mPrivateFlags |= drawn;

            mBackgroundSizeChanged = ;
            mDefaultFocusHighlightSizeChanged = if (mForegroundInfo != ) {
                mForegroundInfo.mBoundsChanged = ;
            }

            notifySubtreeAccessibilityStateChangedIfNeeded();
        }
         changed;
    }
这里我们看到它对 mLeft、mTop、mRight、mBottom 这四个值进行了重新赋值,对于每一个View,包括 ViewGroup 来说,以上四个值保存了 Viwe 的位置信息,所以这四个值是最终宽高,也即是说,如果要得到 View 的位置信息,那么就应该在 layout 方法完成后调用 getLeft()、getTop() 等方法来取得最终宽高,如果是在此之前调用相应的方法,只能得到 0 的结果。当初始化完毕后,ViewGroup 的布局流程也就完成了。

赋值后,前后对比大小,如果发生了改变,就会调用了 sizeChange()方法,最终会回调 onSizeChanged,标明 view 的尺寸发生了变化,第一次 laout 的时候也会调用。在自定义view 的时候,可以在这里获取控件的宽和高度。

  void sizeChange(int newWidth,1)">int newHeight,1)">int oldWidth,1)"> oldHeight) {
        onSizeChanged(newWidth,oldHeight);
      ......
}

子 View Layout 流程

当 DecorView 确定好了自己的位置之后,开始调用 onLayout 来确定子 view 的位置。对于 onLayout 方法,View 和 ViewGroup 类是空实现,接下来看 FrameLayout 的实现:

 FrameLayout 
void onLayout(boolean changed,1)"> bottom) {
        layoutChildren(left,top,right,bottom,1)">false  no force left gravity */);
    }

 可以看到,该方法调用了 layoutChildren 来确定子 view 的位置。

 FrameLaout   
void layoutChildren(int bottom,1)">boolean forceLeftGravity) { int count = getChildCount();      // 获取 DecoverView 剩余的空间范围 int parentLeft = getPaddingLeftWithForeground(); int parentRight = right - left - getPaddingRightWithForeground(); int parentTop = getPaddingTopWithForeground(); int parentBottom = bottom - top - getPaddingBottomWithForeground(); int i = 0; i < count; i++final View child = getChildAt(i); if (child.getVisibility() != GONE) { final LayoutParams lp = (LayoutParams) child.getLayoutParams(); int width = child.getMeasuredWidth(); int height = child.getMeasuredHeight(); childLeft; childTop;
          // DEFAULT_CHILD_GraviTY = Gravity.TOP | Gravity.START 默认是在左上角
int gravity = lp.gravity;
                if (gravity == -1) {
                    gravity = DEFAULT_CHILD_GraviTY;
                }

                int layoutDirection = getLayoutDirection();
                int absoluteGravity = Gravity.getAbsoluteGravity(gravity,layoutDirection);
                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;
                     Gravity.RIGHT:
                        forceLeftGravity) {
                            childLeft = parentRight - width - lp.rightMargin;
                            ;
                        }
                     Gravity.LEFT:
                    default:
                        childLeft = parentLeft + lp.leftMargin;
                }
          // 确定上边起始点
                switch (verticalGravity) {
                     Gravity.TOP:
                        childTop = parentTop + lp.topMargin;
                         Gravity.CENTER_VERTICAL:
                        childTop = parentTop + (parentBottom - parentTop - height) / 2 +
                        lp.topMargin - lp.bottomMargin;
                         Gravity.BottOM:
                        childTop = parentBottom - height -:
                        childTop = parentTop + lp.topMargin;
                }

                child.layout(childLeft,childTop,childLeft + width,childTop + height);
            }
        }
    }

先梳理一下以上逻辑:

  • 首先先获取父容器的 padding 值,得到 DecorView 的可用于显示的空间范围。

  • 然后遍历其每一个子 View,根据子 View 的 layout_gravity 属性、子 View 的测量宽高、父容器的 padding 值、来确定子 View 的左上角的坐标位置

  • 然后调用 child.layout 方法,参数是左上角坐标和自身宽高结合起来的,这样就可以确定子 View 的位置。

最终调用 layout 是 View#layout,前面已经分析过,就不在分析了。

可以看到子 View 的布局流程也很简单,如果子 View 是一个 ViewGroup,那么就会重复以上步骤,如果是一个 View,那么会直接调用 View#layout 方法,根据以上分析,在该方法内部会设置 view 的四个布局参数,接着调用 onLayout 方法 :

 bottom) {

}

此方法是一个空方法,也就是说需要子类去实现此方法,不同的 View 实现方式不同,这里就不分析了。

layout阶段的基本思想也是由根View开始,递归地完成整个控件树的布局(layout)工作。 

对于宽高的获取这里在总结下:

 

 

 这里需要注意一下,在非一般情况下,也就是通过人为设置,重写View的layout()强行设置,这种情况下,测量的值与最终的值是不一样的。

Layout 整体的流程图

上面分别从 View 和 ViewGroup 的角度讲解了布局流程,这里再以流程图的形式归纳一下整个 Layout 过程,便于加深记忆:

 

DecorView Draw 流程

Draw 的入口也是在 ViewRootImpl 中,执行 ViewRootImpl#performTraversals 中会执行 ViewRootIml#performDraw:

 performDraw() {
...
fullRedrawNeeded,它的作用是判断是否需要重新绘制全部视图
draw(fullRedrawNeeded);
...
}

然后会执行到 ViewRootImpl#draw:

void draw( fullRedrawNeeded) {
 ...
 获取mDirty,该值表示需要重绘的区域
 final Rect dirty = mDirty;
 if (mSurfaceHolder != ) {
   The app owns the surface,we won''t draw.
  dirty.setEmpty();
   (animating) {
   if (mScroller != ) {
    mScroller.abortAnimation();
   }
   disposeResizeBuffer();
  }
  ;
 }

 如果fullRedrawNeeded为真,则把dirty区域置为整个屏幕,表示整个视图都需要绘制
 第一次绘制流程,需要绘制所有视图
  (fullRedrawNeeded) {
  mAttachInfo.mIgnoreDirtyState = ;
  dirty.set(0,(int) (mWidth * appScale + 0.5f),1)">int) (mHeight * appScale + 0.5f));
 }
 ...
 drawSoftware(surface,mAttachInfo,xOffset,yOffset,scalingrequired,dirty)) {
    ;
  }
}

 接着会执行到 ViewRootIml#drawSoftware,然后在 ViewRootIml#drawSoftware 会执行到 mView.draw(canvas)。

boolean drawSoftware(Surface surface,AttachInfo attachInfo,1)">int xoff,1)"> yoff,1)"> scalingrequired,Rect dirty) {
 final Canvas canvas;
  锁定canvas区域,由dirty区域决定
  这个canvas就是我们想在上面绘制东西的画布
  canvas = mSurface.lockCanvas(dirty);
  ...
 画布支持位图的密度,和手机分辨率相关
  canvas.setDensity(mDensity);
 ...
   if (!canvas.isOpaque() || yoff != 0 || xoff != 0) {
                canvas.drawColor(0yoff);
   ...
   正式开始绘制
   mView.draw(canvas);
  ...
 提交需要绘制的东西
  surface.unlockCanvasAndPost(canvas);
}

 mView.draw(canvas) 开始真正的绘制。此处 mView 就是 DecorView,先看 DecorView 中 Draw 的方法:

   draw(Canvas canvas) {
        .draw(canvas);

        if (mMenuBackground != ) {
            mMenuBackground.draw(canvas);
        }
    }

 里面会调用 View#draw:

    int privateFlags = mPrivateFlags;
        boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
                (mAttachInfo == 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 needed
         saveCount;

        绘制背景
        dirtyOpaque) {
            drawBackground(canvas);
        }

         如果可以跳过2和5步
        int viewFlags = mViewFlags;
      判断是否有绘制衰退边缘的标示
        boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
        boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
      如果没有绘制衰退边缘只需要3,4,6步
        if (!verticalEdges && !horizontalEdges) {
             Step 3,draw the content
            dirtyOpaque) onDraw(canvas);

             Step 4,draw the children
            dispatchDraw(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);

             we''re done...
            ;
        }

        
         * Here we do the full fledged routine...
         * (this is an uncommon case where speed matters less,* this is why we repeat some of the tests that have been
         * done above)
         boolean drawTop = boolean drawBottom = boolean drawLeft = boolean drawRight = float topFadeStrength = 0.0ffloat bottomFadeStrength = 0.0ffloat leftFadeStrength = 0.0ffloat rightFadeStrength = 0.0f Step 2,save the canvas'' layers
        int paddingLeft = mPaddingLeft;

        boolean offsetrequired = isPaddingOffsetrequired();
         (offsetrequired) {
            paddingLeft += getLeftPaddingOffset();
        }

        int left = mScrollX + paddingLeft;
        int right = left + mRight - mLeft - mPaddingRight -int top = mScrollY + getFadetop(offsetrequired);
        int bottom = top + getFadeHeight(offsetrequired);

         (offsetrequired) {
            right += getRightPaddingOffset();
            bottom += getBottomPaddingOffset();
        }

        final ScrollabilityCache scrollabilityCache = mScrollCache;
        float fadeHeight = scrollabilityCache.fadingEdgeLength;
        int length = () fadeHeight;

         clip the fade length if top and bottom fades overlap
         overlapping fades produce odd-looking artifacts
        if (verticalEdges && (top + length > bottom - length)) {
            length = (bottom - top) / 2 also clip horizontal fades if necessary
        if (horizontalEdges && (left + length > right - length)) {
            length = (right - left) / 2 (verticalEdges) {
            topFadeStrength = Math.max(0.0f,Math.min(1.0f;
            bottomFadeStrength = Math.max(0.0f,getBottomFadingEdgeStrength()));
            drawBottom = bottomFadeStrength * fadeHeight > 1.0f (horizontalEdges) {
            leftFadeStrength = Math.max(0.0f,getLeftFadingEdgeStrength()));
            drawLeft = leftFadeStrength * fadeHeight > 1.0f;
            rightFadeStrength = Math.max(0.0f,getRightFadingEdgeStrength()));
            drawRight = rightFadeStrength * fadeHeight > 1.0f;
        }

        saveCount = canvas.getSaveCount();

        int solidColor = getSolidColor();
        if (solidColor == 0int flags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG;

             (drawTop) {
                canvas.saveLayer(left,top + length,flags);
            }

             (drawBottom) {
                canvas.saveLayer(left,bottom - length,1)"> (drawLeft) {
                canvas.saveLayer(left,left + length,1)"> (drawRight) {
                canvas.saveLayer(right - length,flags);
            }
        }  {
            scrollabilityCache.setFadeColor(solidColor);
        }

        dirtyOpaque) onDraw(canvas);

                dispatchDraw(canvas);

         Step 5,draw the fade effect and restore layers
        final Paint p = scrollabilityCache.paint;
        final Matrix matrix = scrollabilityCache.matrix;
        final Shader fade = scrollabilityCache.shader;

         (drawTop) {
            matrix.setScale(1,fadeHeight * topFadeStrength);
            matrix.postTranslate(left,top);
            fade.setLocalMatrix(matrix);
            p.setShader(fade);
            canvas.drawRect(left,top + length,p);
        }

         (drawBottom) {
            matrix.setScale(1,1)"> bottomFadeStrength);
            matrix.postRotate(180);
            matrix.postTranslate(left,bottom);
            fade.setLocalMatrix(matrix);
            p.setShader(fade);
            canvas.drawRect(left,bottom - (drawLeft) {
            matrix.setScale(1,1)"> leftFadeStrength);
            matrix.postRotate(-90 (drawRight) {
            matrix.setScale(1,1)"> rightFadeStrength);
            matrix.postRotate(90);
            matrix.postTranslate(right,top);
            fade.setLocalMatrix(matrix);
            p.setShader(fade);
            canvas.drawRect(right -etoCount(saveCount);

         Overlay is part of the content and draws beneath Foreground
        mOverlay.isEmpty()) {
            mOverlay.getoverlayView().dispatchDraw(canvas);
        }

                onDrawForeground(canvas);
    }

可以看到,draw过程比较复杂,但是逻辑十分清晰,而官方注释也清楚地说明了每一步的做法。我们首先来看一开始的标记位 dirtyOpaque,该标记位的作用是判断当前 View 是否是透明的,如果 View 是透明的,那么根据下面的逻辑可以看出,将不会执行一些步骤,比如绘制背景、绘制内容等。这样很容易理解,因为一个 View 既然是透明的,那就没必要绘制它了。接着是绘制流程的六个步骤,这里先小结这六个步骤分别是什么,然后再展开来讲。绘制流程的六个步骤:

  1. 对 View 的背景进行绘制

  2. 保存当前的图层信息(可跳过)

  3. 绘制 View 的内容

  4. 对 View 的子 View 进行绘制(如果有子 View )

  5. 绘制 View 的褪色的边缘,类似于阴影效果(可跳过)

  6. 绘制 View 的装饰(例如:滚动条)

其中第2步和第5步是可以跳过的,我们这里不做分析,我们重点来分析其它步骤。

ViewGroup子类默认情况下就是不执行 onDraw 方法的,在 ViewGroup 源码中的 initViewGroup() 方法中设置了一个标记,源码如下:

 initViewGroup() {
         ViewGroup doesn''t draw by default
        debugDraw()) {
            setFlags(WILL_NOT_DRAW,DRAW_MASK);
        }
        ......
}

看第二行注释也知道,ViewGroup 默认情况下是不会 draw 的。第四行调用 setFlags 方法设置标记 WILL_NOT_DRAW,在回到 View 中 draw 方法看第2行代码:

1   mPrivateFlags;
2  boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
3      (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);

 

setFlags 方法就是对 View中mPrivateFlags 值进行相应改变,我们设置标记 WILL_NOT_DRAW 那么 dirtyOpaque 得到的值就为 true,从而 if (!dirtyOpaque) 不成立,也就不会执行onDraw 方法。

1. 绘制背景

View#drawBackground

     drawBackground(Canvas canvas) {
       获取背景的Drawable,没有就不需要绘制
        final Drawable background = mBackground;
        if (background == ;
        }
       确定背景Drawable边界
        setBackgroundBounds();
        ...

       如果有偏移量先偏移画布再将drawable绘制上去
        int scrollX = mScrollX;
        int scrollY = mScrollY;
        if ((scrollX | scrollY) == 0) {
            background.draw(canvas);
        }  {
            canvas.translate(scrollX,scrollY);
            此处会执行各种Drawable对应的draw方法
            background.draw(canvas);
            把画布的原点移回去,drawable在屏幕上的位置不动
            canvas.translate(-scrollX,1)">scrollY);
        }
    }

3. 绘制 View 的内容

先跳过第 2 步,是因为不是所有的 View 都需绘制褪色边缘。DecorView#onDraw:

    onDraw(Canvas c) {
        .onDraw(c);

        mBackgroundFallback.draw(
   onDraw(Canvas canvas) {
}

onDraw 是空实现,需要子 View 自己去绘制。对于DecorView 一般也没啥内容,除了需要背景颜色等,所以本身并需要绘制啥。

4. 绘制子View

DecorView 绘制完成后,开始绘制子 View,所以 ViewGroup 的绘制需要绘制子 View,直接看看 ViewGroup#dispatchDraw:

 dispatchDraw(Canvas canvas) {
       boolean usingRenderNodeProperties = canvas.isRecordingFor(mRenderNode);
       int childrenCount = mChildrenCount;
       final View[] children = mChildren;
        mGroupFlags;

ViewGroup是否有设置子View入场动画,如果有绑定到View
 启动动画控制器
      ...

指定修改区域
       int clipSaveCount = 0;
       boolean clipToPadding = (flags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK;
    // 不让子view绘制在pandding里面,也就是去除padding
(clipToPadding) { clipSaveCount = canvas.save(); canvas.clipRect(mScrollX + mPaddingLeft,mScrollY + mPaddingTop,mScrollX + mRight - mLeft - mPaddingRight,mScrollY + mBottom - mTop - mPaddingBottom); } ... int i = 0; i < childrenCount; i++) { 先取mTransientViews中的View,mTransientViews中的View通过addTransientView添加,它们只是容器渲染的一个item while (transientIndex >= 0 && mTransientIndices.get(transientIndex) == i) { final View transientChild = mTransientViews.get(transientIndex); if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE || transientChild.getAnimation() != ) { more |= drawChild(canvas,transientChild,drawingTime); } transientIndex++; if (transientIndex >= transientCount) { transientIndex = -1; } } int childindex = customOrder ? getChildDrawingOrder(childrenCount,i) : i; final View child = (preorderedList == ) ? children[childindex] : preorderedList.get(childindex); if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != ) { more |=dispatchDraw 的流程是先启动第一次加到布局中的动画,然后确定绘制区域,遍历绘制 View,遍历 View 的时候优先绘制渲染的 mTransientViews,绘制 View 调用到ViewGroup#drawChild:

boolean drawChild(Canvas canvas,View child,1)">long drawingTime) {
        View中有两个draw方法
        这个多参数的draw用于view绘制自身内容
        return child.draw(canvas,drawingTime);
    }

 View#draw(canvas,this,drawingTime)

boolean draw(Canvas canvas,ViewGroup parent,1)"> drawingTime) {
 
       boolean drawingWithRenderNode = mAttachInfo != null
                && mAttachInfo.mHardwareAccelerated
                && hardwareAcceleratedCanvas;
      ...

     主要判断是否有绘制缓存,如果有,直接使用缓存,如果没有,调用 draw(canvas)方法
        drawingWithDrawingCache) {
             (drawingWithRenderNode) {
                mPrivateFlags &= ~PFLAG_DIRTY_MASK;
                ((displayListCanvas) canvas).draWrenderNode(renderNode);
            }  {
                 Fast path for layouts with no backgrounds
                if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
                    mPrivateFlags &= ~PFLAG_DIRTY_MASK;
                    dispatchDraw(canvas);
                }  {
                    draw(canvas);
                }
            }
        } if (cache != PFLAG_DIRTY_MASK;
            if (layerType == LAYER_TYPE_NONE) {
                 no layer paint,use temporary paint to draw bitmap
                Paint cachePaint = parent.mCachePaint;
                if (cachePaint == ) {
                    cachePaint =  Paint();
                    cachePaint.setDither();
                    parent.mCachePaint = cachePaint;
                }
                cachePaint.setAlpha((int) (alpha * 255));
                canvas.drawBitmap(cache,0.0f,0.0f use layer paint to draw the bitmap,merging the two alphas,but also restore
                int layerPaintAlpha = mLayerPaint.getAlpha();
                mLayerPaint.setAlpha((int) (alpha * layerPaintAlpha));
                canvas.drawBitmap(cache,mLayerPaint);
                mLayerPaint.setAlpha(layerPaintAlpha);
            }
      }
}

首先判断是否已经有缓存,即之前是否已经绘制过一次了,如果没有,则会调用 draw(canvas) 方法,开始正常的绘制,即上面所说的六个步骤,否则利用缓存来显示。

这一步也可以归纳为 ViewGroup 绘制过程,它对子 View 进行了绘制,而子 View 又会调用自身的 draw 方法来绘制自身,这样不断遍历子 View 及子 View 的不断对自身的绘制,从而使得 View 树完成绘制。

对于自定义 View ,如果需要绘制东西的话,直接重新 onDraw 就可以了。

6. 绘制装饰

     onDrawForeground(Canvas canvas) {
     绘制滑动指示
        onDrawScrollIndicators(canvas);
     绘制ScrollBar
        onDrawScrollBars(canvas);
     获取前景色的Drawable,绘制到canvas上
        final Drawable foreground = mForegroundInfo != null ? mForegroundInfo.mDrawable : if (foreground !=  (mForegroundInfo.mBoundsChanged) {
                mForegroundInfo.mBoundsChanged = ;
                final Rect selfBounds = mForegroundInfo.mSelfBounds;
                final Rect overlayBounds = mForegroundInfo.mOverlayBounds;
                 (mForegroundInfo.mInsidePadding) {
                    selfBounds.set(0,0 {
                    selfBounds.set(getPaddingLeft(),getPaddingTop(),getWidth() - getPaddingRight(),getHeight() - getPaddingBottom());
                }
                int ld = getLayoutDirection();
                Gravity.apply(mForegroundInfo.mGravity,foreground.getIntrinsicWidth(),foreground.getIntrinsicHeight(),selfBounds,overlayBounds,ld);
                foreground.setBounds(overlayBounds);
            }
            foreground.draw(canvas);
        }
    }

 2和5.绘制View的褪色边缘

当 horizontalEdges 或者 verticalEdges 有一个 true 的时候,表示需要绘制 View 的褪色边缘:

     boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;

这时候先计算出是否需要绘制上下左右的褪色边缘和它的参数,然后保存视图层:

         mPaddingLeft;
         getLeftPaddingOffset();
        }
         getFadeHeight(offsetrequired);
         getBottomPaddingOffset();
        }
        ) fadeHeight;
        ;
        }
        saveCount = canvas.getSaveCount();
         Canvas.HAS_ALPHA_LAYER_SAVE_FLAG;
             {
            scrollabilityCache.setFadeColor(solidColor);
        }

绘制褪色边缘,恢复视图层 :

         scrollabilityCache.shader;
        etoCount(saveCount);

所谓的绘制装饰,就是指 View 除了背景、内容、子 View 的其余部分,例如滚动条等。

最后附上 View 的 draw 流程:

 

 

到此,View 的绘制流程就讲完了,下一篇会讲自定义 View。

小结:

  • view 不停找 parent 可以一直找到 DecorView,按理说 DecorView 是顶点了,但是 DecorView 还有个虚拟父 view,ViewRootImpl。 ViewRootImpl 不是一个 View 或者ViewGroup,他有个成员 mView 是 DecorView,所有的操作从 ViewRootImpl 开始自上而下分发

  • view 的 invalidate 不会导致 ViewRootImpl 的 invalidate 被调用,而是递归调用父 view的invalidateChildInParent,直到 ViewRootImpl 的 invalidateChildInParent,然后触发peformTraversals,会导致当前 view 被重绘,由于 mLayoutRequested 为 false,不会导致 onMeasure 和 onLayout 被调用,而 OnDraw 会被调用

  • 一个 view 的 invalidate 会导致本身 PFLAG_INVALIDATED 置 1,导致本身以及父族 viewgroup 的 PFLAG_DRAWING_CACHE_VALID 置 0

  • requestLayout 会直接递归调用父窗口的 requestLayout,直到 ViewRootImpl,然后触发 peformTraversals,由于 mLayoutRequested 为 true,会导致 onMeasure 和onLayout 被调用。不一定会触发 OnDraw

  • requestLayout 触发 onDraw 可能是因为在在 layout 过程中发现 l,b 和以前不一样,那就会触发一次 invalidate,所以触发了onDraw,也可能是因为别的原因导致 mDirty 非空(比如在跑动画)

  • requestLayout 会导致自己以及父族 view 的 PFLAG_FORCE_LAYOUT 和 PFLAG_INVALIDATED 标志被设置。

  • 一般来说,只要刷新的时候就调用 invalidate,需要重新 measure 就调用 requestLayout,后面再跟个 invalidate(为了保证重绘),

 

总结

以上是小编为你收集整理的Android View 的绘制流程之 Layout 和 Draw 过程详解 (二) Android View 绘制流程之 DecorView 与 ViewRootImplAndroid View 的绘制流程之 Measure 过程详解 (一)Android View 的绘制流程之 Layout 和 Draw 过程详解 (二)Android View 的事件分发原理解析Android 自定义 View 详解Android View 的绘制流程之 Measure 过程详解 (一)全部内容。

如果觉得小编网站内容还不错,欢迎将小编网站推荐给好友。

Android View 的绘制流程之 Measure 过程详解 (一) Android View 绘制流程之 DecorView 与 ViewRootImplAndroid View 的绘制流程之 Measure 过程详解 (一)Android View 的绘制流程之 Layout 和 Draw 过程详解 (二)Android View 的事件分发原理解析Android 自定义 View 详解Android View 绘制流程之 DecorView 与 ViewRootImp

Android View 的绘制流程之 Measure 过程详解 (一) Android View 绘制流程之 DecorView 与 ViewRootImplAndroid View 的绘制流程之 Measure 过程详解 (一)Android View 的绘制流程之 Layout 和 Draw 过程详解 (二)Android View 的事件分发原理解析Android 自定义 View 详解Android View 绘制流程之 DecorView 与 ViewRootImp

View 的绘制系列文章:

  • Android View 绘制流程之 DecorView 与 ViewRootImpl

  • Android View 的绘制流程之 Measure 过程详解 (一)

  • Android View 的绘制流程之 Layout 和 Draw 过程详解 (二)

  • Android View 的事件分发原理解析

  • Android 自定义 View 详解

 

概述

上一篇 Android View 绘制流程之 DecorView 与 ViewRootImpl 分析了在调用 setContentView 之后,DecorView 是如何与 activity 关联在一起的,最后讲到了 ViewRootImpl 开始绘制的逻辑。本文接着上篇,继续往下讲,开始分析 view 的绘制流程。

上文说到了调用 performTraversals 进行绘制,由于 performTraversals 方法比较长,看一个简化版:

// ViewRootImpl 类
private void performTraversals() {
     这个方法代码非常多,但是重点就是执行这三个方法
     执行测量
    performMeasure(childWidthMeasureSpec,childHeightMeasureSpec);
     执行布局(ViewGroup)中才会有
    performlayout(lp,mWidth,mHeight);
     执行绘制
    performDraw();
}

其流程具体如下:

 View的整个绘制流程可以分为以下三个阶段:

  • measure: 判断是否需要重新计算 View 的大小,需要的话则计算;

  • layout: 判断是否需要重新计算 View 的位置,需要的话则计算;

  • draw: 判断是否需要重新绘制 View,需要的话则重绘制。

MeasureSpec

在介绍绘制前,先了解下 MeasureSpec。MeasureSpec 封装了父布局传递给子布局的布局要求,它通过一个 32 位 int 类型的值来表示,该值包含了两种信息,高两位表示的是 SpecMode(测量模式),低 30 位表示的是 Specsize(测量的具体大小)。下面通过注释的方式来分析来类:

/**  
 * 三种SpecMode: 
 * 1.UNSPECIFIED 
 * 父 ViewGroup 没有对子View施加任何约束,子 view 可以是任意大小。这种情况比较少见,主要用于系统内部多次measure的情形,
* 用到的一般都是可以滚动的容器中的子view,比如ListView、GridView、RecyclerView中某些情况下的子view就是这种模式。
* 一般来说,我们不需要关注此模式。  * 2.EXACTLY   * 该 view 必须使用父 ViewGroup 给其指定的尺寸。对应 match_parent 或者具体数值(比如30dp)  * 3.AT_MOST   * 该 View 最大可以取父ViewGroup给其指定的尺寸。对应wrap_content  *    * MeasureSpec使用了二进制去减少对象的分配。   
*/   public class MeasureSpec {            进位大小为2的30次方(int的大小为32位,所以进位30位就是要使用int的最高位和第二高位也就是32和31位做标志位)           private static final int MODE_SHIFT = 30;            运算遮罩,0x3为16进制,10进制为3,二进制为11。3向左进位30,就是11 00000000000(11后跟30个0)            (遮罩的作用是用1标注需要的值,0标注不要的值。因为1与任何数做与运算都得任何数,0与任何数做与运算都得0)           int MODE_MASK  = 0x3 << MODE_SHIFT;            0向左进位30,就是00 00000000000(00后跟30个0)           int UNSPECIFIED = 0 << MODE_SHIFT;            1向左进位30,就是01 00000000000(01后跟30个0)           int EXACTLY     = 1 << 2向左进位30,就是10 00000000000(10后跟30个0)           int AT_MOST     = 2 <<           * 根据提供的size和mode得到一个详细的测量结果                       第一个return:          measureSpec = size + mode;   (注意:二进制的加法,不是十进制的加法!)            这里设计的目的就是使用一个32位的二进制数,32和31位代表了mode的值,后30位代表size的值            例如size=100(4),mode=AT_MOST,则measureSpec=100+10000...00=10000..00100                      第二个return:          size &; ~MODE_MASK就是取size 的后30位,mode &amp; MODE_MASK就是取mode的前两位,最后执行或运算,得出来的数字,前面2位包含代表mode,后面30位代表size         int makeMeasureSpec(int size, int mode) {               if (sUsebrokenMakeMeasureSpec) {                 return size + mode;             } else {                 return (size & ~MODE_MASK) | (mode &  MODE_MASK);             }         }                      * 获得SpecMode           mode = measureSpec &amp; MODE_MASK;            MODE_MASK = 11 00000000000(11后跟30个0),原理是用MODE_MASK后30位的0替换掉measureSpec后30位中的1,再保留32和31位的mode值。            例如10 00..00100 & 11 00..00(11后跟30个0) = 10 00..00(AT_MOST),这样就得到了mode的值           int getMode( measureSpec) {               return (measureSpec & MODE_MASK);           }                      * 获得Specsize            size = measureSpec &  ~MODE_MASK;            原理同上,不过这次是将MODE_MASK取反,也就是变成了00 111111(00后跟30个1),将32,31替换成0也就是去掉mode,保留后30位的size           int getSize(return (measureSpec &  ~MODE_MASK);           }   }  

顺便提下 MATCH_PARENT 和 WRAP_CONTENT 这两个代表的值,分别是 -1 和 -2。

       
         * Special value for the height or width requested by a View.
         * MATCH_PARENT means that the view wants to be as big as its parent,* minus the parent''s padding,if any. Introduced in API Level 8.
         */
        public static final int MATCH_PARENT = -1;

        
         * Special value for the height or width requested by a View.
         * WRAP_CONTENT means that the view wants to be just large enough to fit
         * its own internal content,taking its own padding into account.
         int WRAP_CONTENT = -2;

Decorview 尺寸的确定

在 performTraversals 中,首先是要确定 DecorView 的尺寸。只有当 DecorView 尺寸确定了,其子 View 才可以知道自己能有多大。具体是如何去确定的,可以看下面的代码:

Activity窗口的宽度和高度
 desiredWindowWidth;
 desiredWindowHeight;
...
用来保存窗口宽度和高度,来自于全局变量mWinFrame,这个mWinFrame保存了窗口最新尺寸
Rect frame = mWinFrame;
构造方法里mFirst赋值为true,意思是第一次执行遍历吗    
 (mFirst) {
    是否需要重绘
    mFullRedrawNeeded = true;
    是否需要重新确定Layout
    mLayoutRequested = ;
    
    // 这里又包含两种情况:是否包括状态栏
    
    判断要绘制的窗口是否包含状态栏,有就去掉,然后确定要绘制的Decorview的高度和宽度
     (shouldUsedisplaySize(lp)) {
         NOTE -- system code,won''t try to do compat mode.
        Point size = new Point();
        mdisplay.getRealSize(size);
        desiredWindowWidth = size.x;
        desiredWindowHeight = size.y;
    }  {
        宽度和高度为整个屏幕的值
        Configuration config = mContext.getResources().getConfiguration();
        desiredWindowWidth = dipToPx(config.screenWidthDp);
        desiredWindowHeight = dipToPx(config.screenHeightDp);
    }
    ...
 else{
    
        // 这是window的长和宽改变了的情况,需要对改变的进行数据记录
    
        如果不是第一次进来这个方法,它的当前宽度和高度就从之前的mWinFrame获取
        desiredWindowWidth = frame.width();
        desiredWindowHeight = frame.height();
        
         * mWidth和mHeight是由WindowManagerService服务计算出的窗口大小,
         * 如果这次测量的窗口大小与这两个值不同,说明WMS单方面改变了窗口的尺寸
         if (desiredWindowWidth != mWidth || desiredWindowHeight != mHeight) {
            if (DEBUG_ORIENTATION) Log.v(mTag,"View " + host + " resized to: " + frame);
            需要进行完整的重绘以适应新的窗口尺寸
            mFullRedrawNeeded = ;
            需要对控件树进行重新布局
            mLayoutRequested = window窗口大小改变
            windowSizeMayChange = ;
        }
 }
    ...
    // 进行预测量
     (layoutRequested){
        ...
         (mFirst) {
             视图窗口当前是否处于触摸模式。
            mAttachInfo.mInTouchMode = !mAddedTouchMode;
            确保这个Window的触摸模式已经被设置
            ensuretouchModeLocally(mAddedTouchMode);
        }  {
            六个if语句,判断insects值和上一次比有什么变化,不同的话就改变insetsChanged
            insects值包括了一些屏幕需要预留的区域、记录一些被遮挡的区域等信息
            if (!mPendingOverscanInsets.equals(mAttachInfo.mOverscanInsets)) {
                    insetsChanged = ;
            }
            ...
            
          //  这里有一种情况,我们在写dialog时,会手动添加布局,当设定宽高为Wrap_content时,会把屏幕的宽高进行赋值,给出尽量长的宽度
            
            
             * 如果当前窗口的根布局的width或height被指定为 WRAP_CONTENT 时,
             * 比如Dialog,那我们还是给它尽量大的长宽,这里是将屏幕长宽赋值给它
             */
            if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT
                    || lp.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
                windowSizeMayChange = ;
                判断要绘制的窗口是否包含状态栏,有就去掉,然后确定要绘制的Decorview的高度和宽度
                 (shouldUsedisplaySize(lp)) {
                     Point();
                    mdisplay.getRealSize(size);
                    desiredWindowWidth = size.x;
                    desiredWindowHeight = size.y;
                }  {
                    Configuration config = res.getConfiguration();
                    desiredWindowWidth = dipToPx(config.screenWidthDp);
                    desiredWindowHeight = dipToPx(config.screenHeightDp);
                }
            }
        }
    }
}

这里主要是分两步走:

  1. 如果是第一次测量,那么根据是否有状态栏,来确定是直接使用屏幕的高度,还是真正的显示区高度。

  2. 如果不是第一次,那么从 mWinFrame 获取,并和之前保存的长宽高进行比较,不相等的话就需要重新测量确定高度。

当确定了 DecorView 的具体尺寸之后,然后就会调用 measureHierarchy 来确定其 MeasureSpec :

  Ask host how big it wants to be
  windowSizeMayChange |= measureHierarchy(host,lp,res,desiredWindowWidth,desiredWindowHeight); 

其中 host 就是 DecorView,lp 是 wm 在添加时候传给 DecorView 的,最后两个就是刚刚确定显示宽高 ,看下方法的具体逻辑 :

    boolean measureHierarchy(final View host,final WindowManager.LayoutParams lp,final Resources res,1)">int desiredWindowWidth,1)"> desiredWindowHeight) {
         childWidthMeasureSpec;
         childHeightMeasureSpec;
        boolean windowSizeMayChange = false;boolean goodMeasure = ;
     // 说明是 dialog
ViewGroup.LayoutParams.WRAP_CONTENT) { On large screens,we don''t want to allow dialogs to just stretch to fill the entire width of the screen to display one line of text. First try doing the layout at a smaller size to see if it will fit. final displayMetrics packageMetrics = res.getdisplayMetrics(); res.getValue(com.android.internal.R.dimen.config_prefDialogWidth,mTmpValue,1)">); int baseSize = 0       // 获取一个基本的尺寸 if (mTmpValue.type == TypedValue.TYPE_DIMENSION) { baseSize = ()mTmpValue.getDimension(packageMetrics); } if (DEBUG_DIALOG) Log.v(mTag,"Window " + mView + ": baseSize=" + baseSize + ",desiredWindowWidth=" + desiredWindowWidth);
       // 如果大于基本尺寸
if (baseSize != 0 && desiredWindowWidth > baseSize) { childWidthMeasureSpec = getRootMeasureSpec(baseSize,lp.width); childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight,lp.height); performMeasure(childWidthMeasureSpec,childHeightMeasureSpec); host.getMeasuredHeight() + ") from width spec: " + MeasureSpec.toString(childWidthMeasureSpec) + " and height spec: " + MeasureSpec.toString(childHeightMeasureSpec));
          // 判断测量是否准确
if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) { goodMeasure = ; } { Didn''t fit in that size... try expanding a bit. baseSize = (baseSize+desiredWindowWidth)/2; baseSize); childWidthMeasureSpec = performMeasure(childWidthMeasureSpec,childHeightMeasureSpec); ); ) { ); goodMeasure = ; } } } }      // 这里就是一般 DecorView 会走的逻辑 goodMeasure) { childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth,lp.width); childHeightMeasureSpec = performMeasure(childWidthMeasureSpec,childHeightMeasureSpec);
       // 与之前的尺寸进行对比,看看是否相等,不想等,说明尺寸可能发生了变化
if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()) { windowSizeMayChange = ; } } return windowSizeMayChange; }

上面主要主要做的就是来确定父 View 的 MeasureSpec。但是分了两种不同类型:

  • 如果宽是 WRAP_CONTENT 类型,说明这是 dialog,会有一些针对 dialog 的处理,最终会调用 performMeasure 进行测量;

  • 对于一般 Activity 的尺寸,会调用  getRootMeasureSpec MeasureSpec 。

下面看下 DecorView MeasureSpec 的计算方法:

    int getRootMeasureSpec(int windowSize,1)"> rootDimension) {
         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;
         ViewGroup.LayoutParams.WRAP_CONTENT:
             Window can resize. Set max size for root view.
            measureSpec =default:
             Window wants to be an exact size. Force root view to be that size.
            measureSpec = MeasureSpec.makeMeasureSpec(rootDimension,1)">;
        }
        return measureSpec;
    }

 该方法主要是根据 View 的 MeasureSpec 是根据宽高的参数来划分的。

  • MATCH_PARENT :精确模式,大小就是窗口的大小;

  • WRAP_CONTENT :最大模式,大小不定,但是不能超过窗口的大小;

  • 固定大小:精确模式,大小就是指定的具体宽高,比如100dp。

对于 DecorView 来说就是走第一个 case,到这里 DecorView 的 MeasureSpec 就确定了,从 MeasureSpec 可以得出 DecorView 的宽高的约束信息。

获取子 view 的 MeasureSpec

当父 ViewGroup 对子 View 进行测量时,会调用 View 类的 measure 方法,这是一个 final 方法,无法被重写。ViewGroup 会传入自己的 widthMeasureSpec 和  heightMeasureSpec,分别表示父 View 对子 View 的宽度和高度的一些限制条件。尤其是当 ViewGroup 是 WRAP_CONTENT 的时候,需要优先测量子 View,只有子 View 宽高确定,ViewGroup 才能确定自己到底需要多大的宽高。

当 DecorView 的 MeasureSpec 确定以后,ViewRootImpl 内部会调用 performMeasure 方法:

  void performMeasure(int childWidthMeasureSpec,1)"> childHeightMeasureSpec) {
        if (mView == null) {
            ;
        }
        Trace.traceBegin(Trace.TRACE_TAG_VIEW,"measure");
        try {
            mView.measure(childWidthMeasureSpec,childHeightMeasureSpec);
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
    }

该方法传入的是对 DecorView 的 MeasureSpec,其中 mView 就是 DecorView 的实例,接下来看 measure() 的具体逻辑:


 * 调用这个方法来算出一个View应该为多大。参数为父View对其宽高的约束信息。
 * 实际的测量工作在onMeasure()方法中进行
 */
void measure(int widthMeasureSpec,1)"> heightMeasureSpec) {
  ......
  // Suppress sign extension for the low bytes
   long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;
   if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);
 若mPrivateFlags中包含PFLAG_FORCE_LAYOUT标记,则强制重新布局
   比如调用View.requestLayout()会在mPrivateFlags中加入此标记
  boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
  boolean specChanged = widthMeasureSpec != mOldWidthMeasureSpec
      || heightMeasureSpec != mOldHeightMeasureSpec;
  boolean isspecExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY
      && MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;
  boolean matchesSpecsize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec)
      && getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);
  boolean needsLayout = specChanged
      && (sAlwaysRemeasureExactly || !isspecExactly || !matchesSpecsize);

   需要重新布局  
  if (forceLayout || needsLayout) {
    // first clears the measured dimension flag 标记为未测量状态
mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
  // 对阿拉伯语、希伯来语等从右到左书写、布局的语言进行特殊处理
resolveRtlPropertiesIfNeeded();
 先尝试从缓从中获取,若forceLayout为true或是缓存中不存在或是
     忽略缓存,则调用onMeasure()重新进行测量工作
    int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
    if (cacheIndex < 0 || sIgnoreMeasureCache) {
       measure ourselves,this should set the measured dimension flag back
      onMeasure(widthMeasureSpec,heightMeasureSpec);
      . . .
    }  {
       缓存命中,直接从缓存中取值即可,不必再测量
      long value = mMeasureCache.valueAt(cacheIndex);
       Casting a long to int drops the high 32 bits,no mask needed
      setMeasuredDimensionRaw((int) (value >> 32),() value);
mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
    }
   // 如果自定义的View重写了onMeasure方法,但是没有调用setMeasuredDimension()方法就会在这里抛出错误;
   // flag not set,setMeasuredDimension() was not invoked,we raise
   // an exception to warn the developer
   if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {
   throw new IllegalStateException("View with id " + getId() + ": "
  + getClass().getName() + "#onMeasure() did not set the"
  + " measured dimension by calling"
  + " setMeasuredDimension()");
   } 
      //到了这里,View已经测量完了并且将测量的结果保存在View的mMeasuredWidth和mMeasuredHeight中,将标志位置为可以layout的状态

    mPrivateFlags |= PFLAG_LAYOUT_required;

  }
  mOldWidthMeasureSpec = widthMeasureSpec;
  mOldHeightMeasureSpec = heightMeasureSpec;
// 保存到缓存中 mMeasureCache.put(key,((
long) mMeasuredWidth) << 32 | (long) mMeasuredHeight & 0xffffffffL); suppress sign extension }

 这里要注意的是,这是一个 final 方法,不能被继承。这个方法只在 View 类里面。总结一下 measure() 都干了什么事:

  • 调用 View.measure()方法时 View 并不是立即就去测量,而是先判断一下要不要进行测量操作,如果没必要,那么 View 就不需要重新测量了,避免浪费时间资源

  • 如果需要测量,在测量之前,会先判断是否存在缓存,存在直接从缓存中获取就可以了,再调用一下 setMeasuredDimensionRaw 方法,将从缓存中读到的测量结果保存到成员变量 mMeasuredWidth 和 mMeasuredHeight 中。

  • 如果不能从 mMeasureCache 中读到缓存过的测量结果,调用 onMeasure() 方法去完成实际的测量工作,并且将尺寸限制条件 widthMeasureSpec 和 heightMeasureSpec 传递给 onMeasure() 方法。关于 onMeasure() 方法,会在下面详细介绍。

  • 将结果保存到 mMeasuredWidth 和 mMeasuredHeight 这两个成员变量中,同时缓存到成员变量 mMeasureCache 中,以便下次执行 measure() 方法时能够从其中读取缓存值。

  • 需要说明的是,View 有一个成员变量 mPrivateFlags,用以保存 View 的各种状态位,在测量开始前,会将其设置为未测量状态,在测量完成后会将其设置为已测量状态。

DecorView 是 FrameLayout 子类,这时候应该去看 FrameLayout 中的 onMeasure() 方法,代码具体如下:

 protected void onMeasure( heightMeasureSpec) {
     // 获取子view的个数
int count = getChildCount(); boolean measureMatchParentChildren = MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY || MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY; mMatchParentChildren.clear(); int maxHeight = 0int maxWidth = 0int childState = 0for (int i = 0; i < count; i++final View child = getChildAt(i);
       // mMeasureAllChildren 默认为FALSE,表示是否全部子 view 都要测量,子view不为GONE就要测量
if (mMeasureAllChildren || child.getVisibility() != GONE) {
// 测量子view measureChildWithMargins(child,widthMeasureSpec,
0,heightMeasureSpec,0);
// 获取子view的布局参数
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
// 记录子view的最大宽度和高度 maxWidth
= Math.max(maxWidth,child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin); maxHeight = Math.max(maxHeight,child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin); childState = combineMeasuredStates(childState,child.getMeasuredState());
// 记录所有跟父布局有着相同宽或高的子view
(measureMatchParentChildren) { if (lp.width == LayoutParams.MATCH_PARENT || lp.height == LayoutParams.MATCH_PARENT) { mMatchParentChildren.add(child); } } } } Account for padding too 子view的最大宽高计算出来后,还要加上父View自身的padding maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground(); maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground(); Check against our minimum height and width maxHeight = Check against our foreground''s minimum height and width final Drawable drawable = getForeground(); if (drawable != ) { maxHeight =     // 确定父 view 的宽高 setMeasuredDimension(resolveSizeAndState(maxWidth,childState),resolveSizeAndState(maxHeight,childState << MEASURED_HEIGHT_STATE_SHIFT)); count = mMatchParentChildren.size(); if (count > 1) { mMatchParentChildren.get(i); final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); childWidthMeasureSpec;
          // 如果子view的宽是MATCH_PARENT,那么宽度 = 父view的宽 - 父Padding - 子Margin
LayoutParams.MATCH_PARENT) { int width = Math.max(0,getMeasuredWidth() - getPaddingLeftWithForeground() - getPaddingRightWithForeground() - lp.leftMargin - lp.rightMargin); childWidthMeasureSpec = MeasureSpec.makeMeasureSpec( width,MeasureSpec.EXACTLY); } { childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,getPaddingLeftWithForeground() + getPaddingRightWithForeground() + lp.leftMargin + lp.rightMargin,lp.width); } childHeightMeasureSpec;
          // 同理
if (lp.height ==int height = Math.max(0 getPaddingBottomWithForeground() - lp.topMargin - lp.bottomMargin); childHeightMeasureSpec = MeasureSpec.makeMeasureSpec( height,1)"> { childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,getPaddingTopWithForeground() + getPaddingBottomWithForeground() + lp.topMargin + lp.bottomMargin,lp.height); } child.measure(childWidthMeasureSpec,childHeightMeasureSpec); } } }

FrameLayout 是 ViewGroup 的子类,后者有一个 View[] 类型的成员变量 mChildren,代表了其子 View 集合。通过 getChildAt(i) 能获取指定索引处的子 View,通过 getChildCount() 可以获得子 View 的总数。

在上面的源码中,首先调用 measureChildWithMargins() 方法对所有子 View 进行了一遍测量,并计算出所有子 View 的最大宽度和最大高度。而后将得到的最大高度和宽度加上padding,这里的 padding 包括了父 View 的 padding 和前景区域的 padding。然后会检查是否设置了最小宽高,并与其比较,将两者中较大的设为最终的最大宽高。最后,若设置了前景图像,我们还要检查前景图像的最小宽高。

经过了以上一系列步骤后,我们就得到了 maxHeight 和 maxWidth 的最终值,表示当前容器 View 用这个尺寸就能够正常显示其所有子 View(同时考虑了 padding 和 margin )。而后我们需要调用 resolveSizeAndState() 方法来结合传来的 MeasureSpec 来获取最终的测量宽高,并保存到 mMeasuredWidth 与 mMeasuredHeight 成员变量中。

如果存在一些子 View 的宽或高是 MATCH_PARENT,那么需要等父 View 的尺寸计算出来后,重新计算这些子 View 的 MeasureSpec,再来测量这些子 view 的宽高。

这里提醒我们在自定义 View 的时候需要考虑你的子 View 是不是和你自定义的 View 的大小是一样,如果一样,就需要等自定义 View 的大小确定了,再重新测量一遍。

下面看看 measureChildWithMargins() 方法具体逻辑:


     * Ask one of the children of this view to measure itself,taking into
     * account both the MeasureSpec requirements for this view and its padding
     * and margins. The child must have MarginLayoutParams The heavy lifting is
     * done in getChildMeasureSpec.
     *
     * @param child The child to measure
     *  parentWidthMeasureSpec The width requirements for this view
     *  widthUsed Extra space that has been used up by the parent
     *        horizontally (possibly by other children of the parent)
     *  parentHeightMeasureSpec The height requirements for this view
     *  heightUsed Extra space that has been used up by the parent
     *        vertically (possibly by other children of the parent)
     */
     measureChildWithMargins(View child,1)">int parentWidthMeasureSpec,1)"> widthUsed,1)">int parentHeightMeasureSpec,1)"> heightUsed) {
         (MarginLayoutParams) child.getLayoutParams();

        int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
                        +int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
                        + heightUsed,lp.height);

        child.measure(childWidthMeasureSpec,childHeightMeasureSpec);
    }

 该方法主要是获取子 View 的 MeasureSpec,然后调用 child.measure() 来完成子 View 的测量。下面看看子 View 获取 MeasureSpec 的具体逻辑:

 int getChildMeasureSpec(int spec,1)">int padding,1)"> childDimension) {
// 父 view 的 mode 和 size int specMode = MeasureSpec.getMode(spec); int specsize = MeasureSpec.getSize(spec);      // 去掉 padding int size = Math.max(0,specsize - padding); int resultSize = 0int resultMode = 0 (specMode) { Parent has imposed an exact size on us MeasureSpec.EXACTLY: if (childDimension >= 0) { resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { Child wants to be our size. So be it. resultSize = size; resultMode = LayoutParams.WRAP_CONTENT) { Child wants to determine its own size. It can''t be bigger than us. resultSize = MeasureSpec.AT_MOST; } Parent has imposed a maximum size on us MeasureSpec.AT_MOST: Child wants a specific size... so be it resultSize = Child wants to be our size,but our size is not fixed. Constrain child to not be bigger than us. resultSize = MeasureSpec.AT_MOST; } Parent asked to see how big we want to be MeasureSpec.UNSPECIFIED: Child wants a specific size... let him have it resultSize = Child wants to be our size... find out how big it should be resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size; resultMode = MeasureSpec.UNSPECIFIED; } Child wants to determine its own size.... find out how big it should be resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 MeasureSpec.UNSPECIFIED; } noinspection ResourceType MeasureSpec.makeMeasureSpec(resultSize,resultMode); }

 方法清楚展示了普通 View 的 MeasureSpec 的创建规则,每个 View 的 MeasureSpec 状态量由其直接父 View 的 MeasureSpec 和 View 自身的属性 LayoutParams (LayoutParams 有宽高尺寸值等信息)共同决定。

从上面的代码可以知道,返回 View 的 MeasureSpec 大致可以分为一下机制情况:
  • 子 View 为具体的宽/高,那么 View 的 MeasureSpec 都为 LayoutParams 中大小。

  • 子 View 为 match_parent,父元素为精度模式(EXACTLY),那么 View 的 MeasureSpec 也是精准模式他的大小不会超过父容器的剩余空间。

  • 子 View 为 wrap_content,不管父元素是精准模式还是最大化模式(AT_MOST),View 的 MeasureSpec 总是为最大化模式并且大小不超过父容器的剩余空间。

  • 父容器为 UNSPECIFIED 模式主要用于系统多次 Measure 的情形,一般我们不需要关心。

总结为下表:

 View.measure()  代码逻辑前面已经分析过了,最终会调用 onMeasuere 方法,下面看下 View.onMeasuere() 的代码:

 protected int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(),widthMeasureSpec),getDefaultSize(getSuggestedMinimumHeight(),heightMeasureSpec));
    }

上面方法中调用了 方法中调用了 setMeasuredDimension()方法,setMeasuredDimension()又调用了 getDefaultSize() 方法。getDefaultSize() 又调用了getSuggestedMinimumWidth()和 getSuggestedMinimumHeight(),那反向研究一下,先看下 getSuggestedMinimumWidth() 方法  (getSuggestedMinimumHeight() 原理 getSuggestedMinimumWidth() 跟一样)。 

  getSuggestedMinimumWidth() {
        return (mBackground == null) ? mMinWidth : max(mMinWidth,mBackground.getMinimumWidth());
    }

 源码很简单,如果 View 没有背景,就直接返回 View 本身的最小宽度 mMinWidth;如果给 View 设置了背景,就取 View 本身的最小宽度 mMinWidth 和背景的最小宽度的最大值.

那么 mMinWidth 是哪里来的?搜索下源码就可以知道,View 的最小宽度 mMinWidth 可以有两种方式进行设置:

  • 第一种是在 View 的构造方法中进行赋值的,View 通过读取 XML 文件中View设置的 minWidth 属性来为 mMinWidth 赋值
 R.styleable.View_minWidth:
     mMinWidth = a.getDimensionPixelSize(attr,1)">);
     break;
  •  第二种是在调用 View 的 setMinimumWidth 方法为 mMinWidth 赋值
void setMinimumWidth( minWidth) {
    mMinWidth = minWidth;
    requestLayout();
}

 下面看下 getDefaultSize() 的代码逻辑:

    int getDefaultSize(int size,1)"> measureSpec) {
        int result = size;
         MeasureSpec.getMode(measureSpec);
         MeasureSpec.getSize(measureSpec);

         MeasureSpec.UNSPECIFIED:
            result = size;
             MeasureSpec.AT_MOST:
         MeasureSpec.EXACTLY:
            result = specsize;
             result;
    }

从注释可以看出,getDefaultSize()这个测量方法并没有适配 wrap_content 这一种布局模式,只是简单地将 wrap_content 跟 match_parent 等同起来。

到了这里,我们要注意一个问题:

getDefaultSize()方法中 wrap_content 和 match_parent 属性的效果是一样的,而该方法是 View 的 onMeasure()中默认调用的,也就是说,对于一个直接继承自 View 的自定义 View 来说,它的 wrap_content 和 match_parent 属性是一样的效果,因此如果要实现自定义 View 的 wrap_content,则要重写 onMeasure() 方法,对 wrap_content 属性进行处理。

如何处理呢?也很简单,代码如下所示:

protected void onMeasure(int widthMeasureSpec,1)"> heightMeasureSpec){
  super.onMeasure(widthMeasureSpec, heightMeasureSpec);
  取得父ViewGroup指定的宽高测量模式和尺寸
  int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
  int widthSpecsize = MeasureSpec.getSize(widthMeasureSpec);
  int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
  int heightSpecsize = MeasureSpec.getSize(heightMeasureSpec);
  if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {
    如果宽高都是AT_MOST的话,即都是wrap_content布局模式,就用View自己想要的宽高值
        setMeasuredDimension(mWidth, mHeight);
  }else if (widthSpecMode ==如果只有宽度都是AT_MOST的话,即只有宽度是wrap_content布局模式,宽度就用View自己想要的宽度值,高度就用父ViewGroup指定的高度值
pecsize);
  }if (heightSpecMode ==如果只有高度都是AT_MOST的话,即只有高度是wrap_content布局模式,高度就用View自己想要的宽度值,宽度就用父ViewGroup指定的高度值
        setMeasuredDimension(widthSpecsize, mHeight);
  }
}

 在上面的代码中,我们要给 View 指定一个默认的内部宽/高(mWidth 和 mHeight),并在 wrap_content 时设置此宽/高即可。最后将在将宽高设置到 View 上:

    // View    
    protected void setMeasuredDimension(int measuredWidth,1)"> measuredHeight) {
        boolean optical = isLayoutModeOptical(this);
        if (optical != isLayoutModeOptical(mParent)) {
            Insets insets = getopticalInsets();
            int opticalWidth  = insets.left + insets.right;
            int opticalHeight = insets.top  + insets.bottom;
 
            measuredWidth  += optical ? opticalWidth  : -opticalWidth;
            measuredHeight += optical ? opticalHeight : -opticalHeight;
        }
        setMeasuredDimensionRaw(measuredWidth, measuredHeight);
    }

    void setMeasuredDimensionRaw( measuredHeight) {
        mMeasuredWidth = measuredWidth;
        mMeasuredHeight = measuredHeight;
        mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
    }

这里就是把测量完的宽高值赋值给 mMeasuredWidthmMeasuredHeight 这两个 View 的属性,然后将标志位置为已测量状态。

子 View 测量完成以后,会计算 childState,看下 combineMeasuredStates 方法 :

int combineMeasuredStates(int curState,1)"> newState) {
        return curState | newState;
    }

 当前 curState 为 0, newState 是调用 child.getMeasuredState() 方法得到的,来看下这个方法的具体逻辑:

 
     * Return only the state bits of {@link #getMeasuredWidthAndState()}
     * and { #getMeasuredHeightAndState()},combined into one integer.
     * The width component is in the regular bits { #MEASURED_STATE_MASK}
     * and the height component is at the shifted bits
     * { #MEASURED_HEIGHT_STATE_SHIFT}>>{ #MEASURED_STATE_MASK}.
      getMeasuredState() {
        return (mMeasuredWidth&MEASURED_STATE_MASK)
                | ((mMeasuredHeight>>MEASURED_HEIGHT_STATE_SHIFT)
                        & (MEASURED_STATE_MASK>>MEASURED_HEIGHT_STATE_SHIFT));
    }

 该方法返回一个 int 值,该值同时包含宽度的 state 以及高度的 state 信息,不包含任何的尺寸信息。

  • MEASURED_STATE_MASK 的值为 0xff000000,其高字节的 8 位全部为 1,低字节的 24 位全部为 0。

  • MEASURED_HEIGHT_STATE_SHIFT 值为 16。

  • MEASURED_STATE_MASK 与 mMeasuredWidth 做与操作之后就取出了存储在宽度首字节中的 state 信息,过滤掉低位三个字节的尺寸信息。

  • 由于 int 有四个字节,首字节已经存了宽度的 state 信息,那么高度的 state 信息就不能存在首位字节。MEASURED_STATE_MASK 向右移 16 位,变成了 0x0000ff00,这个值与高度值 mMeasuredHeight 做与操作就取出了 mMeasuredHeight 第三个字节中的信息。而 mMeasuredHeight 的 state 信息是存在首字节中,所以也得对mMeasuredHeight 向右移相同的位置,这样就把 state 信息移到了第三个字节中。

  • 最后,将得到的宽度 state 与高度 state 按位或操作,这样就拼接成一个 int 值,该值首个字节存储宽度的 state 信息,第三个字节存储高度的 state 信息。

这些都得到之后,就可以开始去计算父 View 的尺寸了:

        // 确定父 View 的宽高
        setMeasuredDimension(resolveSizeAndState(maxWidth,1)">resolveSizeAndState(maxHeight,childState << MEASURED_HEIGHT_STATE_SHIFT));

 下面开始看 resolveSizeAndState 具体逻辑:

 View 的静态方法
int resolveSizeAndState(int measureSpec,1)"> childMeasuredState) {
         MeasureSpec.getSize(measureSpec);
         result;
         (specMode) {
             MeasureSpec.AT_MOST:
                if (specsize < size) {
                    result = specsize | MEASURED_STATE_TOO_SMALL;
                }  {
                    result = size;
                }
                 MeasureSpec.EXACTLY:
                result = specsize;
                :
                result = size;
        }
        return result | (childMeasuredState & MEASURED_STATE_MASK);
    }

这个方法的代码结构跟前文提到的 getDefaultSize()方法很相似,主要的区别在于 specMode 为 AT_MOST 的情况。我们当时说 getDefaultSize() 方法是没有适配wrap_content 这种情况,而这个 resolveSizeAndState() 方法是已经适配了 wrap_content 的布局方式,那具体怎么实现 AT_MOST 测量逻辑的呢?有两种情况:

  • 当父 ViewGroup 指定的最大尺寸比 View 想要的尺寸还要小时,会给这个父 ViewGroup  的指定的最大值 specsize 加入一个尺寸太小的标志  MEASURED_STATE_TOO_SMALL,然后将这个带有标志的尺寸返回,父 ViewGroup 通过该标志就可以知道分配给 View 的空间太小了,在窗口协商测量的时候会根据这个标志位来做窗口大小的决策。

  • 当父 ViewGroup 指定的最大尺寸比没有比 View 想要的尺寸小时(相等或者 View 想要的尺寸更小),直接取 View 想要的尺寸,然后返回该尺寸。

getDefaultSize() 方法只是 onMeasure() 方法中获取最终尺寸的默认实现,其返回的信息比 resolveSizeAndState() 要少,那么什么时候才会调用 resolveSizeAndState() 方法呢? 主要有两种情况:

  • Android 中的大部分 ViewGroup 类都调用了 resolveSizeAndState() 方法,比如 LinearLayout 在测量过程中会调用 resolveSizeAndState() 方法而非 getDefaultSize()方法。

  • 我们自己在实现自定义的 View 或 ViewGroup 时,我们可以重写 onMeasure() 方法,并在该方法内调用 resolveSizeAndState() 方法。

到此,终于把 View 测量过程讲完了。

下一篇开始讲 View 的 layout 和 draw 过程。

 

参考文章

Android源码完全解析——View的Measure过程

View的绘制流程 

总结

以上是小编为你收集整理的Android View 的绘制流程之 Measure 过程详解 (一) Android View 绘制流程之 DecorView 与 ViewRootImplAndroid View 的绘制流程之 Measure 过程详解 (一)Android View 的绘制流程之 Layout 和 Draw 过程详解 (二)Android View 的事件分发原理解析Android 自定义 View 详解Android View 绘制流程之 DecorView 与 ViewRootImp全部内容。

如果觉得小编网站内容还不错,欢迎将小编网站推荐给好友。

今天关于从 Swift 中的闭包返回多个 View 对象swift 闭包作为函数入参的讲解已经结束,谢谢您的阅读,如果想了解更多关于ABAP webdynpro 的 view navigation 和 WebUI 的 view navigation、Android View 源码解析 (三) - View 的绘制过程、Android View 的绘制流程之 Layout 和 Draw 过程详解 (二) Android View 绘制流程之 DecorView 与 ViewRootImplAndroid View 的绘制流程之 Measure 过程详解 (一)Android View 的绘制流程之 Layout 和 Draw 过程详解 (二)Android View 的事件分发原理解析Android 自定义 View 详解Android View 的绘制流程之 Measure 过程详解 (一)、Android View 的绘制流程之 Measure 过程详解 (一) Android View 绘制流程之 DecorView 与 ViewRootImplAndroid View 的绘制流程之 Measure 过程详解 (一)Android View 的绘制流程之 Layout 和 Draw 过程详解 (二)Android View 的事件分发原理解析Android 自定义 View 详解Android View 绘制流程之 DecorView 与 ViewRootImp的相关知识,请在本站搜索。

本文标签: