5

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

 3 years ago
source link: https://blog.csdn.net/mingyunxiaohai/article/details/88775422
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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

上一篇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/childLayoutParamsEXACTLYAT_MOSTUNSPECIFIEDdp/pxEXACTLY / chileSizeEXACTLY / chileSizeEXACTLY / chileSizematch_parentEXACTLY / parentSizeAT_MOST /parentSize0wrap_contentAT_MOST/ parentSizeAT_MOST /parentSize0
  • 当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来绘制内容的时候,我们需要显示的关闭这个标志位。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK