本文转自 http://blog.****.net/u011791526/article/details/53925370
更多内容可参考
(一)自定义View的分类点击打开链接
(二)自定义View的构造方法及自定义属性点击打开链接
(三)自定义View的常用方法(测量、绘制、位置)点击打开链接
(四)View的生命周期
(五)自定义View的具体实现
(六)事件分发机制
上一节中,我们讨论了自定义View的几个常用方法,掌握这些方法的使用,是自定义View的核心。那么这些方法是怎么调度的?我们一起从源码来看看吧。
我们平时看到的android屏幕,它的层次是这样的

DecorView:窗口的根View,即整个窗口的根视图,它继承了FrameLayout
窗口布局View:DecorView的子View
①它的风格属性由application或者activity中的android:Theme = “”指定,比如为Activity配置xml属性:
-
<span style="font-size:14px;"> android:theme="@android:style/Theme.NoTitleBar"</span>
也可以由activity的requestWindowFeature方法(最终调用了PhoneWindow.requestFeature方法)指定,比如
-
<span style="font-size:14px;"> requestWindowFeature(Window.FEATURE_NO_TITLE);</span>
上述两种方式指定风格属性以后,系统会根据他们拿到相应的窗口布局文件(下面源码中的features就是风格属性,layoutResource就是根据风格属性指定的窗口布局文件)
②窗口布局文件中包含一个存放Activity自定义布局文件的ViewGroup视图,一般为FrameLayout
其id 为: android:id="@android:id/content"
源码如下
-
//1、根据requestFreature()和Activity节点的android:theme="" 设置好 features值
-
//2 根据设定好的 features值,即特定风格属性,选择不同的窗口修饰布局文件
-
int layoutResource; //窗口修饰布局文件
-
int features = getLocalFeatures();
-
// System.out.println("Features: 0x" + Integer.toHexString(features));
-
if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
-
if (mIsFloating) {
-
layoutResource = com.android.internal.R.layout.dialog_title_icons;
-
} else {
-
layoutResource = com.android.internal.R.layout.screen_title_icons;
-
}
-
// System.out.println("Title Icons!");
-
} else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0) {
-
// Special case for a window with only a progress bar (and title).
-
// XXX Need to have a no-title version of embedded windows.
-
layoutResource = com.android.internal.R.layout.screen_progress;
-
// System.out.println("Progress!");
-
}
-
//...
-
//3 选定了窗口修饰布局文件 ,添加至DecorView对象里,并且指定mcontentParent值
-
View in = mLayoutInflater.inflate(layoutResource, null);
-
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
-
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
contentParent:就是上面窗口布局文件中id为"@android:id/content"的View,他就是我们activity的布局文件的父布局
Activity的布局文件:在activity中,我们通过setContentView方法指定Activity的布局文件,这个文件就是被存放到了contentParent中
以上就是android系统屏幕视图的层次了,这里推荐一篇文章里面的讲解更加详细View添加到窗口的过程
接下来讲一讲DecorView的绘制过程,这里有兴趣的可以了解一下
DecorView是ViewRoot类调用View的生命周期方法绘制完成的
\frameworks\base\core\java\android\view\ViewRootImpl.java
通过ViewRoot调用performTranversals开始绘制View,依次通过measure、layout、draw三个过程
-
private void performTraversals() {
-
final View host = mView;
-
……
-
if (layoutRequested) {
-
windowSizeMayChange |= measureHierarchy(host, lp, res,
-
desiredWindowWidth, desiredWindowHeight);///
-
}
-
……
-
if (mFitSystemWindowsRequested) {
-
if (mLayoutRequested) {
-
windowSizeMayChange |= measureHierarchy(host, lp,///
-
mView.getContext().getResources(),
-
desiredWindowWidth, desiredWindowHeight);
-
}
-
}
-
if (!mStopped) {
-
if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
-
|| mHeight != host.getMeasuredHeight() || contentInsetsChanged) {
-
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);///
-
}
-
if (measureAgain) {
-
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);///
-
}
-
}
-
……
-
if(didLayout){
-
performLayout(lp, desiredWindowWidth, desiredWindowHeight);///
-
}
-
……
-
if(!cancelDraw&&!newSurface)
-
{
-
performDraw();///
-
}
-
}
源码中有几个比较重要的方法,他们在满足条件时会被调用
measureHierarchy——>performMeasure——>measure
performMeasure——>measure
performLayout——>layout
performDraw——>draw
因为performTranversals可能会被调用多次,所以measure方法也可能被调用多次,详情可参考大神的文章点击打开链接
讲了这么多,读者你是不是一脸懵逼呀,上面的内容和View的生命周期有什么关系呢。
我们还是来看看源码里View和ViewGroup怎么调用几个主要方法的吧
一、View
measure过程
View的该方法由父View调用
-
public final void measure(int widthMeasureSpec, int heightMeasureSpec) { //两个参数是父控件的长和宽信息
-
……
-
widthMeasureSpec = MeasureSpec.adjust(widthMeasureSpec, optical ? -oWidth : oWidth); //根据父控件长宽信息获取自己的长宽信息
-
heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);
-
……
-
onMeasure(widthMeasureSpec, heightMeasureSpec);//测量的是本View的信息
-
……
-
}
onMeasure方法一般在自定义的View中重写
-
@Override
-
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-
// super.onMeasure(widthMeasureSpec, heightMeasureSpec);//调用基类的onMeasure方法
-
setMeasuredDimension(10, 10);//设置新的长宽值
-
}
如果我们重写了onMeasure方法,通常要调用setMeasuredDimension方法设置我们指定的长宽值
-
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
-
……
-
mMeasuredWidth = measuredWidth;
-
mMeasuredHeight = measuredHeight;
-
……
-
}
如果我们没有重写onMeasure方法,则默认调用View类的onMeasure方法,该方法为我们的控件指定了默认长宽值
-
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
-
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
-
}
继承View的自定义View不需要测量子View长宽
Layout过程
-
/**
-
* Assign a size and position to a view and all of its
-
* descendants
-
*
-
* <p>This is the second phase of the layout mechanism.
-
* (The first is measuring). In this phase, each parent calls
-
* layout on all of its children to position them.
-
* This is typically done using the child measurements
-
* that were stored in the measure pass().</p>
-
*
-
* <p>Derived classes should not override this method.
-
* Derived classes with children should override
-
* onLayout. In that method, they should
-
* call layout on each of their children.</p>
-
*/
-
public void layout(int l, int t, int r, int b) {
-
……
-
boolean changed = isLayoutModeOptical(mParent) ?
-
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);//放置自己的位置
-
-
……
-
onLayout(changed, l, t, r, b);
-
……
-
}
源码的注释说的很清楚,这个方法由View的父View调用。是布局机制的第二步。
在实际运用中,我们并不重写layout方法,而是重写onLayout方法,遍历每一个子View,然后子View再调用layout放置自己
所以,继承View时,onLayout方法完全可以不重写,但继承ViewGroup时,onLayout方法一定必须重写(稍后会做分析)
-
/**
-
* Called from layout when this view should
-
* assign a size and position to each of its children.
-
*
-
* Derived classes with children should override
-
* this method and call layout on each of
-
* their children.
-
* @param changed This is a new size or position for this view
-
* @param left Left position, relative to parent
-
* @param top Top position, relative to parent
-
* @param right Right position, relative to parent
-
* @param bottom Bottom position, relative to parent
-
*/
-
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
-
}
draw过程
-
/**
-
* Manually render this view (and all of its children) to the given Canvas.
-
* The view must have already done a full layout before this function is
-
* called. When implementing a view, implement
-
* {@link #onDraw(android.graphics.Canvas)} instead of overriding this method.
-
* If you do need to override this method, call the superclass version.
-
*
-
* @param canvas The Canvas to which the View is rendered.
-
*/
-
public void draw(Canvas canvas) {
-
/*
-
* 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 绘制子View
-
* 5. If necessary, draw the fading edges and restore layers 如果需要绘制边并恢复图层
-
* 6. Draw decorations (scrollbars for instance)绘制装饰(如滚动条)
-
*/
-
// Step 1, draw the background, if needed
-
……
-
// skip step 2 & 5 if possible (common case)
-
……
-
// Step 3, draw the content
-
onDraw(canvas);
-
……
-
// Step 4, draw the children
-
dispatchDraw(canvas);
-
-
// Step 6, draw decorations (scrollbars)
-
onDrawScrollBars(canvas);
-
……
-
// we're done...
-
return;
-
}
注释里说,draw方法用来绘制view。在调用之前必须保证布局过程已经完成。继承View时,重写onDraw方法,而不是本方法,如果一定要重写此方法,一定记得调用super.draw(
)。上述代码第三步,调用了onDraw方法。
-
/**
-
* Implement this to do your drawing.
-
*
-
* @param canvas the canvas on which the background will be drawn
-
*/
-
protected void onDraw(Canvas canvas) {
-
}
重写onDraw方法,绘制我们自己的View
-
/**
-
* Called by draw to draw the child views. This may be overridden
-
* by derived classes to gain control just before its children are drawn
-
* (but after its own view has been drawn).
-
* @param canvas the canvas on which to draw the view
-
*/
-
protected void dispatchDraw(Canvas canvas) {
-
-
}
重写dispatchDraw方法绘制子View
二、 ViewGroup
measure过程
ViewGroup调用measure测量自己,过程和View一样的。但如果我们需要测量子View的大小(如果后面的过程里需要获取子View的大小,一定必须先测量)就需要重写onMeasure方法,然后遍历子View依次测量了
-
@Override
-
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-
int count = getChildCount();
-
for (int i = 0; i < count; i++) {
-
View child = getChildAt(i);
-
child.measure(widthMeasureSpec, heightMeasureSpec);//方法1
-
// measureChild(child, widthMeasureSpec, heightMeasureSpec);//方法2
-
}
-
// measureChildren(widthMeasureSpec, heightMeasureSpec); //方法3
-
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-
}
测量子view一共有三个方法
①child.measure(widthMeasureSpec, heightMeasureSpec)
②measureChild(child,widthMeasureSpec, heightMeasureSpec)
③measureChildren(widthMeasureSpec, heightMeasureSpec)
在ViewGroup的源码里。measureChild最终调用了measure方法
-
/**
-
* Ask one of the children of this view to measure itself, taking into
-
* account both the MeasureSpec requirements for this view and its padding.
-
* The heavy lifting is done in getChildMeasureSpec.
-
*
-
* @param child The child to measure
-
* @param parentWidthMeasureSpec The width requirements for this view
-
* @param parentHeightMeasureSpec The height requirements for this view
-
*/
-
protected void measureChild(View child, int parentWidthMeasureSpec,
-
int parentHeightMeasureSpec) {
-
……
-
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
-
}
在ViewGroup的源码里。measureChildren遍历子View最终也调用了measure方法
-
/**
-
* Ask all of the children of this view to measure themselves, taking into
-
* account both the MeasureSpec requirements for this view and its padding.
-
* We skip children that are in the GONE state The heavy lifting is done in
-
* getChildMeasureSpec.
-
*
-
* @param widthMeasureSpec The width requirements for this view
-
* @param heightMeasureSpec The height requirements for this view
-
*/
-
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);
-
}
-
}
-
}
Layout过程
继承ViewGroup时,onLayout方法必须重写
在ViewGroup中,onlayout方法是抽象方法
-
@Override
-
protected abstract void onLayout(boolean changed,
-
int l, int t, int r, int b);
重写onlayout方法,遍历子View,每个子View依次调用layout方法放置自己
draw过程
ViewGroup绘制自身的过程和View的过程是一样的
到这里View和ViewGroup的几个主要方法我们已经都了解了
三、生命周期
放一张网上比较流行的View的生命周期图,实际调用是不是这种情况呢?

讲道理的话,这幅图描绘的生命周期正是View的生命周期,但实际运用中,我们常常可以看到自定义View的onMeasure被调用很多次,这是什么原因导致的呢?
按照我们上面所讲述的层次关系,Activity的布局是被放置在一个id为content的窗口控件中的,通常这个控件是FrameLayout,它将会两次测量子控件的长宽,举个最简单的例子,如果我们的项目只有一个activity,而这个activity的布局文件只放置我们的自定义View,那么我们可以看到这个View的onMeasure方法被调用了两次,原因是它的父控件(FrameLayout)让它进行了两次measure。如果我们在自定义View外再套一层RelativeLayout,因为RelativeLayout也会测量两次子view的长宽值,所以此时的自定义View的onMeasure方法会被调用四次。再套一层RelativeLayout,则自定义View的onMeasure被调用8次,以此类推。因此,我们自定义View的onMeasure和onLayout调用几次完全取决于它有多少外层的控件,以及这些控件会调用测量其子控件多少次。