本文转自 http://blog.csdn.net/u011791526/article/details/53925370
更多内容可参考
(一)自定义View的分类点击打开链接
(二)自定义View的构造方法及自定义属性点击打开链接
(三)自定义View的常用方法(测量、绘制、位置)点击打开链接
(四)View的生命周期
(五)自定义View的具体实现
(六)事件分发机制
上一节中,我们讨论了自定义View的几个常用方法,掌握这些方法的使用,是自定义View的核心。那么这些方法是怎么调度的?我们一起从源码来看看吧。
我们平时看到的android屏幕,它的层次是这样的
![](https://img-blog.csdn.net/20161229165706960?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdTAxMTc5MTUyNg==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
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"
源码如下
-
-
- int layoutResource;
- int features = getLocalFeatures();
-
- 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;
- }
-
- } else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0) {
-
-
- layoutResource = com.android.internal.R.layout.screen_progress;
-
- }
-
-
- 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);
- ……
- }
onMeasure方法一般在自定义的View中重写
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-
- 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过程
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 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方法一定必须重写(稍后会做分析)
-
-
-
-
-
-
-
-
-
-
-
-
-
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- }
draw过程
-
-
-
-
-
-
-
-
-
- public void draw(Canvas canvas) {
-
-
-
-
-
-
-
-
-
-
-
-
- ……
-
- ……
-
- onDraw(canvas);
- ……
-
- dispatchDraw(canvas);
-
-
- onDrawScrollBars(canvas);
- ……
-
- return;
- }
注释里说,draw方法用来绘制view。在调用之前必须保证布局过程已经完成。继承View时,重写onDraw方法,而不是本方法,如果一定要重写此方法,一定记得调用super.draw( )。上述代码第三步,调用了onDraw方法。
-
-
-
-
-
- protected void onDraw(Canvas canvas) {
- }
重写onDraw方法,绘制我们自己的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);
-
- }
-
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- }
测量子view一共有三个方法
①child.measure(widthMeasureSpec, heightMeasureSpec)
②measureChild(child,widthMeasureSpec, heightMeasureSpec)
③measureChildren(widthMeasureSpec, heightMeasureSpec)
在ViewGroup的源码里。measureChild最终调用了measure方法
-
-
-
-
-
-
-
-
-
- protected void measureChild(View child, int parentWidthMeasureSpec,
- int parentHeightMeasureSpec) {
- ……
- child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
- }
在ViewGroup的源码里。
measureChildren遍历子View最终也调用了measure方法
-
-
-
-
-
-
-
-
-
- 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的生命周期图,实际调用是不是这种情况呢?
![](https://img-blog.csdn.net/20161229174753270?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdTAxMTc5MTUyNg==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
讲道理的话,这幅图描绘的生命周期正是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调用几次完全取决于它有多少外层的控件,以及这些控件会调用测量其子控件多少次。