androidP: View的工作原理-View的工作流程

通过上文 androidP: View的工作原理-理解MeasureSpec(https://blog.csdn.net/zhuowalun8427/article/details/123224642)已经获取到普通View与DecorView的MeasureSpec,后面根据MeasureSpec进行View的三个流程
**measure(测量):**确定View的测量宽/高;
**layout(布局):**确定VIew的最终宽/高和四个顶点的位置;
**draw(绘制):**将View绘制到屏幕上。
一、measure过程
measure的过程主要分两种情况,如果只是一个原始的View,那么通过measure方法就完成了其测量过程;如果是一个ViewGroup除了完成自己的测量过程外,还会遍历去调用所有子元素的measure方法,各个子元素再递归去执行这个流程。
1、View的measure过程
根据上文源码可知,DecorView获取MeasureSpec后会调用performMeasure(childWidthMeasureSpec, childHeightMeasureSpec)方法,该方法中调用View.measure()方法。

// android_build_9/frameworks/base/core/java/android/view/ViewRootImpl.java
View mView;
private boolean measureHierarchy(final View host, final WindowManager.LayoutParams lp,
            final Resources res, final int desiredWindowWidth, final int desiredWindowHeight) {
     int childWidthMeasureSpec;
     int childHeightMeasureSpec;
     ...
     childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
     childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
     performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
     ...
}

 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);
        }
    }

普通VIew获取MeasureSpec后,直接调用VIew.measure()方法。

// android_build_9/frameworks/base/core/java/android/view/ViewGroup.java
 protected void measureChildWithMargins(View child,
            int parentWidthMeasureSpec, int widthUsed,
            int parentHeightMeasureSpec, int heightUsed) {
        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

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

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

下面直接看View的measure过程。

// android_build_9/frameworks/base/core/java/android/view/View.java
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
   ...
   onMeasure(widthMeasureSpec, heightMeasureSpec);
   ...
}

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

/**
     * Utility to return a default size. Uses the supplied size if the
     * MeasureSpec imposed no constraints. Will get larger if allowed
     * by the MeasureSpec.
     *
     * @param size Default size for this view
     * @param measureSpec Constraints imposed by the parent
     * @return The size this view should be.
     */
    public static int getDefaultSize(int size, int measureSpec) {
        int result = size;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        switch (specMode) {
        case MeasureSpec.UNSPECIFIED:
            result = size;
            break;
        case MeasureSpec.AT_MOST:
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
        }
        return result;
    }
 

1)measure()方法是一个final类型的方法,这意味着子类不能重写此方法。
2)onMeasure()方法中直接调用setMeasuredDimension方法来设置View的宽/高的测量值,直接看getDefaultSize方法。
3)getDefaultSize()方法中对于AT_MOST和EXACTLY这两种情况,返回的大小为specSize,即为测量后的大小。所以对于直接继承View自定义的控件需要重写onMeasure方法并设置wrap_content时的自身大小,否则在布局中使用wrap_content就相当于match_parent。通过上文图标可以了解,当View在布局中使用wrap_content定义宽/高时,该View返回的MeasureSpec的mode均为AT_MOST类型,且size均为可用的父容器大小,即getDefaultSize方法中specSize为可用的父容器的大小,因此在布局中使用wrap_content就相当于match_parent。解决该问题需要重写onMeasure方法,给View一个默认的宽/高(mWidth和mHeight,无特殊要求根据实际情况定义),并在wrap_content时设置此宽/高,其他情况不变。系统TextView 、ImageView等控件针对wrap_content情形,在onMeasure方法中都有做特殊处理。

 @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //默认的内部宽/高,没有固定的依据,根据需要灵活指定即可
        int mWidth = 100;
        int mheight = 100;
        int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);

        int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);

        if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(mWidth,mheight);
        }else if (widthSpecMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(mWidth,heightSpecSize);
        }else if (heightMeasureSpec == MeasureSpec.AT_MOST) {
             (widthSpecSize,mheight);
        }
    }

在这里插入图片描述
View的测量流程:获取VIew(或者DecorView)的MeasureSpec->View.measure->onMeasure->重写自定义VIew的onMeasure方法。
4)getDefaultSize()方法中,对于UNSPECIFIED情况,View的宽/高为getSuggestedMinimumWidth()和getSuggestedMinimumHeight()的返回值,以getSuggestedMinimumWidth()为例,源码:

