View的绘制流程二 requestLayout

发布时间:2024年01月20日

view绘制的起点---》ViewRootImpl的rerquestLayout方法

检查调用方法的线程是否与创建ViewRootImpl的线程相同

@Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }

void checkThread() {
        if (mThread != Thread.currentThread()) {
            throw new CalledFromWrongThreadException(
                    "Only the original thread that created a view hierarchy can touch its views.");
        }
    }

在ViewRootImpl里面的requestLayout方法,首先会检查当前的线程是否和创建ViewRootImpl的线程是一致的,如果不是一致的会报错。

发送屏障消息,异步消息等待屏幕刷新信号到来开始真正绘制流程

接下来看scheduleTraversals方法:

    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

 final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }

 void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

            if (mProfile) {
                Debug.startMethodTracing("ViewAncestor");
            }

            performTraversals();

            if (mProfile) {
                Debug.stopMethodTracing();
                mProfile = false;
            }
        }
    }

这边先发送了一个屏障消息,异步消息。然后等待屏幕刷新信号到来之时,移除屏障消息,执行performTraversals()开启三大流程。

performTraversals()开启三大流程

private void performTraversals() {
    ...
    //之前记录的Window LayoutParams
    WindowManager.LayoutParams lp = mWindowAttributes;

    //Window需要的大小
    int desiredWindowWidth;
    int desiredWindowHeight;
    ...

    Rect frame = mWinFrame;
    if (mFirst) {
        ...
        if (shouldUseDisplaySize(lp)) {
            ...
        } else {
            //mWinFrame即是之前添加Window时返回的Window最大尺寸
            desiredWindowWidth = mWinFrame.width();
            desiredWindowHeight = mWinFrame.height();
        }
        ...
    } else {
        ...
    }

    ...
    if (layoutRequested) {
        ...
        //ViewTree的预测量 -----------(1)
        windowSizeMayChange |= measureHierarchy(host, lp, res,
                desiredWindowWidth, desiredWindowHeight);
    }
    ...

    if (mFirst || windowShouldResize || insetsChanged ||
            viewVisibilityChanged || params != null || mForceNextWindowRelayout) {
        ...
        try {
            ...
            //对window进行布局(宽高) --------(2)
            relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
            ...
        } catch (RemoteException e) {
        }
        ...
        if (!mStopped || mReportNextDraw) {
            ...
            if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
                    || mHeight != host.getMeasuredHeight() || contentInsetsChanged ||
                    updatedConfiguration) {
                ...
                //再次测量ViewTree -------- (3)
                performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
                ...
            }
        }
    } else {
        ...
    }
    ...
    if (didLayout) {
        //对ViewTree 进行Layout ---------- (4)
        performLayout(lp, mWidth, mHeight);
        ...
    }
    ...
    if (!cancelDraw) {
        ...
        //开始ViewTree Draw过程 ------- (5)
        performDraw();
    } else {
        ...
    }
}
预测量measureHierarchy()
  private boolean measureHierarchy(final View host, final WindowManager.LayoutParams lp,
            final Resources res, final int desiredWindowWidth, final int desiredWindowHeight) {
        int childWidthMeasureSpec;// 合成后的用于描述宽度的MeasureSpec
        int childHeightMeasureSpec;// 合成后的用于描述高度的MeasureSpec
        boolean windowSizeMayChange = false;// 表示测量结果是否可能导致窗口的尺寸发生变化

        if (DEBUG_ORIENTATION || DEBUG_LAYOUT) Log.v(mTag,
                "Measuring " + host + " in display " + desiredWindowWidth
                + "x" + desiredWindowHeight + "...");

        boolean goodMeasure = false;// 表示测量能否满足控件树充分显示内容的要求
        // 测量协商仅发生在LayoutParams.width被指定为WRAP_CONTENT的情况下
        if (lp.width == 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();
            /* 第一次协商。measureHierarchy()使用它最期望的宽度限制进行测量。这一宽度限制定义为一个系统资源。
            可以在frameworks/base/core/res/res/values/config.xml找到它的定义,320dp */
            res.getValue(com.android.internal.R.dimen.config_prefDialogWidth, mTmpValue, true);
            int baseSize = 0;
            // 宽度限制被存放在baseSize中
            if (mTmpValue.type == TypedValue.TYPE_DIMENSION) {
                baseSize = (int)mTmpValue.getDimension(packageMetrics);
            }
            if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": baseSize=" + baseSize
                    + ", desiredWindowWidth=" + desiredWindowWidth);
            if (baseSize != 0 && desiredWindowWidth > baseSize) {
                // 使用getRootMeasureSpec()函数组合SPEC_MODE与SPEC_SIZE为一个MeasureSpec
                childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
                childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
                // 第一次测量。由performMeasure()方法完成
                performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
                if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": measured ("
                        + host.getMeasuredWidth() + "," + host.getMeasuredHeight()
                        + ") from width spec: " + MeasureSpec.toString(childWidthMeasureSpec)
                        + " and height spec: " + MeasureSpec.toString(childHeightMeasureSpec));
                /* 控件树的测量结果可以通过mView的getmeasuredWidthAndState()方法获取。
                如果控件树对这个测量结果不满意,则会在返回值中添加MEASURED_STATE_TOO_SMALL位 */
                if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) {
                    goodMeasure = true;// 控件树对测量结果满意,测量完成
                } else {
                    // Didn't fit in that size... try expanding a bit.
                    /* 第二次协商。上次测量结果表明控件树认为measureHierarchy()给予的宽度太小,
                    在此适当地放宽对宽度的限制,使用最大宽度与期望宽度的中间值作为宽度限制 */
                    baseSize = (baseSize+desiredWindowWidth)/2;
                    if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": next baseSize="
                            + baseSize);
                    childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
                    // 第二次测量
                    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
                    if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": measured ("
                            + host.getMeasuredWidth() + "," + host.getMeasuredHeight() + ")");
                    // 再次检查控件树是否满足此次测量
                    if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) {
                        if (DEBUG_DIALOG) Log.v(mTag, "Good!");
                        goodMeasure = true;//控件树对测量结果满意,测量完成
                    }
                }
            }
        }

        if (!goodMeasure) {
            /* 当控件树对上述两次协商的结果都不满意时,measureHierarchy()放弃所有限制做最终测量。
            这一次将不再检查控件树是否满意了,因为即便其不满意,measurehierarchy()也没有更多的空间供其使用了 */
            childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
            childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
            performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
            /* 最后,如果测量结果与ViewRootImpl中当前的窗口尺寸不一致,则表明随后可能有必要进行窗口尺寸的调整 */
            if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()) {
                windowSizeMayChange = true;
            }
        }

        if (DBG) {
            System.out.println("======================================");
            System.out.println("performTraversals -- after measure");
            host.debug();
        }

        // 返回窗口尺寸是否可能需要发生变化
        return windowSizeMayChange;
    }
