View的内容大致分为一下四项:
View的事件分发机制
首先聊一聊View的绘制。大家应该都知道View的绘制经历了三个步骤:Measure,Layout,Draw,这也是View类中的三个方法,但它们并不真正的做工作,只是对工作的结果进行审查。在这个三个方法中,分别调用了onMeasure,onLayout,onDraw三个方法来做真正的测量,布局与绘制的工作。所以Measure,Layout,Draw三个方法只是作为监督者,其中Measure方法使用了final修饰符进行了修饰,我们无法对其进行重写与继承。我们一般通过重写onMeasure,onLayout,onDraw三个真正做工作的方法来对View进行自定义。
每一个View都存在于一个窗口(Window)里,每一个新窗口都会创建一个ViewRootImpl,作为这个控件树的根部,负责整个窗口中的所有View。View的测量,布局,绘制,沿着控件树,完成每个父View及子View的测量,布局,绘制的工作。
ViewRootImpl调用其performTraversals方法来对整个控件树进行遍历,在performTraversals方法中调用performMeasure方法,该方法调用view.Measure方法,对控件进测量。measure()方法接收两个参数,widthMeasureSpec和heightMeasureSpec,这两个值分别用于确定视图的宽度和高度的规格和大小。在测量之前,Measure方法会先判断是否需要测量,依据为:MeasureSpec是否发生了变化,强制布局位是否被设置;两个条件满足一个,便会调用onMeasure方法进行重新测量。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
以上是View中的onMeasure方法,内部调用了setMeasuredDimension方法来确定View的高,宽的测量值。setMeasuredDimension方法被调用之后,我们就可以使用getMeasuredWidth和getMeasuredHeight方法来获得View的高,宽的测量值。
一个ViewGroup内部包含多个View。ViewGroup中定义了一个measureChildren()方法来去测量子视图的大小,如下所示:
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);
}
}
}
measureChildren方法中,一次对每一个child遍历,如果child满足一定条件,就会对其调用measureChild方法。
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);
}
在measureChild方法中,最终child作为View类,调用了内部的measure方法,进行测量。measure方法的最后,View会设置强制布局标志位,保证下面的layout可以顺利进行。
performTraversals()方法继续执行,调用View的layout()方法来执行布局过程,如下所示:
public void layout(int l, int t, int r, int b) {
int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;
boolean changed = setFrame(l, t, r, b);
if (changed || (mPrivateFlags & LAYOUT_REQUIRED) == LAYOUT_REQUIRED) {
if (ViewDebug.TRACE_HIERARCHY) {
ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_LAYOUT);
}
onLayout(changed, l, t, r, b);
mPrivateFlags &= ~LAYOUT_REQUIRED;
if (mOnLayoutChangeListeners != null) {
ArrayList<OnLayoutChangeListener> listenersCopy =
(ArrayList<OnLayoutChangeListener>) 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);
}
}
}
mPrivateFlags &= ~FORCE_LAYOUT;
}
在layout()方法中,首先会调用setFrame()方法来判断视图的大小和位置是否发生过变化。第7行,layout方法会进行判断,如果View的大小和位置都未发生变化,而且强制布局位也未被设置,便不会调用onLayout方法进行重新布局。若两个条件满足其一,就会在接下来的第11行调用onLayout方法。
View中的onLayout()方法是一个空方法,因为onLayout()过程是为了确定视图在布局中所在的位置,而这个操作应该是由布局来完成的,即父视图决定子视图的显示位置。而ViewGroup中的onLayout()方法是一个抽象方法,这就意味着所有ViewGroup的子类都必须重写这个方法。正如LinearLayout、RelativeLayout等布局,都是重写了这个方法,然后在内部按照各自的规则对子视图进行布局的。
接下来继续执行Draw方法,并在其中调用onDraw方法,一次绘制背景,内容,子View以及滚动条。每个视图都是有滚动条的,TextView,Button等,我们都可以通过设置来让滚动条显示出来。
这也就是为什么调用requestLayout方法会调用onMeasure,onLayout,onDraw三个方法, 因为requestLayout方法内部重新设置了强制布局位。
@CallSuper
public void requestLayout() {
if (mMeasureCache != null) mMeasureCache.clear();
if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {
// Only trigger request-during-layout logic if this is the view requesting it,
// not the views in its parent hierarchy
ViewRootImpl viewRoot = getViewRootImpl();
if (viewRoot != null && viewRoot.isInLayout()) {
if (!viewRoot.requestLayoutDuringLayout(this)) {
return;
}
}
mAttachInfo.mViewRequestingLayout = this;
}
mPrivateFlags |= PFLAG_FORCE_LAYOUT;
mPrivateFlags |= PFLAG_INVALIDATED;
if (mParent != null && !mParent.isLayoutRequested()) {
mParent.requestLayout();
}
if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
mAttachInfo.mViewRequestingLayout = null;
}
}
第15,16,18行,requestLayout不仅重置了自己的强制布局位,还会设置自己父亲的强制标志为位,如此不断传递,从而引起了整个控件树的测量,布局,绘制。
invalidate()方法和postinvalidate方法设置了脏标志位,只会调用onDraw方法进行绘制,而不会进行侧量和布局的工作。
mPrivateFlags |= PFLAG_DIRTY;