1, 引子

一直以为绘制的三个方法onMeasure,onLayout,和onDraw只调用一次,分析源码时也没有在意,后来在看listview时发现onLayout居然调用了2次,然后就有些疑惑了,这些方法到底是调用几次。其实, ViewRootImpl 的performTraversals方法调用2次, View的onMeasure方法至少调用2次, onLayout方法调用2次, onDraw方法调用1次。调用顺序如下,

performTraversals –>onMeasure-->onMeasure-->onLayout
performTraversals –>onMeasure--> onLayout -->onDraw

2, 2次performTraversals

ViewRootImpl 的performTraversals部分源码如下,

private void performTraversals() {            
        mIsInTraversal = true;
        mWillDrawSoon = true;
        boolean windowSizeMayChange = false;
        boolean newSurface = false;
        boolean surfaceChanged = false;
        WindowManager.LayoutParams lp = mWindowAttributes;
        ~~~
if (!cancelDraw && !newSurface) {
            if (!skipDraw || mReportNextDraw) {
                if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
                    for (int i = 0; i < mPendingTransitions.size(); ++i) {
                        mPendingTransitions.get(i).startChangingAnimations();
                    }
                    mPendingTransitions.clear();
                }

                performDraw();
            }
        } else {
            if (viewVisibility == View.VISIBLE) {
                // Try again
                scheduleTraversals();
            } else if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
                for (int i = 0; i < mPendingTransitions.size(); ++i) {
                    mPendingTransitions.get(i).endChangingAnimations();
                }
                mPendingTransitions.clear();
            }
        }

        mIsInTraversal = false;
    }

第1次执行performTraversals方法时, newSurface为false,所以会执行scheduleTraversals方法,该方法最后又会调用performTraversals方法,第2次执行performTraversals方法时,newSurface为true,所以会调用performDraw方法,最后会调用onDraw方法。

3, measure

View的measure部分源码如下,

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
~~~
if ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ||
                widthMeasureSpec != mOldWidthMeasureSpec ||
                heightMeasureSpec != mOldHeightMeasureSpec) {
        ~~~
       if (cacheIndex < 0 || sIgnoreMeasureCache) {
                // measure ourselves, this should set the measured dimension flag back
                onMeasure(widthMeasureSpec, heightMeasureSpec);
                mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            }
  ~~~
}

有些ViewGroup可能会让自己的子视图测量两次。比如说,父视图先让每个子视图自己测量,使用View.MeasureSpec.UNSPECIFIED,然后在根据每个子视图希望得到的大小不超过父视图的一些限制,就让子视图得到自己希望的大小,否则就用其他尺寸来重新测量子视图。这一类的视图有FrameLayout,RelativeLayout等。所以,在measure方法中,对重新调用onMeasure方法有些限制。

所以, measure方法至少会调用2次, onLayout一定会调用2次, onDraw方法仅会调用1次。

4,View状态

View状态的种类非常多,一共有十几种类型,不过多数情况下我们只会使用到其中的几种,因此这里我们也就只去分析最常用的几种视图状态。

enabled

setEnabled

isEnabled

focused

setFocusable

isFocusable

selected

 

 

pressed

 

 

enabled

setEnable()方法来改变视图的可用状态,传入true表示可用,传入false表示不可用。它们之间最大的区别在于,不可用的视图是无法响应onTouch事件的。

focused

表示当前视图是否获得到焦点。通常情况下有两种方法可以让视图获得焦点,即通过键盘的上下左右键切换视图,以及调用requestFocus()方法。

selected

表示当前视图是否处于选中状态。一个界面当中可以有多个视图处于选中状态,调用setSelected()方法能够改变视图的选中状态,传入true表示选中,传入false表示未选中。

pressed

表示当前视图是否处于按下状态。可以调用setPressed()方法来对这一状态进行改变,传入true表示按下,传入false表示未按下。


一般当View的状态发生改变时,会重新绘制View,刷新界面。

当View的状态发生改变时,最后都会调用ViewRootImpl的performTraversals方法进行刷新,具体的细节就不多说了。