预测量次数?

在开始view树的真正绘制流程之前,会对整个viewTree进行预测量。首先判定DecorView的LayoutParams进行一次判断,如果是WRAP_CONTENT的话,会先给一个宽度给到DecorView,然后去测量整个viewTree,看看这个宽度够不够。如果不够的话,再把宽度扩大一点,再给DecorView,通过调用performMeasure方法去测量整个viewTree树。如果宽度还是不够的话,把屏幕的全部宽度都给Decorview,再去测量整个viewTree树。这时候宽度够不够都不管了,已经把全部的宽度都交出去了。

1.判断DecorView的LayoutParams是否为WRAP_CONTENT

2.满足条件的话,先给一个宽度给到DecorView,然后去测量整个viewTree,看看这个宽度够不够。不满足条件的话直接把最大值给DecorView。

3.宽度不够再把宽度扩大一点,再给DecorView,通过调用performMeasure方法去测量整个viewTree树。

4.如果宽度还是不够的话,把屏幕的全部宽度都给Decorview,再去测量整个viewTree树。

所以在预测量measureHierarchy方法里面最少都要测量一次,最多是三次。

DecorView的MeasureSpec怎么来的

在预测量方法里面有一个getRootMeasureSpec方法,这个方法是确定DecorView的宽高和具体测量模式的。

 private static int getRootMeasureSpec(int windowSize, int measurement, int privateFlags) {
        int measureSpec;
        final int rootDimension = (privateFlags & PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT) != 0
                ? MATCH_PARENT : measurement;
        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;
    }