// android_build_9/frameworks/base/core/java/android/view/View.java
 /**
     * Returns the suggested minimum width that the view should use. This
     * returns the maximum of the view's minimum width
     * and the background's minimum width
     *  ({@link android.graphics.drawable.Drawable#getMinimumWidth()}).
     * <p>
     * When being used in {@link #onMeasure(int, int)}, the caller should still
     * ensure the returned width is within the requirements of the parent.
     *
     * @return The suggested minimum width of the view.
     */
    private Drawable mBackground;
    protected int getSuggestedMinimumWidth() {
        return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
    }

// android_build_9/frameworks/base/graphics/java/android/graphics/drawable/Drawable.java
/**
     * Returns the minimum width suggested by this Drawable. If a View uses this
     * Drawable as a background, it is suggested that the View use at least this
     * value for its width. (There will be some scenarios where this will not be
     * possible.) This value should INCLUDE any padding.
     *
     * @return The minimum width suggested by this Drawable. If this Drawable
     *         doesn't have a suggested minimum width, 0 is returned.
     */
    public int getMinimumWidth() {
        final int intrinsicWidth = getIntrinsicWidth();
        return intrinsicWidth > 0 ? intrinsicWidth : 0;
    }

getSuggestedMinimumWidth()方法中,如果View没有设置背景,View的宽度为mMinWidth(对应于android:minWidth这个属性所制定的值,这个属性不指定,则mMinWidth默认为0);如果VIew设置了背景,则VIew的宽度为max(mMinWidth, mBackground.getMinimumWidth()。
Drawable.getMinimumWidth()返回的是Drawable的原始宽度,前提是这个Drawable有原始宽度,否则就返回0。那么Drawable在什么情况下有原始宽度呢,举个例子,ShapeDrawable无原始宽/高,而BitmapDrawable有原始宽/高(图片尺寸)。(这里不懂,需要学习《android开发艺术探索》第6章的内容)。

2、ViewGroup的measure过程
对于ViewGroup来说,除了完成自己的measure过程以外,还会遍历去调用所有子元素的measure方法,各个子元素再递归去执行这个过程。和View不同的是,ViewGroup是一个抽象类,因此它没有重写View的onMeasure方法,但是它提供了一个叫measureChildren的方法:

// android_build_9/frameworks/base/core/java/android/view/ViewGroup.java
// android_build_9/frameworks/base/core/java/android/view/View.java
@UiThread
public abstract class ViewGroup extends View implements ViewParent, ViewManager {
    /**
     * Ask all of the children of this view to measure themselves, taking into
     * account both the MeasureSpec requirements for this view and its padding.
     * We skip children that are in the GONE state The heavy lifting is done in
     * getChildMeasureSpec.
     *
     * @param widthMeasureSpec The width requirements for this view
     * @param heightMeasureSpec The height requirements for this view
     */
    protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
        final int size = mChildrenCount;
        final View[] children = mChildren;
        for (int i = 0; i < size; ++i) {
            final View child = children[i];
            if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
                measureChild(child, widthMeasureSpec, heightMeasureSpec);
            }
        }
    }
    
    /**
     * Ask one of the children of this view to measure itself, taking into
     * account both the MeasureSpec requirements for this view and its padding.
     * The heavy lifting is done in getChildMeasureSpec.
     *
     * @param child The child to measure
     * @param parentWidthMeasureSpec The width requirements for this view
     * @param parentHeightMeasureSpec The height requirements for this view
     */
    protected void measureChild(View child, int parentWidthMeasureSpec,
            int parentHeightMeasureSpec) {
        final LayoutParams lp = child.getLayoutParams();

        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom, lp.height);

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }
}

1)measureChildren()将所有的view取出来分别调用measureChild()方法,获取每一个view的MeasureSpec;
2)measureChild()的思想就是取出子元素的LayoutParams,然后再通过getChildMeasureSpec来创建子元素的MeasureSpec,接着将MeasureSpec传递给View的measure方法来进行测量。

ViewGroup是一个抽象类,其测量过程的onMeasure方法需要各个子类去具体实现,比如LinearLayout、RelativeLayout等。因为不同的ViewGrop子类有不同的布局特性,这导致他们的测量细节各部相同,比如LinearLayout、RelativeLayout这两者的布局特性显然不同,因此ViewGroup无法做统一实现。下面是LinearLayout的onMeasure方法的实现过程。

