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

Android View 的绘制流程之 Layout 和 Draw 过程详解 (二),第1张

概述View的绘制系列文章:AndroidView绘制流程之DecorView与ViewRootImplAndroidView的绘制流程之Measure过程详解(一)在上一篇 AndroidView的绘制流程之Measure过程详解(一),已经详细的分析了DecorView和其子View的测量过程,接下去就要开始讲 layout和 VIEw 的绘制系列文章:Android View 绘制流程之 DecorView 与 ViewRootImplAndroid View 的绘制流程之 Measure 过程详解 (一)

在上一篇 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_@R_301_6843@FILL_ENTER_ON_LAYOUT) != 0) { mPrivateFlags3 &= ~PFLAG3_NOTIFY_@R_301_6843@FILL_ENTER_ON_LAYOUT; notifyEnterOrExitFor@R_301_6843@FillifNeeded(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 既然是透明的,那就没必要绘制它了。接着是绘制流程的六个步骤,这里先小结这六个步骤分别是什么,然后再展开来讲。绘制流程的六个步骤:

对 VIEw 的背景进行绘制

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

绘制 VIEw 的内容

对 VIEw 的子 VIEw 进行绘制(如果有子 VIEw )

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

绘制 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。

 

总结

以上是内存溢出为你收集整理的Android View 的绘制流程之 Layout 和 Draw 过程详解 (二)全部内容,希望文章能够帮你解决Android View 的绘制流程之 Layout 和 Draw 过程详解 (二)所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/1071804.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-26
下一篇 2022-05-26

发表评论

登录后才能评论

评论列表(0条)

保存