UI绘制流程--View的绘制流程

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/mingyunxiaohai/article/details/88775422

上一篇UI绘制流程–View是如何被添加到屏幕上的我们学习了View是怎么添加到屏幕上的,这一片来学习View绘制流程,它的入口在入口ActivityThread.handleResumeActivity()。

本篇基于9.0的源码以前的一篇文章 Activity启动流程,最后我Activity启动的最后走到了ActivityThread中执行handleResumeActivity方法并里面执行了activity的onResume方法我们在来看这个方法

public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
           String reason) {
               ...
           final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
           ...
           final Activity a = r.activity;
           ...
           final Activity a = r.activity;
           ...
           //获取Window也就是PhoneWindow
            r.window = r.activity.getWindow();
            //获取PhoneWindow中的DecorView
            View decor = r.window.getDecorView();
            decor.setVisibility(View.INVISIBLE);
           ViewManager wm = a.getWindowManager();
           //获取PhoneWindow的参数
           WindowManager.LayoutParams l = r.window.getAttributes();
           a.mDecor = decor;
           l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
           l.softInputMode |= forwardBit;
           ...
             a.mWindowAdded = true;
             wm.addView(decor, l);
           ...
            Looper.myQueue().addIdleHandler(new Idler());
           }

代码中performResumeActivity就是去执行activity的onResume方法,之后创建了一个ViewManager ,然后拿到WindowManager的LayoutParams,最后通过addView方法把DecorView和LayoutParams放入ViewManager中。那ViewManager是什么呢

从这里我们可以知道,view的添加和绘制是onResume之后才开始的,所以onResume的时候我们是拿不到View的宽和高的

我们看到它是通过a.getWindowManager()获得,a是activity,那就去activity中找一下这个方法

  public WindowManager getWindowManager() {
        return mWindowManager;
    }

这里直接返回了activity的一个成员变量mWindowManager,那我们去找一下这个成员变量的赋值的地方,可以找到一个set方法

  public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
            boolean hardwareAccelerated) {
        mAppToken = appToken;
        mAppName = appName;
        mHardwareAccelerated = hardwareAccelerated
                || SystemProperties.getBoolean(PROPERTY_HARDWARE_UI, false);
        if (wm == null) {
            wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
        }
        mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
    }

可以看到是调用了WindowManagerImpl中的createLocalWindowManager方法来创建的

public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
        return new WindowManagerImpl(mContext, parentWindow);
    }

结果返回了一个WindowManagerImpl对象,所以上面的ViewManager其实就是一个WindowManagerImpl对象。所以呢最后调用的就是它的addView方法

 public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
    }

它又调用了mGlobal的addView方法,mGlobal是个WindowManagerGlobal对象在成员变量中直接通过单例创建WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();去看它的addView方法

  public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
                ...
                
            WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
            
            ...
                
             ViewRootImpl root;
             View panelParentView = null;
        
        ...
          //创建一个ViewRootImpl并设置参数
          root = new ViewRootImpl(view.getContext(), display);

            view.setLayoutParams(wparams);
            //保存传过来的view,ViewRootImpl,LayoutParams
            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);
            
            ...
            root.setView(view, wparams, panelParentView);
            ...
            }

看到这里创建了一个ViewRootImpl,给传过来的DecorView置LayoutParams参数,然后放到对应的集合中缓存,最后调用root.setView方法将他们关联起来。

 public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
            ...
           requestLayout();  
            ...
           view.assignParent(this);
        }

里面代码太多了,我们只关注里面的 requestLayout()方法就行

    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            //判断是不是主线程
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }
    void checkThread() {
        if (mThread != Thread.currentThread()) {
            throw new CalledFromWrongThreadException(
                    "Only the original thread that created a view hierarchy can touch its views.");
        }
    }

判断是不是在当前线程,当前activity的启动时在主线程,这就是为什么不能再子线程中更新UI,不过这里我们知道上面的方法时在onResume之后执行的,所以如果我们在onResume之前的子线程中执行一个很快的更新UI的操作,如果没有执行到这里就不会报错

首先判断是不是在主线程然后调用了scheduleTraversals方法。

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

我们看到mChoreographer.postCallback方法中传了一个mTraversalRunnable参数到队列中去执行,mTraversalRunnable是TraversalRunnable对象,TraversalRunnable其实是一个Runnable对象,所以真正的的执行的代码在其run方法中。

   final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }
    
   void doTraversal() {
           ...
            //真正的开始执行绘制
            performTraversals();

           ...
        }
    }

又调用了performTraversals方法

     private void performTraversals() {
         //DecorView
         final View host = mView;
         ...
           int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
           int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
         ...
         //measure过程
         performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
         ...
         //layout过程
         performLayout(lp, mWidth, mHeight);
         ...
         //绘制过程
         performDraw();
     }