LinearLayout是Android开发中最常用的Layout之一,它支持水平或垂直线性布局,并且支持child设置权重weight,使child能够在主轴按一定比例填充LinearLayout。(线性布局的完整流程太难了,等三个流程都看完之后,再学这个。线性布局源码解析链接:https://blog.csdn.net/dehang0/article/details/104166111)

// android_build_9/frameworks/base/core/java/android/widget/LinearLayout.java
@RemoteView
public class LinearLayout extends ViewGroup {
     @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (mOrientation == VERTICAL) {
            measureVertical(widthMeasureSpec, heightMeasureSpec);
        } else {
            measureHorizontal(widthMeasureSpec, heightMeasureSpec);
        }
    }
}

根据线性布局的布局方向(水平/垂直),调用measureVertical()或measureVertical()方法。以垂直方向为例:

// android_build_9/frameworks/base/core/java/android/widget/LinearLayout.java
/**
     * Measures the children when the orientation of this LinearLayout is set
     * to {@link #VERTICAL}.
     *
     * @param widthMeasureSpec Horizontal space requirements as imposed by the parent.
     * @param heightMeasureSpec Vertical space requirements as imposed by the parent.
     *
     * @see #getOrientation()
     * @see #setOrientation(int)
     * @see #onMeasure(int, int)
     */
    void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
       // 内容总高度(所有child的测量高度总和+divider高度+边距)
        mTotalLength = 0;
        // 最大宽度(最大child宽度+边距)
        int maxWidth = 0;
        // child测量状态(可设置MEASURED_STATE_TOO_SMALL标识位,用于向父布局请求加大宽高)
        int childState = 0;
        // 备选最大宽度(记录非权重的child最大宽度)
        int alternativeMaxWidth = 0;
        // 权重最大宽度(记录含权重的child最大宽度)
        int weightedMaxWidth = 0;
        // 标记是否所有child的LayoutParams.width为MATCH_PARENT
        boolean allFillParent = true;
        // 计算child的权重之和
        float totalWeight = 0;
        // child数量(该方法内直接调用getChildCount,子类可重写该方法进行)
        final int count = getVirtualChildCount();
        // 取出父布局传入的测量规格模式。
        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        // 标记是否在LinearLayout的宽度确定后,对LayoutParams.width为MATCH_PARENT的child进行再次测量。
        boolean matchWidth = false;
        // 标记是否有对某个child暂不测量。
        boolean skippedMeasure = false;
        // baselineAlignedChildIndex属性值,默认为-1。
        final int baselineChildIndex = mBaselineAlignedChildIndex;
        // measureWithLargestChild属性值,默认为false。
        final boolean useLargestChild = mUseLargestChild;
        // 最大child的高度,当useLargestChild为true时有用
        int largestChildHeight = Integer.MIN_VALUE;
        // 记录设置了LayoutParams.height为0像素且权重大于0的child占用的总高度
        int consumedExcessSpace = 0;
        // 记录在第一轮遍历child时,有效child个数。
        int nonSkippedChildCount = 0;
    }

特殊情况:比如我们在Activity已启动的时候获取某个View的宽/高,发现在onCreate onResume onStart 中均无法正确得到某个View的宽/高信息,这是因为View的measure过程和Activity的生命周期方法不是同步执行的,因此无法保证Activity执行了onCreate onResume onStart 时某个View已经测量完毕了,如果View还没有测量完毕,那么获得的宽/高就是0。下面四种方法来解决这个问题:
1)Activity/View #onWindowFocusChanged
onWindowFocusChanged这个方法的含义是:View已经初始化完毕了,宽/高已经准备好了,这个时候去获取宽/高是没有问题的。需要注意的是,onWindowFocusChanged会被调用多次,当Activity的窗口得到焦点(onResume)和失去焦点(onPause)时都会被调用一次。典型代码如下:

private TextView mTvFirst;
BtStart = findViewById(R.id.bt_start);
 @Override
    public void onWindowFocusChanged(boolean hasWindowFocus) {
        super.onWindowFocusChanged(hasWindowFocus);
        if (hasWindowFocus) {
            int width = mTvFirst.getMeasuredWidth();
            int height = mTvFirst.getMeasuredHeight();
        }
    }

2)view.post(Runnable)
通过post可以将一个Runnable投递到消息队列的尾部,然后等待Looper调用此Runnable的时候,View也已经初始化好了,典型代码如下:

 @Override
    protected void onStart() {
        super.onStart();
        mTvFirst.post(new Runnable() {
            @Override
            public void run() {
                int width = mTvFirst.getMeasuredWidth();
                int height = mTvFirst.getMeasuredHeight();
            }
        });
    }

