Android 自定义View之View的绘制流程(一)

View绘制流程网上已经有很多详细介绍的文章,这里权当给自己最近在这方面的学习的一个记录,另外本文主要是记录自己在实际代码的一些写法,一来记录基础知识方便自己后面复习二来在开发中可以参考这段时间的代码记录多作参考,一般遇到问题都会在网上找答案,如果自己多记录参考自己的文章何乐而不为呢。

1. 从setContentView(int layoutId)方法说起

做Android开发的都知道,我们写的XML布局文件是通过在Activity的onCreate()方法的setContentView()方法加载到屏幕上的,在XML文件中包含了ViewGroup(LinearLayout, FrameLayout)和View(TextView, Button, ImageView),那么它们是怎么显示上去的呢?

先看图


从图中可以看出其实setContentView并没有View或者布局文件做显示的动作,只是做了一些初始化的工作,主要包括以下几点:

1. 获取Windows对象,Window是一个抽象类,在Android实现中目前只有一个实现类,PhoneWindow,在我们的Activity中调用setContentView其实调用的PhoneWindow对象的setContentView方法。

2. 在PhoneWindow中的setContentView中调用了installDecor方法,在installDecor方法中调用了generateDecor方法,然后new DecorView,创建 了一个DecorView对象,这个DecorView对象其实是PhoneWindow的一个内部类,相当于是一个View的包装类,除了View引用之外还包含有其它一些属性,如以Title Bar的显示管理。

3. 调用LayoutInflater的inflate方法初始化一个系统的XML布局文件如R.layout.screen_simple,这个系统的布局文件就是我们屏幕上看到的显示的全部,包括TitleBar,ActionBar及mContentParent等内容,其中mContentParent是用来显示加载我们自己的布局XML的一个ViewGroup,其实是一个FrameLayout,然后把加载出来的这个布局文件对应的View赋值给mDecor,到这儿,mDecor就初始化完成了,而且在这里有两个重要对象也被初始化了,即mDecor和mContentParent对象,平时大家在Codding过程中想必会经常直接或者间接用到这两个对象吧。

4. 再调用LayoutInflater的inflate方法解析我们自己的XML布局文件,也就是我们在自己的Activity中onCreate方法中调用setContentView方法中传入的Layout ID,然后把解析出来的View添加到步骤2中的对象mContentParent中,这样就把我们自己的Layout添加到mDecor中。

看到这里也只是把我们自己的Layout添加到了mDecor中但是还没有真正的显示到屏幕上,下面我们就接着继续。

2. 控件是怎么显示的

相信大家都对Activiy的生命周期都知道吧,前面我们在生命周期中的onCreate方法中调用了setContentView方法,在Activity生命周期方法中紧接着就是onResume方法,在Activity的调用流程中我们知道,它都是通过Binder进程间通讯来实现的,通过ActivityManagerService来具体实现的,而最终是通过Handler消息分发机制最张调用到ActivityThread中mH的handleMessage中what等于RESUME_ACTIVITY的条件中,最终调用了ActivityThread中的handleResumeActivity方法来进行后面的逻缉的,那么我们从handleResumeActivity这个方法看起。

套路,还是先上图:


从前面描述及流程图中可以看出,View的测量(Measure),布局(Layout),绘制(Draw)都是在生命周期的onResume在进行的,但其实最终还是调用View自身或者其父容器完成的,具体过程如下:

1. 在onCreate方法中也即Activity生命周期的第一个方法我们通常会调用setContentView(layoutID),这个过程我们在前面已经说过了,紧接着进行生命周期的第二个方法onResume(通过AMS及Handler机制),最终会通过mH中handleMessage()方法调用what=RESUME_ACTIVITY的逻辑判断体内,即调用handleResumeActivity方法。

2. 在handleResumeActivity方法中会调用WindowManagerImpl的addView(mDecor,LayoutParam)把前面创建的DecorView(即从系统加载的那个XML布局文件,同样也包含了我们自己的布局文件)。

