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


    View 的绘制系列文章:

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

    DecorView Layout 阶段

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

    // ViewRootImpl 
    private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth, int desiredWindowHeight) { mLayoutRequested = false; mScrollMayChange = true; mInLayout = true; 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 = false; 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, false); if (validLayoutRequesters != null) { // 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 = true; // 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, 0, host.getMeasuredWidth(), host.getMeasuredHeight()); mHandlingLayoutInLayoutRequest = false; // 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, true); if (validLayoutRequesters != null) { final ArrayList<View> finalRequesters = validLayoutRequesters; // Post second-pass requests to the next frame getRunQueue().post(new Runnable() { @Override public void run() { int numValidRequests = finalRequesters.size(); for (int i = 0; i < numValidRequests; ++i) { final View view = 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 = false; }

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

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

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

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

    // View   
    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;      // 设置界面的显示大小 boolean changed = isLayoutModeOptical(mParent) ? setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);      // 发生了改变,或者是需要重新 layout,那么就会进入 onLayout 逻辑 if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
    // 开始 onLayout(changed, l, t, r, b);
    if (shouldDrawRoundScrollbar()) { if(mRoundScrollbarRenderer == null) { mRoundScrollbarRenderer = new RoundScrollbarRenderer(this); } } else { mRoundScrollbarRenderer = null; }        // 清除请求 layout 的标记 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); } } } final boolean wasLayoutValid = isLayoutValid(); mPrivateFlags &= ~PFLAG_FORCE_LAYOUT; mPrivateFlags3 |= PFLAG3_IS_LAID_OUT; if (!wasLayoutValid && isFocused()) { mPrivateFlags &= ~PFLAG_WANTS_FOCUS; if (canTakeFocus()) { // We have a robust focus, so parents should no longer be wanting focus. clearParentsWantFocus(); } else if (getViewRootImpl() == null || !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, /* propagate */ true, /* refocus */ false); clearParentsWantFocus(); } else if (!hasParentWantsFocus()) { // original requestFocus was likely on this view directly, so just clear focus clearFocusInternal(null, /* propagate */ true, /* refocus */ false); } // otherwise, we let parents handle re-assigning focus during their layout passes. } else if ((mPrivateFlags & PFLAG_WANTS_FOCUS) != 0) { mPrivateFlags &= ~PFLAG_WANTS_FOCUS; View focused = findFocus(); if (focused != null) { // 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(null, /* propagate */ true, /* refocus */ false); } } } if ((mPrivateFlags3 & PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT) != 0) { mPrivateFlags3 &= ~PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT; notifyEnterOrExitForAutoFillIfNeeded(true); } }

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

    protected boolean setFrame(int left, int top, int right, int bottom) {
            boolean changed = false;
    
            if (DBG) {
                Log.d(VIEW_LOG_TAG, this + " View.setFrame(" + left + "," + top + ","
                        + right + "," + bottom + ")");
            }
         // 有一个不一样说明发生了改变
            if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
                changed = true;
    
                // 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 改变的回调
                if (sizeChanged) {
                    sizeChange(newWidth, newHeight, oldWidth, oldHeight);
                }
    
                if ((mViewFlags & VISIBILITY_MASK) == VISIBLE || mGhostView != null) {
                    // 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 = true;
                mDefaultFocusHighlightSizeChanged = true;
                if (mForegroundInfo != null) {
                    mForegroundInfo.mBoundsChanged = true;
                }
    
                notifySubtreeAccessibilityStateChangedIfNeeded();
            }
            return changed;
        }
    这里我们看到它对 mLeft、mTop、mRight、mBottom 这四个值进行了重新赋值,对于每一个View,包括 ViewGroup 来说,以上四个值保存了 Viwe 的位置信息,所以这四个值是最终宽高,也即是说,如果要得到 View 的位置信息,那么就应该在 layout 方法完成后调用 getLeft()、getTop() 等方法来取得最终宽高,如果是在此之前调用相应的方法,只能得到 0 的结果。当初始化完毕后,ViewGroup 的布局流程也就完成了。

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

      private void sizeChange(int newWidth, int newHeight, int oldWidth, int oldHeight) {
            onSizeChanged(newWidth, newHeight, oldWidth, oldHeight);
          ......
    }

    子 View Layout 流程

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

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

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

    // FrameLaout   
    void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) { final int count = getChildCount();      // 获取 DecoverView 剩余的空间范围 final int parentLeft = getPaddingLeftWithForeground(); final int parentRight = right - left - getPaddingRightWithForeground(); final int parentTop = getPaddingTopWithForeground(); final int parentBottom = bottom - top - getPaddingBottomWithForeground(); for (int i = 0; i < count; i++) { final View child = getChildAt(i); if (child.getVisibility() != GONE) { final LayoutParams lp = (LayoutParams) child.getLayoutParams(); final int width = child.getMeasuredWidth(); final int height = child.getMeasuredHeight(); int childLeft; int childTop;
              // DEFAULT_CHILD_GRAVITY = Gravity.TOP | Gravity.START 默认是在左上角
    int gravity = lp.gravity;
                    if (gravity == -1) {
                        gravity = DEFAULT_CHILD_GRAVITY;
                    }
    
                    final int layoutDirection = getLayoutDirection();
                    final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
                    final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
              // 确定左边起始点
                    switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
                        case Gravity.CENTER_HORIZONTAL:
                            childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
                            lp.leftMargin - lp.rightMargin;
                            break;
                        case Gravity.RIGHT:
                            if (!forceLeftGravity) {
                                childLeft = parentRight - width - lp.rightMargin;
                                break;
                            }
                        case Gravity.LEFT:
                        default:
                            childLeft = parentLeft + lp.leftMargin;
                    }
              // 确定上边起始点
                    switch (verticalGravity) {
                        case Gravity.TOP:
                            childTop = parentTop + lp.topMargin;
                            break;
                        case Gravity.CENTER_VERTICAL:
                            childTop = parentTop + (parentBottom - parentTop - height) / 2 +
                            lp.topMargin - lp.bottomMargin;
                            break;
                        case Gravity.BOTTOM:
                            childTop = parentBottom - height - lp.bottomMargin;
                            break;
                        default:
                            childTop = parentTop + lp.topMargin;
                    }
    
                    child.layout(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 方法 :

    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {

    }

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

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

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

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

    Layout 整体的流程图

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

     

    DecorView Draw 流程

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

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

    然后会执行到 ViewRootImpl#draw:

    private void draw(boolean fullRedrawNeeded) {
     ...
     //获取mDirty,该值表示需要重绘的区域
     final Rect dirty = mDirty;
     if (mSurfaceHolder != null) {
      // The app owns the surface, we won't draw.
      dirty.setEmpty();
      if (animating) {
       if (mScroller != null) {
        mScroller.abortAnimation();
       }
       disposeResizeBuffer();
      }
      return;
     }
    
     //如果fullRedrawNeeded为真,则把dirty区域置为整个屏幕,表示整个视图都需要绘制
     //第一次绘制流程,需要绘制所有视图
     if (fullRedrawNeeded) {
      mAttachInfo.mIgnoreDirtyState = true;
      dirty.set(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
     }
     ...
     if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
        return;
      }
    }

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

    private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
       boolean 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(0, PorterDuff.Mode.CLEAR);
                }
       ...
          canvas.translate(-xoff, -yoff);
       ...
       //正式开始绘制
       mView.draw(canvas);
      ...
     //提交需要绘制的东西
      surface.unlockCanvasAndPost(canvas);
    }

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

      public void draw(Canvas canvas) {
            super.draw(canvas);
    
            if (mMenuBackground != null) {
                mMenuBackground.draw(canvas);
            }
        }

     里面会调用 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);
            }
    
            // 如果可以跳过2和5步
            final 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
                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)
                onDrawForeground(canvas);
    
                // we're done...
                return;
            }
    
            /*
             * 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 = false;
            boolean drawBottom = false;
            boolean drawLeft = false;
            boolean drawRight = false;
    
            float topFadeStrength = 0.0f;
            float bottomFadeStrength = 0.0f;
            float leftFadeStrength = 0.0f;
            float rightFadeStrength = 0.0f;
    
            // Step 2, save the canvas' layers
            int paddingLeft = mPaddingLeft;
    
            final boolean offsetRequired = isPaddingOffsetRequired();
            if (offsetRequired) {
                paddingLeft += getLeftPaddingOffset();
            }
    
            int left = mScrollX + paddingLeft;
            int right = left + mRight - mLeft - mPaddingRight - paddingLeft;
            int top = mScrollY + getFadeTop(offsetRequired);
            int bottom = top + getFadeHeight(offsetRequired);
    
            if (offsetRequired) {
                right += getRightPaddingOffset();
                bottom += getBottomPaddingOffset();
            }
    
            final ScrollabilityCache scrollabilityCache = mScrollCache;
            final float fadeHeight = scrollabilityCache.fadingEdgeLength;
            int length = (int) 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;
            }
    
            if (verticalEdges) {
                topFadeStrength = Math.max(0.0f, Math.min(1.0f, getTopFadingEdgeStrength()));
                drawTop = topFadeStrength * fadeHeight > 1.0f;
                bottomFadeStrength = Math.max(0.0f, Math.min(1.0f, getBottomFadingEdgeStrength()));
                drawBottom = bottomFadeStrength * fadeHeight > 1.0f;
            }
    
            if (horizontalEdges) {
                leftFadeStrength = Math.max(0.0f, Math.min(1.0f, getLeftFadingEdgeStrength()));
                drawLeft = leftFadeStrength * fadeHeight > 1.0f;
                rightFadeStrength = Math.max(0.0f, Math.min(1.0f, getRightFadingEdgeStrength()));
                drawRight = rightFadeStrength * fadeHeight > 1.0f;
            }
    
            saveCount = canvas.getSaveCount();
    
            int solidColor = getSolidColor();
            if (solidColor == 0) {
                final int flags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG;
    
                if (drawTop) {
                    canvas.saveLayer(left, top, right, top + length, null, flags);
                }
    
                if (drawBottom) {
                    canvas.saveLayer(left, bottom - length, right, bottom, null, flags);
                }
    
                if (drawLeft) {
                    canvas.saveLayer(left, top, left + length, bottom, null, flags);
                }
    
                if (drawRight) {
                    canvas.saveLayer(right - length, top, right, bottom, null, flags);
                }
            } else {
                scrollabilityCache.setFadeColor(solidColor);
            }
    
            // 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
            final Paint p = scrollabilityCache.paint;
            final Matrix matrix = scrollabilityCache.matrix;
            final Shader fade = scrollabilityCache.shader;
    
            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);
            }
    
            if (drawBottom) {
                matrix.setScale(1, fadeHeight * bottomFadeStrength);
                matrix.postRotate(180);
                matrix.postTranslate(left, bottom);
                fade.setLocalMatrix(matrix);
                p.setShader(fade);
                canvas.drawRect(left, bottom - length, right, bottom, p);
            }
    
            if (drawLeft) {
                matrix.setScale(1, fadeHeight * leftFadeStrength);
                matrix.postRotate(-90);
                matrix.postTranslate(left, top);
                fade.setLocalMatrix(matrix);
                p.setShader(fade);
                canvas.drawRect(left, top, left + length, bottom, p);
            }
    
            if (drawRight) {
                matrix.setScale(1, fadeHeight * rightFadeStrength);
                matrix.postRotate(90);
                matrix.postTranslate(right, top);
                fade.setLocalMatrix(matrix);
                p.setShader(fade);
                canvas.drawRect(right - length, top, right, bottom, p);
            }
    
            canvas.restoreToCount(saveCount);
    
            // 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);
        }

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

    1. 对 View 的背景进行绘制

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

    3. 绘制 View 的内容

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

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

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

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

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

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

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

    1  final int privateFlags = mPrivateFlags;
    2  final 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

        private void drawBackground(Canvas canvas) {
           //获取背景的Drawable,没有就不需要绘制
            final Drawable background = mBackground;
            if (background == null) {
                return;
            }
           //确定背景Drawable边界
            setBackgroundBounds();
            ...
    
           //如果有偏移量先偏移画布再将drawable绘制上去
            final int scrollX = mScrollX;
            final int scrollY = mScrollY;
            if ((scrollX | scrollY) == 0) {
                background.draw(canvas);
            } else {
                canvas.translate(scrollX, scrollY);
                //此处会执行各种Drawable对应的draw方法
                background.draw(canvas);
                //把画布的原点移回去,drawable在屏幕上的位置不动
                canvas.translate(-scrollX, -scrollY);
            }
        }

    3. 绘制 View 的内容

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

       public void onDraw(Canvas c) {
            super.onDraw(c);
    
            mBackgroundFallback.draw(this, mContentRoot, c, mWindow.mContentParent,
                    mStatusColorViewState.view, mNavigationColorViewState.view);
        }

    View#onDraw

      protected void onDraw(Canvas canvas) {
    }

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

    4. 绘制子View

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

    protected void dispatchDraw(Canvas canvas) {
           boolean usingRenderNodeProperties = canvas.isRecordingFor(mRenderNode);
           final int childrenCount = mChildrenCount;
           final View[] children = mChildren;
           int flags = mGroupFlags;
    
    //ViewGroup是否有设置子View入场动画,如果有绑定到View
    // 启动动画控制器
          ...
    
    //指定修改区域
           int clipSaveCount = 0;
           final boolean clipToPadding = (flags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK;
        // 不让子view绘制在pandding里面,也就是去除padding
    if (clipToPadding) { clipSaveCount = canvas.save(); canvas.clipRect(mScrollX + mPaddingLeft, mScrollY + mPaddingTop, mScrollX + mRight - mLeft - mPaddingRight, mScrollY + mBottom - mTop - mPaddingBottom); } ... for (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() != null) { more |= drawChild(canvas, transientChild, drawingTime); } transientIndex++; if (transientIndex >= transientCount) { transientIndex = -1; } } int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i; final View child = (preorderedList == null) ? children[childIndex] : preorderedList.get(childIndex); if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) { more |= drawChild(canvas, child, drawingTime); } } ... }

    ViewGroup#dispatchDraw 的流程是先启动第一次加到布局中的动画,然后确定绘制区域,遍历绘制 View,遍历 View 的时候优先绘制渲染的 mTransientViews,绘制 View 调用到ViewGroup#drawChild:

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

     View#draw(canvas, this, drawingTime)

    boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
     
           boolean drawingWithRenderNode = mAttachInfo != null
                    && mAttachInfo.mHardwareAccelerated
                    && hardwareAcceleratedCanvas;
          ...
    
         //主要判断是否有绘制缓存,如果有,直接使用缓存,如果没有,调用 draw(canvas)方法
            if (!drawingWithDrawingCache) {
                if (drawingWithRenderNode) {
                    mPrivateFlags &= ~PFLAG_DIRTY_MASK;
                    ((DisplayListCanvas) canvas).drawRenderNode(renderNode);
                } else {
                    // Fast path for layouts with no backgrounds
                    if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
                        mPrivateFlags &= ~PFLAG_DIRTY_MASK;
                        dispatchDraw(canvas);
                    } else {
                        draw(canvas);
                    }
                }
            } else if (cache != null) {
                mPrivateFlags &= ~PFLAG_DIRTY_MASK;
                if (layerType == LAYER_TYPE_NONE) {
                    // no layer paint, use temporary paint to draw bitmap
                    Paint cachePaint = parent.mCachePaint;
                    if (cachePaint == null) {
                        cachePaint = new Paint();
                        cachePaint.setDither(false);
                        parent.mCachePaint = cachePaint;
                    }
                    cachePaint.setAlpha((int) (alpha * 255));
                    canvas.drawBitmap(cache, 0.0f, 0.0f, cachePaint);
                } else {
                    // 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, 0.0f, 0.0f, mLayerPaint);
                    mLayerPaint.setAlpha(layerPaintAlpha);
                }
          }
    }

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

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

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

    6. 绘制装饰

        public void onDrawForeground(Canvas canvas) {
         //绘制滑动指示
            onDrawScrollIndicators(canvas);
         //绘制ScrollBar
            onDrawScrollBars(canvas);
         //获取前景色的Drawable,绘制到canvas上
            final Drawable foreground = mForegroundInfo != null ? mForegroundInfo.mDrawable : null;
            if (foreground != null) {
                if (mForegroundInfo.mBoundsChanged) {
                    mForegroundInfo.mBoundsChanged = false;
                    final Rect selfBounds = mForegroundInfo.mSelfBounds;
                    final Rect overlayBounds = mForegroundInfo.mOverlayBounds;
                    if (mForegroundInfo.mInsidePadding) {
                        selfBounds.set(0, 0, getWidth(), getHeight());
                    } else {
                        selfBounds.set(getPaddingLeft(), getPaddingTop(),
                                getWidth() - getPaddingRight(), getHeight() - getPaddingBottom());
                    }
                    final 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 horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
         boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;

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

            int paddingLeft = mPaddingLeft;
            final boolean offsetRequired = isPaddingOffsetRequired();
            if (offsetRequired) {
                paddingLeft += getLeftPaddingOffset();
            }
            int left = mScrollX + paddingLeft;
            int right = left + mRight - mLeft - mPaddingRight - paddingLeft;
            int top = mScrollY + getFadeTop(offsetRequired);
            int bottom = top + getFadeHeight(offsetRequired);
            if (offsetRequired) {
                right += getRightPaddingOffset();
                bottom += getBottomPaddingOffset();
            }
            final ScrollabilityCache scrollabilityCache = mScrollCache;
            final float fadeHeight = scrollabilityCache.fadingEdgeLength;
            int length = (int) fadeHeight;
            if (verticalEdges && (top + length > bottom - length)) {
                length = (bottom - top) / 2;
            }
            if (horizontalEdges && (left + length > right - length)) {
                length = (right - left) / 2;
            }
            if (verticalEdges) {
                topFadeStrength = Math.max(0.0f, Math.min(1.0f, getTopFadingEdgeStrength()));
                drawTop = topFadeStrength * fadeHeight > 1.0f;
                bottomFadeStrength = Math.max(0.0f, Math.min(1.0f, getBottomFadingEdgeStrength()));
                drawBottom = bottomFadeStrength * fadeHeight > 1.0f;
            }
            if (horizontalEdges) {
                leftFadeStrength = Math.max(0.0f, Math.min(1.0f, getLeftFadingEdgeStrength()));
                drawLeft = leftFadeStrength * fadeHeight > 1.0f;
                rightFadeStrength = Math.max(0.0f, Math.min(1.0f, getRightFadingEdgeStrength()));
                drawRight = rightFadeStrength * fadeHeight > 1.0f;
            }
            saveCount = canvas.getSaveCount();
            int solidColor = getSolidColor();
            if (solidColor == 0) {
                final int flags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG;
                if (drawTop) {
                    canvas.saveLayer(left, top, right, top + length, null, flags);
                }
                if (drawBottom) {
                    canvas.saveLayer(left, bottom - length, right, bottom, null, flags);
                }
                if (drawLeft) {
                    canvas.saveLayer(left, top, left + length, bottom, null, flags);
                }
                if (drawRight) {
                    canvas.saveLayer(right - length, top, right, bottom, null, flags);
                }
            } else {
                scrollabilityCache.setFadeColor(solidColor);
            }

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

            final Paint p = scrollabilityCache.paint;
            final Matrix matrix = scrollabilityCache.matrix;
            final Shader fade = scrollabilityCache.shader;
            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);
            }
            if (drawBottom) {
                matrix.setScale(1, fadeHeight * bottomFadeStrength);
                matrix.postRotate(180);
                matrix.postTranslate(left, bottom);
                fade.setLocalMatrix(matrix);
                p.setShader(fade);
                canvas.drawRect(left, bottom - length, right, bottom, p);
            }
            if (drawLeft) {
                matrix.setScale(1, fadeHeight * leftFadeStrength);
                matrix.postRotate(-90);
                matrix.postTranslate(left, top);
                fade.setLocalMatrix(matrix);
                p.setShader(fade);
                canvas.drawRect(left, top, left + length, bottom, p);
            }
            if (drawRight) {
                matrix.setScale(1, fadeHeight * rightFadeStrength);
                matrix.postRotate(90);
                matrix.postTranslate(right, top);
                fade.setLocalMatrix(matrix);
                p.setShader(fade);
                canvas.drawRect(right - length, top, right, bottom, p);
            }
            canvas.restoreToCount(saveCount);

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

    最后附上 View 的 draw 流程:

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

  • 相关阅读:
    python PyQt5
    传入一张图,生成它的油画版!(python实现)(转 )
    Python——画一棵漂亮的樱花树(不同种樱花+玫瑰+圣诞树喔)(转)
    Python3.7实现自动刷博客访问量(只需要输入用户id)(转)
    Python3 多线程的两种实现方式
    图片生成字符
    SqlServer性能优化 通过压缩与计算列提高性能(十一)
    json与bson的区别
    浅析Redis 和MongoDB
    Solr DocValues详解
  • 原文地址:https://www.cnblogs.com/huansky/p/12000771.html
Copyright © 2020-2023  润新知