3)ViewTreeObserver
使用ViewTreeObserver的众多回掉可以完成这个功能,比如使用onGlobalLayoutListener这个接口,当View树的状态发生改变或者View树内部的View的可见性发现改变时,onGlobalLayout方法将被回掉,因此这是获取view的宽/高一个很好的时机。需要注意的是,伴随View树的状态改变等,onGlobalLayout会被调用多次。典型代码如下:

 @Override
    protected void onStart() {
        super.onStart();
        ViewTreeObserver observer = mTvFirst.getViewTreeObserver();
        observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                mTvFirst.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                int width = mTvFirst.getMeasuredWidth();
                int height = mTvFirst.getMeasuredHeight();
            }
        });

    }

4)view.measure(int widhMeasureSpen, int heightMeasureSpec)
(没看懂,先这样吧)
通过手动对View进行measure来得到View的宽/高。根据LayoutParams分为四种情况:
a、match_parent
直接放弃,无法measure出具体的宽/高。根据View的measure过程,构造此MeasureSpec需要知道parentSize,即父容器的剩余空间,而这个时候我们无法知道parentSize的大小,所以理论上不可能测量出View的大小。

b、具体的数值(dp/px)
比如款/高都是100px,如下measure

二、layout过程
Layout的作用是ViewGroup用来确定子元素的位置,当ViewGroup的位置确定后,它在onLayout中会遍历所有的子元素并调用其layout方法,在layout方法中onLayout方法又被调用。

// android_build_9/frameworks/base/core/java/android/view/View.java
 /**
     * Assign a size and position to a view and all of its
     * descendants
     *
     * <p>This is the second phase of the layout mechanism.
     * (The first is measuring). In this phase, each parent calls
     * layout on all of its children to position them.
     * This is typically done using the child measurements
     * that were stored in the measure pass().</p>
     *
     * <p>Derived classes should not override this method.
     * Derived classes with children should override
     * onLayout. In that method, they should
     * call layout on each of their children.</p>
     *
     * @param l Left position, relative to parent
     * @param t Top position, relative to parent
     * @param r Right position, relative to parent
     * @param b Bottom position, relative to parent
     */
    @SuppressWarnings({"unchecked"})
    public void layout(int l, int t, int r, int b) {
        if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
            onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
            mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
        }

        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 (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;
            }

            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);
        }
    }

layout方法的大致流程如下:首先通过setFrame方法来设定View的四个定点位置,即mLeft mTop mBottom mRight四个值,View的四个定点确定之后View在父容器中的位置就确定了;接着会调用onLayout方法,这个方法的用途是父容器确定子元素的位置,onLayout的具体实现与布局有关,需要重写onLayout方法。
例如线性布局onlayout方法(后面再学)。

补充:View的getMeasuredWidth与getWidth的区别(height一样)
源码:

// android_build_9/frameworks/base/core/java/android/view/View.java
@ViewDebug.ExportedProperty(category = "layout")
    public final int getWidth() {
        return mRight - mLeft;
    }

getWidth方法的返回值刚好是View的测量宽度。在View的默认实现中,View的测量宽/高和最终宽/高是相等的,只不过测量宽/高形成于VIew的measure阶段,而最终宽/高形成于View的layout过程,即两者的赋值时机不同。但是有某些特殊情况两者的值是不同的。
总结:
1)getMeasuredWidth方法获得的值是setMeasuredDimension方法设置的值,它的值在measure方法运行后就会确定
2)getWidth方法获得是layout方法中传递的四个参数中的mRight-mLeft,它的值是在layout方法运行后确定的
3)一般情况下在onLayout方法中使用getMeasuredWidth方法,而在除onLayout方法之外的地方用getWidth方法。

三、draw过程
draw的作用就是将View绘制到屏幕上。View的绘制过程遵循如下几步:
1)绘制背景background.draw(canvas)
2 ) 绘制自己(onDraw)
3)绘制children(dispatchDraw)
4)绘制装饰(onSrawScrollBars)
源码:

