view的onMeasure,onLayout,onDraw源码分析(下)

来源:互联网 发布:中国真实gdp季度数据 编辑:程序博客网 时间:2024/06/07 00:24

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方法进行刷新,具体的细节就不多说了。

0 0
原创粉丝点击