代码比较多,只提取出3个主要的方法,这几个方法主要执行View的主要绘制流程:测量,布局和绘制。

以上代码其实就是将我们的顶级view->DecorView添加到窗口上,关联到ViewRootImpl中,并调用requestLayout(); 方法请求绘制,最后到了performTraversals方法中执行performMeasure,performLayout,performDraw真正的开始绘制。

下面就分别来看一下这三个方法。

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

可以看到这里调用了mView的measure方法,这个mView就是我们的前面add进来的DecorView。它是一个FrameLayout。点进去查看

 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));
    }
  protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
        boolean optical = isLayoutModeOptical(this);
        if (optical != isLayoutModeOptical(mParent)) {
            Insets insets = getOpticalInsets();
            int opticalWidth  = insets.left + insets.right;
            int opticalHeight = insets.top  + insets.bottom;

            measuredWidth  += optical ? opticalWidth  : -opticalWidth;
            measuredHeight += optical ? opticalHeight : -opticalHeight;
        }
        setMeasuredDimensionRaw(measuredWidth, measuredHeight);
    }

首先我们看到点进来之后到了View这个类中,measure这个方法时final类型的,所以不能被重写,因此就算他是FrameLayout最终也是在View类中执行measure的方法。

measure方法中又调用了onMeasure方法,然后直接调用setMeasuredDimension方法,最后调用了setMeasuredDimensionRaw方法。这些方法时干什么的呢,

首先我们先找到传入的参数widthMeasureSpec和heightMeasureSpec了解这两个参数的作用。

这两个参数是怎么来的呢,回到我们上面的performTraversals()方法,可以看到int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width); int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);

第一个参数表示窗口的宽度,第二个参数表示当前view也就是DectorView的LayoutParams

    private static int getRootMeasureSpec(int windowSize, int rootDimension) {
        int measureSpec;
        switch (rootDimension) {

        case ViewGroup.LayoutParams.MATCH_PARENT:
            // Window can't resize. Force root view to be windowSize.
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
            break;
        case ViewGroup.LayoutParams.WRAP_CONTENT:
            // Window can resize. Set max size for root view.
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
            break;
        default:
            // Window wants to be an exact size. Force root view to be that size.
            measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
            break;
        }
        return measureSpec;
    }

可以看到根据我们View设置的MATCH_PARENT还是WRAP_CONTENT等返回了一个通过MeasureSpec.makeMeasureSpec方法返回了一个int类型的值measureSpec,那它代表什么呢?

我们在测量View的时候需要知道两点:

第一点View的测量模式

第二点View的尺寸

measureSpec表示一个32的整数值,其高两位代表测量模式SpecMode,底30位表示该测量模式下的尺寸SpecSize。

我们进入MeasureSpace类可以看到3个常量

    /**
     * 表示父容器不对子容器进行限制,子容器可以是任意大小,
     * 一般是系统内部使用
     */
    public static final int UNSPECIFIED = 0 << MODE_SHIFT;
    /**
     * 精准测量模式,当view的layout_width 或者 layout_height指定为固定值值
     * 或者为match_parent的时候生效,这时候view的测量值就是SpecSize
     */
    public static final int EXACTLY     = 1 << MODE_SHIFT;
    /**
     * 父容器指定一个固定的大小,子容器可以使不超过这个值的任意大小
     * 对应我们的wrap_content
     */
    public static final int AT_MOST     = 2 << MODE_SHIFT;
    

对于DecorView这个顶级View来说,它的MeasureSpec 由窗口的尺寸和其自身的LayoutParams决定。

我们在回到measure方法中查看onMeasure方法,我们知道measure方法是个final方法不能被子类重写,不过onMeasure方法就没这个限制了,DecorView继承自FrameLayout,所以我们进入FrameLayout中查看它的onMeasure方法

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    ...
      for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            if (mMeasureAllChildren || child.getVisibility() != GONE) {
                //循环测量子view
                measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                maxWidth = Math.max(maxWidth,
                        child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
                maxHeight = Math.max(maxHeight,
                        child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
                childState = combineMeasuredStates(childState, child.getMeasuredState());
                if (measureMatchParentChildren) {
                    if (lp.width == LayoutParams.MATCH_PARENT ||
                            lp.height == LayoutParams.MATCH_PARENT) {
                        mMatchParentChildren.add(child);
                    }
                }
            }
        }
    ...
         // Account for padding too
        maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
        maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();

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

        // Check against our foreground's minimum height and width
        final Drawable drawable = getForeground();
        if (drawable != null) {
            maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
            maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
        }
        //设置自身的宽高
        setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
                resolveSizeAndState(maxHeight, heightMeasureSpec,
                        childState << MEASURED_HEIGHT_STATE_SHIFT));    
}