3. 在WindowManagerImpl内部其实是通过WindowManagerGlobal去完成addView的操作的。


4. 在WindowManagerGlobal的addView方法中调用了ViewRootImpl的setView方法,在这个方法中依次调用requestLayout、scheduleTraversals然后通过Choreographer调用到doTraversal方法中,这里Choreographer并没有在流程图中体现出来,这个类我们可以大致理解为其跟Handler,Looper功能类似,用于消息处理及回调的,但这里仅能说类似肯定有很大的不同要不然Google肯定就直接用Handler,至于它的具体实现这里没有去特别关注,只需要明白一点,通过它最终会调到doTraversal方法,代码如下:

    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
        }
    }

5. 通过doTraversal方法会调用到performTraversals方法中,在这个方法中首先会设定一个Rect为mWinFrame表示可以绘制大区域,然后调用方法performMeasure开始所有View的测量工作。

6. 在performMeasure内部最终于会通过调用onMeasure(widthMeasureSpec, heightMeasureSpec)方法,这个方法我想应该相当熟悉了,我们在自定义View过程中就是通过这个方法来实现我们自己的业务需求测量过程的。

7. 完成测量工作后紧接着进入performLayout方法中,在这个方法中进行摆放工作,通过layout方法最终会调用到View的onLayout(changed, l, t, r, b)方法中,这个方法想必也应该很亲切吧,我们在自定义View的过程中就是通过这个方法来实现View的布局工作的。

8. 完成Layout后会进入performDraw方法中,然后调用draw方法,drawSoftware方法,然后调用到View.draw(canvas),在draw方法中,依次进行background、view's content、Draw children、Draw decorations (scrollbars for instance)过程,其中view's content是我们自定义View的显示内容,其实就是调用onDraw(Canvas)方法来实现绘制的,关于onDraw方法这里也不再多提,它就是通过Canvas及Paint在指定的区域绘制我们想要的内容。

这里我们就完成了View的3个主要步骤,Measure、Layout、Draw,注意在这3个过程中,其都是采用递归的方式从上到下依次进行测量、摆放和绘制过程的。

3. 小结

根据以上1、2小节已经对View的绘制流程在Android中有了一个大致的认识,对于Android源码分析只需要对流程和一些关键点掌握即可,不必要每个点每行代码都必须分析得很清楚,有这些时间我们可以用来做些其它更有用的积累,对流程了解后对于在代码编写和遇到一些莫名其妙的Bug上会有很在帮助,在遇到一些问题时可以通过查看源码帮助解决,好了,总结一下View的绘制流程:

1. 在onCreate 方法中通过setContentView将View及ViewGroup初始化,即将我们自己的XML文件转换了Java对象,并添加到系统根布局DecorView中。

2. 在onResume方法中先将我们的DecorView添加到WindowManager然后通过ViewRootImpl来进行View的具体绘制流程。

3. 在ViewRootImpl的performTraversals()中调用performMeasure然后分别调用View的onMeasure方法对View自己进行测量,测量即计算自己的weight和height。这个过程完成后,我们就可以通过getMeasuredWeight和getMeasuredHeght方法来获取View测量的宽高。

4. 然后通过performLayout最终通过View自己的onLayout方法来摆放自己,即把自己放到合适的位置,在这个过程中我们应该注意View的边距问题。

5. 最后通过performDraw进行具体的绘制,这个方法最张也是调用View自己的onDraw方法进行绘制的,一般在ViewGroup中会调用DrawChild方法进行子View的绘制。

给出一个关系包含图,方便记忆:


View的绘制就简单说到这里,后面将会有专门的文章来介绍前面提到的View的Measure测量、Layout摆放及Draw绘制的相关知识及简单实践期待自己有更多的时间学习。

猜你喜欢

转载自blog.csdn.net/rayht/article/details/80782697