view是以树形结构组织在一起,如下图所示:
这个图没有把RootView画出来,rootView只有一个孩子DecorVeiw,此DecorVeiw是
从android4.0/frameworks/base/core/res/res/layout/screen_title.xml这个文件中inflate出来的,
PhoneWindow.java的generateLayout方法中的关键代码片段
Viewin = mLayoutInflater.inflate(layoutResource, null);
decor.addView(in,new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT))
ViewGroupcontentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
contentParent,是我们在Activity中setContentView时其实就是将我们的布局挂在这个
contentParent上了,而contentParent又是挂靠在decor上的孩子view,所以一个activity的view布局就是一棵树,
所以绘制的时候也是按照树形结构,通过遍历来进行绘制。VeiwRoot中的draw函数准备好Canvas之后,
就会调用mVeiw.draw(Canvas),mVeiw就是调用ViewRoot.setView时设置的DecorView。DecorView本质
是一个FrameLayout,也就是说踏实一个Veiw,然后我们就可以来看看View.java中的draw函数都做了些什么事情:
/*
* Draw traversal performs several drawing steps which must beexecuted
* in the appropriate order:
*
* 1. Draw the background
* 2. If necessary, save the canvas' layers to prepare forfading
* 3. Draw view's content
* 4. Draw children
* 5. If necessary, draw the fading edges and restore layers
* 6. Draw decorations (scrollbars for instance)
*/
1.绘制背景
2.如果有需要,保存画布,为淡入淡出做准备。
3.绘制view本身的内容,通过调用onDraw(canvas)方法,次方法中通过Canvas,画各种图形,直线,圆,图片等。
4.绘制挂靠在自己上面的孩子Veiw,通过dispatchDraw(canvas)方法来实现,在view中这个方法是一个空函数,
这也好理解view下面不会挂靠孩子View,所以用不着这个方法。而VeiwGroup实现了此方法,因为VeiwGroup
是可以挂靠孩子Veiw的。VeiwGroup.java的dispatchDraw(canvas)方法中做的事情:
首先会判断有没有layoutAnimation,如果有的话,会将layoutAnimation通过LayoutAnimationContraller将
layoutAnimation与每一个孩子View进行关联。
然后轮询调用drawChild() 绘制孩子View
drawChild()---->child.draw(canvas,this,drawingTime),这样的调用过程保证每一个孩子view的draw方法都被调
用,通过递归调用,就保证整棵树上的view的draw方法都被调用,从而所有的view都能被绘制出来。
child.draw(canvas,this,drawingTime)方法中会调用
{
….
…..
more= drawAnimation(parent,drawingTime,a,scalingRequired);
…..
….
}
drawAnimation中通知父亲View绘制自己,形成动画效果
在调用每个子View的draw函数之前,需要绘制的View的绘制位置是在Canvas通过translate函数调用来进行切换
的,窗口中的所有View是共用一个Canvas对象。整棵树用的都是同一个Canvas。
Canvas是在什么时候切换坐标系的?
其实就是在这个方法里面进行转换的,什么时候呢?在view要更新时,当父亲Veiw调用
dispatchDraw(canvas)-->drawChild()---->child.draw(canvas,this,drawingTime);
然后child.draw(canvas,this,drawingTime)方法中回去变换canvas的坐标系,当坐标系变换好了之后,就会调用
当前child的draw(canvas)方法,此时的参数canvas已经是转换了坐标的。继续进入draw(canvas)中的六个步骤,依
次递归。如果veiw没有孩子就不会再调用dispatchDraw(canvas),直接将自己的内容画出来就结束了,如果还有孩
子Veiw,则还是会调用到dispatchDraw(canvas),继续递归。
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime)
5.如果有需要,绘制淡入淡出相关的内容并恢复保存的画布
6.绘制修饰内容(例如滚动条)
孩子view要更新都是通过invalidate()方法来通知父亲view来更新自己。这个过程一直上溯到ViewRoot,当ViewRoot
收到这个通知后,就会调用他的draw方法。然后通过递归调用完成绘制。Veiw的onDraw(Canvascanvas)方法中
都会有一个canvas,这个canvas其实就是在ViewRoot的draw方法中创建的那个canvas,所以整个View树公用的是
同一个Canvas实例,Canvas是有坐标系的,并且这个坐标系是可以变换的,平移,旋转,缩放,错切,对称等变
换。默认的坐标系是屏幕的左上角为(0,0),横向为x轴,竖向为y轴。Canvas的一个重要属性就是定义和view
对应的坐标系,你会发现,每个view中拿到的canvas的坐标系都是以该view的左顶点为原点的,这是通过canvas的
平移变换实现的。