这里找出所有的子View,然后循环调用measureChildWithMargins方法测量子view的宽高,之后调用setMeasuredDimension确定自己的宽高

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

先获取子控件的宽高的测量规格,然后调用子控件的measure方法传入测量规格,子控件的测量规格是怎么获取的呢,点进去看到getChildMeasureSpec这个方法是在ViewGroup类中

    /**
     * @param spec 父控件的测量规格
     * @param padding 父控件已经占用的大小(减去padding和margin)
     * @param childDimension 子控件LayoutParams中的尺寸
     * @return a MeasureSpec integer for the child
     */
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
        //父控件的测量模式
        int specMode = MeasureSpec.getMode(spec);
        //父控件的尺寸
        int specSize = MeasureSpec.getSize(spec);
        //子容器可用大小要减去父view的padding和子view的margin
        int size = Math.max(0, specSize - padding);

        int resultSize = 0;
        int resultMode = 0;

        switch (specMode) {
        // 如果父控件是精准尺寸,也就是父控件知道自己的大小
        case MeasureSpec.EXACTLY:
            //如果子view设置了尺寸比如100dp,那么测量大小就是100dp
            if (childDimension >= 0) {
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                //如果子view设置的MATCH_PAREN想要沾满父view
                //父view是精准模式,那么把父view的size给它
                resultSize = size;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                //如果子view设置的WRAP_CONTENT,那么它想随意决定自己的大小
                //你可以随意玩,但是不能大于父控件的大小,
                //那么暂时把父view的size给它
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        // 如果父控件是最大模式,也就是父控件也不知道自己的大小
        case MeasureSpec.AT_MOST:
           //子控件设定了具体值
            if (childDimension >= 0) {
                //那就返回这个具体值
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
               //子view想和父view一样大,但是父view也不知道自己多大
               //把暂时父view的size给它,约束它不能超过父view
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                //子view想要自己确定尺寸
                //不能大于父view的size
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        // 父view是不确定的,一般是系统调用开发中不用
        case MeasureSpec.UNSPECIFIED:
            if (childDimension >= 0) {
                // Child wants a specific size... let him have it
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size... find out how big it should
                // be
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size.... find out how
                // big it should be
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            }
            break;
        }
        //noinspection ResourceType
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }

对于普通的view来说
它的MeasureSpec由其父view的MeasureSpec和自身的LayoutParams来决定

parentSpecMode/childLayoutParams EXACTLY AT_MOST UNSPECIFIED
dp/px EXACTLY / chileSize EXACTLY / chileSize EXACTLY / chileSize
match_parent EXACTLY / parentSize AT_MOST /parentSize 0
wrap_content AT_MOST/ parentSize AT_MOST /parentSize 0
  • 当view采用固定宽高的时候,不管父容器是什么模式,子view的MeasureSpec都是精确模式,并且大小就是其LayoutParams中设置的大小
  • 当view的宽或高是match_parent的时候,如果父容器是精准模式,那么子view的也是精准模式,其大小是父view的剩余空间,如果父容器是最大模式,那么子view也是最大模式,其大小暂时设为父view的大小并不能超过父view的大小。
  • 当view的宽或高是wrap_content的时候,不管父容器是什么模式,子view总是最大化,并且不超过父容器的剩余空间。

OK总结一下

  • ViewGroup执行measure方法->里面通过onMeasure方法递归测量子控件的宽高,测量完后通过setMeasuredDimension调用setMeasuredDimensionRaw方法最终保存自己的宽高。
  • View执行measure->onMeasure测量自己->测量完后通过setMeasuredDimension调用setMeasuredDimensionRaw方法最终保存自己的宽高

我们回到view的onMeasure方法

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

可以看到它在调用setMeasuredDimension传参的的时候调用了getDefaultSize方法

   public static int getDefaultSize(int size, int measureSpec) {
        int result = size;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

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

这个逻辑很简单,首先UNSPECIFIED我们不用管一般系统用,然后我们看到AT_MOST和EXACTLY最后的结果是一样的都赋值为specSize,这个specSize就是view测量后的大小。也就是getSuggestedMinimumWidth和getSuggestedMinimumHeight两个方法返回的值。

   protected int getSuggestedMinimumWidth() {
        return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
    }

从上面可以看出如果view没有设置背景,则返回mMinWidth,反之则宽度为mMinWidth和背景宽度的最大值。mMinWidth对应我们xml中设置的android:minWidth属性值,如果没设置则为0,mBackground.getMinimumWidth()则是返回的Drawable的原始宽度。

从上面的getDefaultSize方法我们可以得出一个结论,当我们直接继承view自定义控件的时候,需要重写其onMeasure方法,然后设置其wrap_content时候的大小,否则即便我们在布局中使用wrap_content,实际情况也相当于match_parent。原因可以从上面的表中看到,如果一个view设置了wrap_content,那么其测量模式是AT_MOST,在这种模式下view的宽高都等于父容器的剩余空间大小。

那怎么解决上面的问题呢?看一个重写onMeasure的例子

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        // 宽的测量规格
        int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
       // 宽的测量尺寸
        int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
        // 高度的测量规格
        int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
        // 高度的测量尺寸
        int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);

        //根据View的逻辑得到,比如TextView根据设置的文字计算wrap_content时的大小。
        //这两个数据根据实现需求计算。
        int wrapWidth,wrapHeight;
        
        // 如果是是AT_MOST则对哪个进行特殊处理
        if(widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST){
            setMeasuredDimension(wrapWidth, wrapHeight);
        }else if(widthSpecMode == MeasureSpec.AT_MOST){
            setMeasuredDimension(wrapWidth, heightSpecSize);
        }else if(heightSpecMode == MeasureSpec.AT_MOST){
            setMeasuredDimension(widthSpecSize, wrapHeight);
        }
}