可以看到如果是MATCH_PARENT,直接就是窗口的宽度;如果是WRAP_CONTENT,DecorView的模式是MeasureSpec.AT_MOST;如果是其他情况,说明DecorView的宽度是一个固定值,直接给固定值就行了。

真正测量ViewTree的函数performMeasure

我们测量ViewTree的函数是performMeasure。我们看到在performTraversals中执行完预测量后会执行performMeasure方法:

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

这里的mView显然就是我们保存在ViewRootImpl里面的DecorView,也就是说ViewRootImpl里面的performMeasure执行了DecorView的measure方法。

DecorView的父类是FrameLayout,FrameLayout里面没有重写measure方法,ViewGroup也没有重写measure方法,那measure方法一定是View里面的了:

    public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
        boolean optical = isLayoutModeOptical(this);
        if (optical != isLayoutModeOptical(mParent)) {
            Insets insets = getOpticalInsets();
            int oWidth  = insets.left + insets.right;
            int oHeight = insets.top  + insets.bottom;
            widthMeasureSpec  = MeasureSpec.adjust(widthMeasureSpec,  optical ? -oWidth  : oWidth);
            heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);
        }

        // Suppress sign extension for the low bytes
        long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;
        if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);

        final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;

        // Optimize layout by avoiding an extra EXACTLY pass when the view is
        // already measured as the correct size. In API 23 and below, this
        // extra pass is required to make LinearLayout re-distribute weight.
        final boolean specChanged = widthMeasureSpec != mOldWidthMeasureSpec
                || heightMeasureSpec != mOldHeightMeasureSpec;
        final boolean isSpecExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY
                && MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;
        final boolean matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec)
                && getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);
        final boolean needsLayout = specChanged
                && (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize);

         /* 仅当给予的MeasureSpec发生变化,或要求强制重新布局时,才会进行测量。        
         所谓强制重新布局,是指当控件树中的一个子控件的内容发生变化时,需要进行重新的测量和布局的情况。
         在这种情况下,这个子控件的父控件(以及其父控件的父控件)所提供的MeasureSpec必定与上次测量        
         时的值相同,因而导致从ViewRootImpl到这个控件的路径上的父控件的measure()方法无法得到执行,        
         进而导致子控件无法重新测量其尺寸或布局。因此,当子控件因内容发生变化时,从子控件沿着控件树回溯        
         到ViewRootImpl,并依次调用沿途父控件的requestLayout()方法。这个方法会在mPrivateFlags中        
         加入标记PFLAG_FORCE_LAYOUT,从而使得这些父控件的measure()方法得以顺利执行,        
         进而这个子控件有机会进行重新测量与布局。这便是强制重新布局的意义 */
        if (forceLayout || needsLayout) {
            // first clears the measured dimension flag
            /* 准备工作。从mPrivateFlags中将PFLAG_MEASURED_DIMENSION_SET标记去除。            
            PFLAG_MEASURED_DIMENSION_SET标记用于检查控件在onMeasure()方法中是否通过            
            调用setMeasuredDimension()将测量结果存储下来 */
            mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;

            resolveRtlPropertiesIfNeeded();

            int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
            if (cacheIndex < 0 || sIgnoreMeasureCache) {
                // measure ourselves, this should set the measured dimension flag back
                /* 对本控件进行测量,每个View子类都需要重载这个方法以便正确地对自身进行测量。                
                View类的onMeasure()方法仅仅根据背景Drawable或style中设置的最小尺寸作为测量结果*/
                onMeasure(widthMeasureSpec, heightMeasureSpec);
                mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            } else {
                long value = mMeasureCache.valueAt(cacheIndex);
                // Casting a long to int drops the high 32 bits, no mask needed
                setMeasuredDimensionRaw((int) (value >> 32), (int) value);
                mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            }

            // flag not set, setMeasuredDimension() was not invoked, we raise
            // an exception to warn the developer
            /* 检查onMeasure()的实现是否调用了setMeasuredDimension(),            
            setMeasuredDimension()会将PFLAG_MEASURED_DIMENSION_SET标记重新加入mPrivateFlags中。            
            之所以做这样的检查,是由于onMeasure()的实现可能由开发者完成,而在Android看来,开发者是不可信的 */
            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()");
            }

            // 将PFLAG_LAYOUT_REQUIRED标记加入mPrivateFlags。这一操作会对随后的布局操作放行
            mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
        }

        // 记录父控件给予的MeasureSpec,用以检查之后的测量操作是否有必要进行
        mOldWidthMeasureSpec = widthMeasureSpec;
        mOldHeightMeasureSpec = heightMeasureSpec;

        mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
                (long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
    }

