(四)View的生命周期
(五)自定义View的具体实现
(六)事件分发机制
上一节中,我们讨论了自定义View的几个常用方法,掌握这些方法的使用,是自定义View的核心。那么这些方法是怎么调度的?我们一起从源码来看看吧。
我们平时看到的android屏幕,它的层次是这样的
DecorView:窗口的根View,即整个窗口的根视图,它继承了FrameLayout
窗口布局View:DecorView的子View
①它的风格属性由application或者activity中的android:Theme = “”指定,比如为Activity配置xml属性:
android:theme="@android:style/Theme.NoTitleBar"
也可以由activity的requestWindowFeature方法(最终调用了PhoneWindow.requestFeature方法)指定,比如
requestWindowFeature(Window.FEATURE_NO_TITLE);
上述两种方式指定风格属性以后,系统会根据他们拿到相应的窗口布局文件(下面源码中的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();///
}
}
源码中有几个比较重要的方法,他们在满足条件时会被调用
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)
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被调用很多次,这是什么原因导致的呢?
RelativeLayout,则自定义View的onMeasure被调用8次,以此类推。因此,我们自定义View的onMeasure和onLayout调用几次完全取决于它有多少外层的控件,以及这些控件会调用测量其子控件多少次。