我们只需给view指定一个默认的宽高,并在AT_MOST的时候设置宽高即可,默认宽高的大小根据实际情况来

OK,measure的方法就看完了下面来看layout的流程,这个比measure简单多了

//lp顶层布局的布局属性,顶层布局的宽和高
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
            int desiredWindowHeight) {
                ...
                final View host = mView;
                ...
                host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
            }

这里吧mView赋值给host然后调用了其layout方法,我们知道mView其实就是DecorView。

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;
        //setFrame来确定4个顶点的位置
        boolean changed = isLayoutModeOptical(mParent) ?
                setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);

        if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
        //父容器确定子view的位置
            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()) {
                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);
            }
        } else if ((mPrivateFlags & PFLAG_WANTS_FOCUS) != 0) {
            mPrivateFlags &= ~PFLAG_WANTS_FOCUS;
            View focused = findFocus();
            if (focused != null) {
                if (!restoreDefaultFocus() && !hasParentWantsFocus()) {
                    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的4个顶点的位置,4个顶点确定了,view在父容器中的位置也就确定了,然后调用onLayout方法来确定子元素的位置。onLayout需要不同的ViewGroup去自己实现比如LinearLayout和RelativeLayout的实现是不同的。

OK,layout也看完了下面看最后一步Draw的流程

private void performDraw() {
    ...
    boolean canUseAsync = draw(fullRedrawNeeded);
    ...
}
private boolean draw(boolean fullRedrawNeeded) {
    ...
     if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset,
                        scalingRequired, dirty, surfaceInsets)) {
    ...
}
 private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
            boolean scalingRequired, Rect dirty, Rect surfaceInsets) {
                ...
                 mView.draw(canvas);
                ...
            }

通过一系列的跳转,我们终于找到关键方法mView.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);
        }

        // 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) {
            // 绘制自己
            if (!dirtyOpaque) onDraw(canvas);

            // 绘制子view
            dispatchDraw(canvas);

            drawAutofilledHighlight(canvas);

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

            // 绘制装饰 前景滚动条(foreground, scrollbars)
            onDrawForeground(canvas);

            // 绘制默认的焦点突出显示
            drawDefaultFocusHighlight(canvas);

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

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

        ...

view的绘制过程上面注释已经写清楚了

  1. 绘制背景 (background.draw(canvas))
  2. 绘制自己 (onDrow)
  3. 绘制子view(dispatchDrow)
  4. 绘制装饰(前景、滚动条)

如果我们是自定义view,就去实现onDraw方法,如果我们是自定义ViewGroup,那就去实现dispatchDraw方法,dispatchDraw方法中会遍历子view调用子view的draw方法。

到这里draw方法就看完了,view的绘制流程也执行完毕!

ps: view中有个特殊的方法setWillNotDraw

 /**
     * If this view doesn't do any drawing on its own, set this flag to
     * allow further optimizations. By default, this flag is not set on
     * View, but could be set on some View subclasses such as ViewGroup.
     *
     * Typically, if you override {@link #onDraw(android.graphics.Canvas)}
     * you should clear this flag.
     *
     * @param willNotDraw whether or not this View draw on its own
     */
    public void setWillNotDraw(boolean willNotDraw) {
        setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);
    }

从注释中看出来,如果一个view不需要绘制任何东西,这个标志位设置为true之后,系统会进行相应的优化。

默认情况下,view没有启动这个标志位,但是ViewGroup是会默认启动这个标志位的。所以当我们继承ViewGroup的时候并且明确知道需要通过onDraw来绘制内容的时候,我们需要显示的关闭这个标志位。

猜你喜欢

转载自blog.csdn.net/mingyunxiaohai/article/details/88775422