这个方法里面比较重要的是onMeasure(widthMeasureSpec, heightMeasureSpec)这个方法:

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

onMeasure实现对本控件进行测量,onMeasure方法在FrameLayout里面是有重写的:

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int count = getChildCount();

        //如果FrameLayout宽高不是精确值的,需要再测量一次match_parent的子视图
        final boolean measureMatchParentChildren =
                MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
                MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
        mMatchParentChildren.clear();

        int maxHeight = 0;
        int maxWidth = 0;
        int childState = 0;

        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            if (mMeasureAllChildren || child.getVisibility() != GONE) {
                //测量子视图,记录最大宽高
                measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                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());
                //如果需要测量match_parent宽或高的子视图,将match_parent子视图添加到集合
                if (measureMatchParentChildren) {
                    if (lp.width == LayoutParams.MATCH_PARENT ||
                            lp.height == LayoutParams.MATCH_PARENT) {
                        mMatchParentChildren.add(child);
                    }
                }
            }
        }

        // Account for padding too
        maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
        maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();

        // Check against our minimum height and width
        maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
        maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());

        // Check against our foreground's minimum height and width
        final Drawable drawable = getForeground();
        if (drawable != null) {
            maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
            maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
        }

        //完成测量
        setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
                resolveSizeAndState(maxHeight, heightMeasureSpec,
                        childState << MEASURED_HEIGHT_STATE_SHIFT));

        //读取需要再测量的子视图数量
        count = mMatchParentChildren.size();
        // count 需要大于1
        if (count > 1) {
            for (int i = 0; i < count; i++) {
                final View child = mMatchParentChildren.get(i);
                final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

                final int childWidthMeasureSpec;
                //如果是match_parent,测量子视图时使用精确模式+可用宽度
                //下面的高度也是差不多
                if (lp.width == LayoutParams.MATCH_PARENT) {
                    final int width = Math.max(0, getMeasuredWidth()
                            - getPaddingLeftWithForeground() - getPaddingRightWithForeground()
                            - lp.leftMargin - lp.rightMargin);
                    childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
                            width, MeasureSpec.EXACTLY);
                } else {
                    childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
                            getPaddingLeftWithForeground() + getPaddingRightWithForeground() +
                            lp.leftMargin + lp.rightMargin,
                            lp.width);
                }

                final int childHeightMeasureSpec;
                if (lp.height == LayoutParams.MATCH_PARENT) {
                    final int height = Math.max(0, getMeasuredHeight()
                            - getPaddingTopWithForeground() - getPaddingBottomWithForeground()
                            - lp.topMargin - lp.bottomMargin);
                    childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
                            height, MeasureSpec.EXACTLY);
                } else {
                    childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
                            getPaddingTopWithForeground() + getPaddingBottomWithForeground() +
                            lp.topMargin + lp.bottomMargin,
                            lp.height);
                }

                child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
            }
        }
    }

可以看到在FrameLayout的onMeasue里面,调用setMeasuredDimension完成对自己的测量之后,还有循环调用子View的measure方法。如果子View是一个ViewGroup,又会循环调用它的子View的measure直到测完viewTree顶部的所有子View。

真正布局ViewTree的函数performLayout

private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
        int desiredWindowHeight) {
    ...
    final View host = mView;
    ...
    try {
        ...
        host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
        ...
    } ...
}

这个host依旧是DecorView,我们到View里面去看看:

