转载请注明出处 http://blog.csdn.net/yxhuang2008/article/details/51126616
ViewRoot 对应 ViewRootImpl 类,它是连接 WindowManager 和 DecorView 的纽带,View 的三大流程都是通过 ViewRoot 来完成的。在ActivityThread 中,当 Activity 对象被创建完毕后,会将 DecorView 添加到 Window 中,同时会创建 ViewRoot 对象。
DecorView 添加到窗口 Window 的过程。
图片来自 https://yq.aliyun.com/articles/3005
View 的绘制流程从 ViewRootImpl 的 preformTraversals 开始,下面是它的伪码
// ViewRootImple#performTraverals 的伪代码 private void preformTraverals(){ preformMeasure(...) --------- View.measure(...); performLayout(...) --------- View.layout(...); performDraw(...) -------- View.draw(...); }
.onMeasure(int widthMeasureSpec, int heightMeasureSpec)
该方法就是我们只自定义 View 的时候需要实现测量绘制逻辑的方法,该方法的参数是父视图对子视图的 widht 和height 测量方法的要求。在自定义 View 时,需要做的就是更加 widthMeasureSpec 和 heightMeasureSpec 计算View 的width 和 height ,不同的处理模式不同。
.setMeasuredDimension(int measuredWidth, int measureHeith)
测量阶段的终极方法,在 onMeasure(int widthMeasureSpec, int heightMeasureSpece) 方法中调用,将计算的得到尺寸传递给该方法,测量阶段结束。该方法必须调用,否则会报异常。在自定义 View 的时候,不需要关系系统复杂的 Measure 过程,只需调用setMeasuredDimension(int measuredWith, int measuredHeith) 设置根据 MeasureSpec计算得到的尺寸即可。
(2)measure 过程
Measure 过程传递尺寸的两个参数
ViewGroup.LayoutParams View 自身的布局参数;
MeasureSpec 类, 父视图对子视图的测量要求。
View 的 measure 过程
View 的 getDefaultSize 方法
// View#getDefaultSize 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; }从 getDefaultSize 方法可以看出, View 的宽高是由 specSize 决定的。
直接继承 View 的自定义控件需要重写 onMeasure(...) 方法并设置 wrap_content 时的自身大小,否则在布局中使用 wrap_content 相当于使用 match_parent. 从 MeasureSpece 和 LayoutParams 关系表格中可看出。
解决方法,给 View 指定一个默认的内部宽高(mWith 和 mHeight),并在 wrap_content 时设置此宽高即可。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){ super.onMeasure(widthMeasureSpec, heightMeasureSpec); int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); int heightSpeceMode = MeasureSpec.getMode(heightMeasureSpec); int widthSpeceSize = View.MeasureSpec.getSize(widthMeasureSpec); int heightSepceSize = View.MeasureSpec.getSize(heightMeasureSpec); if (widthMeasureSpec == MeasureSpec.AT_MOST && heightMeasureSpec == MeasureSpec.AT_MOST){ // 设置一个默认的宽高 setMeasuredDimension(mWidth, mHeight); } else if (widthSpecMode == View.MeasureSpec.AT_MOST){ setMeasuredDimension(mWidth, heightSepceSize); } else if (heightSpeceMode == View.MeasureSpec.AT_MOST){ setMeasuredDimension(widthSpeceSize, mHeight); } }
ViewGroup 的 measure 过程.
ViewGroup 除了完成自己的 measure 过程以为,还会遍历去调用所有子元素的 measure 方法。 ViewGroup 是一个抽象类,没有重写 View 的 onMeasure 方法。ViewGroup 也没有定义其测量的具体过程,其测量过程的 onMeasure 方法需要各个之类去实现。
measure 完成以后,可以通过 getMeasureWidth / getMeasureHeight 获取 View 的测量宽高, 要在 onLayout 方法中去获取 View 的测量宽高或者最终宽高。
因为 View 的 measure 过程和 Activity 的生命周期方法不是同步的,因此无法保证 Activity 在 onCreate, onStart, onResume 方法中获取 View 的宽高信息。
解决办法:
1. 在 Activity/View # onWindowFoucsChanged 方法中
@Override public void onWindowFocusChanged(boolean hasFocus) { super.onWindowFocusChanged(hasFocus); if (hasFocus){ int width = view.getMeasuredWidth(); int height = view.getMeasuredHeight(); } }
2. 使用 view.post(Runnable)
@Override protected void onStart() { super.onStart(); mTextView.post(new Runnable() { @Override public void run() { int width = view.getMeasuredWidth(); int height = view.getMeasuredHeight(); } }); }
3.使用 ViewTreeObserver
@Override protected void onStart() { super.onStart(); ViewTreeObserver observer = view.getViewTreeObserver(); observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @SuppressWarnings("deprecation") @Override public void onGlobalLayout() { view.getViewTreeObserver().removeGlobalOnLayoutListener(this); int widht = view.getMeasuredWidth(); int height = view.getMeasuredHeight(); } }); }
4. 使用 View.measure(int widthMeasureSpec, int heightMeasureSpec)
通过手动对 View 进行 measure 得到 View 的宽高。
View 的 LayoutParams 分:
match_parent:
无法测出;
具体数值(dp/px):
例如宽高都是 100px 时
int widthMesureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY); int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY); view.measure(widthMesureSpec, heightMeasureSpec);
wrap_parent 时
int widthMesureSpec = View.MeasureSpec.makeMeasureSpec((1 << 30) -1, View.MeasureSpec.AT_MOST); int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec((1 << 30) -1, View.MeasureSpec.AT_MOST); view.measure(widthMesureSpec, heightMeasureSpec);
子视图的具体位置是相对于父视图而言的。View 的 onLayout 方法时空方法,ViewGrop 的 onLayout 方法时 abstract .
如果自定义的 View 继承 ViewGroup ,需要实现 onLayout 方法。
// View#layout 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; } int oldL = mLeft; int oldT = mTop; int oldB = mBottom; int oldR = mRight; // setOpticalFrame / setFrame 设定 View 的四个顶点 boolean changed = isLayoutModeOptical(mParent) ? setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b); if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) { // 空方法 onLayout(changed, l, t, r, b); ... } ... }
getMeasureWidth 和 getWidth 之间的区别:
getMeasureWidth 是 measure() 过程之后获取后,getWidth 是在 layout() 过程之后得到的。getMeasureWidth() 方法中的值是通过 setMeasureDimension() 方法类进行设置的,而 getWidth() 方法中的值是通过视图右边的坐标减去左边的坐标计算出来的。
// View#draw public void draw(Canvas canvas) { final int privateFlags = mPrivateFlags; final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE && (mAttachInfo == null || !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 int saveCount; if (!dirtyOpaque) { // 第一步,绘制背景 drawBackground(canvas); } // 正常情况下,跳过第二步和第五步 // skip step 2 & 5 if possible (common case) final int viewFlags = mViewFlags; boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0; boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0; if (!verticalEdges && !horizontalEdges) { // Step 3, draw the content // 第三步, 绘制自身内容 if (!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) // 第六步, 绘制 foreground, scrollbars onDrawForeground(canvas); // we're done... return; } }在 不需要绘制 layer 的时候会跳过第二步和第五步。因此在绘制的时候,能不绘制 layer 就尽量不绘制 layer, 以提高绘制效率。
setWillNotDraw
/** * If this view doesn't do any drawing on its own, set this flag to * allow further optimizations. By default, this flag is not set on * View, but could be set on some View subclasses such as ViewGroup. * * Typically, if you override {@link #onDraw(android.graphics.Canvas)} * you should clear this flag. * * @param willNotDraw whether or not this View draw on its own */ public void setWillNotDraw(boolean willNotDraw) { setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK); }如果一个 View 不需要绘制任何内容,则将这个标志位设置为 true, 系统会进行相应的优化。当我们在自定义控件继承 ViewGroup 并且本身不具备绘制功能时,可以开启这个标志位从而便于系统进行后续的优化。