// android_build_9/frameworks/base/core/java/android/view/View.java
/**
     * Manually render this view (and all of its children) to the given Canvas.
     * The view must have already done a full layout before this function is
     * called.  When implementing a view, implement
     * {@link #onDraw(android.graphics.Canvas)} instead of overriding this method.
     * If you do need to override this method, call the superclass version.
     *
     * @param canvas The Canvas to which the View is rendered.
     */
    @CallSuper
    public void draw(Canvas canvas) {
        final int privateFlags = mPrivateFlags;
        final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
                (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
        mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
        /*
         * Draw traversal performs several drawing steps which must be executed
         * in the appropriate order:
         *
         *      1. Draw the background
         *      2. If necessary, save the canvas' layers to prepare for fading
         *      3. Draw view's content
         *      4. Draw children
         *      5. If necessary, draw the fading edges and restore layers
         *      6. Draw decorations (scrollbars for instance)
         */

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

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

            // Step 4, draw the children
            dispatchDraw(canvas);
             // Step 6, draw decorations (foreground, scrollbars)
            onDrawForeground(canvas);

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

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

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

View绘制过程的传递时通过dispatchDraw来实现的,dispatchDraw会遍历元素的draw方法,如此draw事件就一层层地传递下去。View有一个特殊的方法setWillNotDraw,源码:

// android_build_9/frameworks/base/core/java/android/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);
    }

如果一个View不需要绘制任何内容,那么设置这个标记为true以后,系统会进行相应的优化。默认情况下,View没有启用这个优化标记位,但是ViewGroup会默认启用这个优化标记位。这个标记位对实际开发的意义是:当我们自定义控件继承于ViewGroup并且本身不具备绘制功能时,就可以开启这个标记位从而便于系统进行后续的优化。当然,当明确知道一个ViewGroup需要通过onDraw来绘制内容时,我们需要显示地关闭WILL_NOT_DRAW这个标记位。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
怎么报错应该怎么解决java.lang.IllegalArgumentException: View=com.xiaopeng.xui.widget.XLinearLayout{6842348 V.E...... ......ID 0,0-600,130} not attached to window manager 05-26 17:48:27.970 10708 10708 E AndroidRuntime: at android.view.WindowManagerGlobal.findViewLocked(WindowManagerGlobal.java:543) 05-26 17:48:27.970 10708 10708 E AndroidRuntime: at android.view.WindowManagerGlobal.removeView(WindowManagerGlobal.java:447) 05-26 17:48:27.970 10708 10708 E AndroidRuntime: at android.view.WindowManagerImpl.removeView(WindowManagerImpl.java:196) 05-26 17:48:27.970 10708 10708 E AndroidRuntime: at com.xiaopeng.systemui.speech.component.asr.AsrAreaWidget.onAsrHide(AsrAreaWidget.java:50) 05-26 17:48:27.970 10708 10708 E AndroidRuntime: at com.xiaopeng.systemui.speech.model.AsrModel.notifyChanged(AsrModel.java:85) 05-26 17:48:27.970 10708 10708 E AndroidRuntime: at com.xiaopeng.systemui.speech.model.AsrModel.access$100(AsrModel.java:15) 05-26 17:48:27.970 10708 10708 E AndroidRuntime: at com.xiaopeng.systemui.speech.model.AsrModel$1.onInputText(AsrModel.java:73) 05-26 17:48:27.970 10708 10708 E AndroidRuntime: at com.xiaopeng.systemui.speech.presenter.SpeechManager$2.lambda$onInputText$0$SpeechManager$2(SpeechManager.java:172) 05-26 17:48:27.970 10708 10708 E AndroidRuntime: at com.xiaopeng.systemui.speech.presenter.-$$Lambda$SpeechManager$2$LNEIprveqAbFGXR19BN2ru0Bj2o.run(Unknown Source:4) 05-26 17:48:27.970 10708 10708 E AndroidRuntime: at android.os.Handler.handleCallback(Handler.java:938) 05-26 17:48:27.970 10708 10708 E AndroidRuntime: at android.os.Handler.dispatchMessage(Handler.java:99) 05-26 17:48:27.970 10708 10708 E AndroidRuntime: at android.os.Looper.loopOnce(Looper.java:232) 05-26 17:48:27.970 10708 10708 E AndroidRuntime: at android.os.Looper.loop(Looper.java:334) 05-26 17:48:27.970 10708 10708 E AndroidRuntime: at android.app.ActivityThread.main(ActivityThread.java:7985) 05-26 17:48:27.970 10708 10708 E AndroidRuntime: at java.lang.reflect.Method.invoke(Native Method) 05-26 17:48:27.970 10708 10708 E AndroidRuntime: at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548) 05-26 17:48:27.970 10708 10708 E AndroidRuntime: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1013)
05-27

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值