public void layout(int l, int t, int r, int b) {
  ...
  int oldL = mLeft;
  int oldT = mTop;
  int oldB = mBottom;
  int oldR = mRight;
  boolean changed = isLayoutModeOptical(mParent) ? setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
  ...
  if (...) {
    onLayout(changed, l, t, r, b);
    ...
  }
  ...
}

setFrame(l, t, r, b) 可以理解为给 mLeft , mTop, mRight, mBottom 这四个变量赋值, 然后基本就能确定当前 View 在父视图的位置了。这几个值构成的矩形区域就是当前 View 显示的位置,这里的具体位置都是相对与父视图的位置。

onLayout方法在FrameLayout被重写:

@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    layoutChildren(left, top, right, bottom, false /* no force left gravity */);
}

void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
  final int count = getChildCount();
    ...
  for (int i = 0; i < count; i++) {
      final View child = getChildAt(i);
      if (child.getVisibility() != GONE) {
          ...
          child.layout(childLeft, childTop, childLeft + width, childTop + height);
      }
  }
}

到这个时候就知道了,在FrameLayout通过layout确认自己的位置,然后在onLayout里面循环子View确定子View相对于自己的位置。

真正绘制ViewTree的函数performDraw

执行完viewTree的测量和布局后,大小和位置都固定了,接下来就是执行performDraw方法对整个viewTree进行绘制了。

private void performDraw() {
  ...
  boolean canUseAsync = draw(fullRedrawNeeded, usingAsyncReport && mSyncBuffer);
  ...
}


private boolean draw(boolean fullRedrawNeeded, boolean forceDraw) {
  ........
  if(){
       //硬件绘制,效果更好
       mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);
   }else{
       //软件绘制
      drawSoftware(surface, mAttachInfo, xOffset, yOffset,
                        scalingRequired, dirty, surfaceInsets)
    }
 ........
}

performDraw方法触发draw方法,draw里面根据条件选择触发硬件绘制和软件绘制。硬件绘制效果会更加好一点。

我们来看软件绘制流程:

private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff, boolean scalingRequired, Rect dirty) {
  // Draw with software renderer.
  final Canvas canvas;
  ...
  mView.draw(canvas);
  ...
}

?软件绘制流程调用native方法创建了一个canvas,然后调用DecorView的draw方法。实际上还是会跑到View的draw方法里面:

public void draw(Canvas canvas) {
        final int privateFlags = mPrivateFlags;
        mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;

        /*
         * Draw traversal performs several drawing steps which must be executed
         * in the appropriate order:
         *
         *      1. Draw the background
         *      2. If necessary, save the canvas' layers to prepare for fading
         *      3. Draw view's content
         *      4. Draw children
         *      5. If necessary, draw the fading edges and restore layers
         *      6. Draw decorations (scrollbars for instance)
         *      7. If necessary, draw the default focus highlight
         */

        // Step 1, draw the background, if needed
        int saveCount;

        drawBackground(canvas);

        // skip step 2 & 5 if possible (common case)
        final int viewFlags = mViewFlags;
        boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
        boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
        if (!verticalEdges && !horizontalEdges) {
            // Step 3, draw the content
            onDraw(canvas);

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

            drawAutofilledHighlight(canvas);

            // Overlay is part of the content and draws beneath Foreground
            if (mOverlay != null && !mOverlay.isEmpty()) {
                mOverlay.getOverlayView().dispatchDraw(canvas);
            }

            // Step 6, draw decorations (foreground, scrollbars)
            onDrawForeground(canvas);

            // Step 7, draw the default focus highlight
            drawDefaultFocusHighlight(canvas);

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

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

        }

在View的draw方法里面,会先绘制背景图片,如果没有就不绘制;接着调用onDraw方法,View 的 onDraw(canvas) 是空方法,因为每个 View 的内容是各不相同的,所以需要由子类去实现具体逻辑。接着执行dispatchDraw方法,这个方法在View中是一个空实现,但是在ViewGroup里面有具体的实现:

@Override
protected void dispatchDraw(Canvas canvas) {
    ...
    final int childrenCount = mChildrenCount;
    final View[] children = mChildren;
    ...
    for (int i = 0; i < childrenCount; i++) {
        ...
        if (...) {
            more |= drawChild(canvas, child, drawingTime);
        }
        ...
    }
    ...
}


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

可以看到在ViewGroup里面又循环执行子View的三参的draw方法:

boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
    ...
            // Fast path for layouts with no backgrounds
            if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
                //跳过当前 View 的绘制, 直接绘制子 View.
                mPrivateFlags &= ~PFLAG_DIRTY_MASK;
                dispatchDraw(canvas);
            } else {
                draw(canvas);
            }
    ...
    return more;
}

?我们再来看看viewGroup的构造函数里面:

public ViewGroup(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);

        initViewGroup();
        initFromAttributes(context, attrs, defStyleAttr, defStyleRes);
    }

    private void initViewGroup() {
        // ViewGroup doesn't draw by default
        if (!isShowingLayoutBounds()) {
            setFlags(WILL_NOT_DRAW, DRAW_MASK);
        }
        ......
    }

原来在ViewGroup的构造函数里面已经设置了这些参数,默认让他不执行draw。?

?从这块代码可以看到如果当前的ViewGroup没有backgrounds的话,是不会执行draw绘制自己的,直接执行dispatchDraw绘制子View。

也就是说,从DecorView开始,执行draw方法,先绘制自己,然后循环自己的所有的子view,如果子View是ViewGroup,那么默认直接绘制这个ViewGroup的子View,否则执行draw方法。

viewGroup为什么默认不执行onDraw方法

ViewGroup的构造函数:

public ViewGroup(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);

        initViewGroup();
        initFromAttributes(context, attrs, defStyleAttr, defStyleRes);
    }

    private void initViewGroup() {
        // ViewGroup doesn't draw by default
        if (!isShowingLayoutBounds()) {
            setFlags(WILL_NOT_DRAW, DRAW_MASK);
        }
        ......
    }

在ViewGroup的父类执行draw方法,最终会执行到当前ViewGroup的三参的draw方法,这个份方法里面判断了WILL_NOT_DRAW参数,命中则不会执行onDraw,直接执行dispatchDraw绘制子View。

怎么解决
view.java
    /**
     * 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);
    }
 
    /**
     * Returns whether or not this View draws on its own.
     *
     * @return true if this view has nothing to draw, false otherwise
     */
    @ViewDebug.ExportedProperty(category = "drawing")
    public boolean willNotDraw() {
        return (mViewFlags & DRAW_MASK) == WILL_NOT_DRAW;
    }

?View类里暴露了设置WILL_NOT_DRAW标记的接口:setWillNotDraw(boolean willNotDraw),可以在? 继承ViewGroup的自定义View? 里使用setWillNotDraw(false)。

view.java
public void setBackgroundDrawable(Drawable background) {
if (background != null) {
   if ((mPrivateFlags & PFLAG_SKIP_DRAW) != 0) {
                mPrivateFlags &= ~PFLAG_SKIP_DRAW;
                requestLayout = true;
            }
     }
}
 
public void setForeground(Drawable foreground) {
        if (foreground != null) {
            if ((mPrivateFlags & PFLAG_SKIP_DRAW) != 0) {
                mPrivateFlags &= ~PFLAG_SKIP_DRAW;
            }
    }
}
 
private void setDefaultFocusHighlight(Drawable highlight) {
        mDefaultFocusHighlight = highlight;
        mDefaultFocusHighlightSizeChanged = true;
        if (highlight != null) {
            if ((mPrivateFlags & PFLAG_SKIP_DRAW) != 0) {
                mPrivateFlags &= ~PFLAG_SKIP_DRAW;
            }
      }
    }

background:view背景

foreground(mDrawable字段):view前景

focusHighLight:view获得焦点时高亮

我们调用这三个方法也行。

总结
  1. 若要ViewGroup onDraw()执行,只需要setWillNotDraw(false)、设置背景、设置前景、设置焦点高亮,4个选项其中一项满足即可。
  2. 当然如果不想在 继承ViewGroup的自定义View onDraw里绘制,也可以重写ViewGroup dispatchDraw()方法,在该方法里绘制 自定义View 内容。
    ?

文章来源:https://blog.csdn.net/qq_42426725/article/details/135715241
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。