View的绘制流程
整个View的绘制流程时在ViewRoot.java类的performTraversals()函数展开的,该函数做的执行过程可简单概括为根据之前设置的状态,判断是否需要重新计算视图的大小(measure),是否需要重新安置视图的位置(layout),以及是否需要重新绘制(draw).
整个View树的结构,对每个View的操作相当于是一个递归的实现.
流程一 : measure()过程
主要作用 : 为整个View树计算实际的大小,即设置实际的高(对应属性:mMeasuredHeight)和宽(对应属性:mMeasureWidth),每个View的空间的实际宽高都是有父视图和本身视图决定的
具体的调用链如下:
ViewRoot根对象的属性mView(其类型一般为ViewGroup类型)调用measure()方法去计算View树的大小,回调View/ViewGroup对象的onMeasure()方法,该方法实现的功能如下:
1.设置本View视图的最终大小,该功能的实现通过调用setMeasuredDimension()方法去设置实际的高(对应属性:mMeasuredHeight)和宽(对应属性:mMeasureWidth);
2.如果该View对象是个ViewGroup类型,需要重写该onMeasure()方法,对其子视图进行遍历的measure()过程.
2.1对每个姿势突的measure()过程,是通过调用父类ViewGroup.java类李的measureChildWithMargins()方法去实现,该方法内部只是简单的调用了View对象的measure()方法.(由于measureChildWithMargins()方法只是一个过渡层,更简单的做法时直接调用View对象的measure()方法).
整个measure调用流程就是个树形的递归过程
流程二. layout布局过程
主要作用 : 将整个View树放到合适的位置上.
具体的调用链如下:
host.layout()开始View树的布局, 继而回调给View/ViewGroup类中的layout()方法,具体流程如下:
1.layout方法会设置该View视图位于父视图的坐标轴位置,即,Left, mTop, mLeft, mBottom(调用setFrame()函数去实现).接下来回调onLayout()方法(如果该View对象是ViewGroup对象,需要实现该方法,对每个子视图进行布局);
2.如果该View是个ViewGroup类型,需要遍历每个子视图childView,调用该子视图的layout()方法去设置它的坐标值
整个View树的结构,对每个View的操作相当于是一个递归的实现.
流程一 : measure()过程
主要作用 : 为整个View树计算实际的大小,即设置实际的高(对应属性:mMeasuredHeight)和宽(对应属性:mMeasureWidth),每个View的空间的实际宽高都是有父视图和本身视图决定的
具体的调用链如下:
ViewRoot根对象的属性mView(其类型一般为ViewGroup类型)调用measure()方法去计算View树的大小,回调View/ViewGroup对象的onMeasure()方法,该方法实现的功能如下:
1.设置本View视图的最终大小,该功能的实现通过调用setMeasuredDimension()方法去设置实际的高(对应属性:mMeasuredHeight)和宽(对应属性:mMeasureWidth);
2.如果该View对象是个ViewGroup类型,需要重写该onMeasure()方法,对其子视图进行遍历的measure()过程.
2.1对每个姿势突的measure()过程,是通过调用父类ViewGroup.java类李的measureChildWithMargins()方法去实现,该方法内部只是简单的调用了View对象的measure()方法.(由于measureChildWithMargins()方法只是一个过渡层,更简单的做法时直接调用View对象的measure()方法).
整个measure调用流程就是个树形的递归过程
measure函数原型如下:
public final void meassure(int widthMeasureSpec, iunt heightMeasureSpec) {
//...
//回调onMEasure()方法
onMeasure(widthMeasureSpec, heightMeasureSpec);
//more
}
下面用伪代码来描述measure流程
//回调View视图里面的onMeasure过程
private void onMeasure(int heightm, int width){
//设置view的实际宽高
//1.该方法必须在onMeasure调用,否则会报异常
setMeasuredDimension(h, 1);
//2.如果该View时ViewGroup类型,则对它的每个子View进行measure()过程
int childCOunt = getChildCount();
for (int i = 0; i < childCount; i++) {
//2.1 获得每个子View对象引用
View child = getChildAt(i);
//整个measure()过程就是个递归过程
//该方法只是一个过滤器,最后会调用measure()过程;或者measureChild(child, h, i)方法
measureChildWithMargins(child, h, i);
//其实,对于我们自己写的应用来说,最好的办法就是去掉框架里面的该方法,直接调用view.measure(),如下:
//child.measure(h, l)
}
}
//该方法具体实现在ViewGroup.java中
protected void measureChildWithMargins(View v, int height, int width){
v.measure(h, l);
}
流程二. layout布局过程
主要作用 : 将整个View树放到合适的位置上.
具体的调用链如下:
host.layout()开始View树的布局, 继而回调给View/ViewGroup类中的layout()方法,具体流程如下:
1.layout方法会设置该View视图位于父视图的坐标轴位置,即,Left, mTop, mLeft, mBottom(调用setFrame()函数去实现).接下来回调onLayout()方法(如果该View对象是ViewGroup对象,需要实现该方法,对每个子视图进行布局);
2.如果该View是个ViewGroup类型,需要遍历每个子视图childView,调用该子视图的layout()方法去设置它的坐标值
layout函数原型如下,位于View.java中:
public final void layout(int l, int t, int r, int b) {
boolean changed = setFrame(l, t, r, b);//设置每个视图位于父视图的坐标轴
if (changed || (mPrivateFlags & LAY OUT_REQUIED) == LAYOUT_REQUIRED) {
if (ViewDebug.TRACE_HIERARCHY) {
ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_LAYOUT);
}
onLayout(changed, l, t, r, b); //回调onLayout函数,设置每个子视图的布局
mPrivateFlags &= ~LAYOUT_REQUIRED;
}
mPrivateFlags &= ~FORCE_LAYOUT;
}
同样的,将上面layout调用流程用伪代码描述:
// layout()过程 ViewRoot.java
// 发起layout()的"发号者"在ViewRoot.java里的performTraversals()方法, mView.layout()
private void performTraversals(){
<span style="white-space:pre"> </span> //...
<span style="white-space:pre"> </span> View mView ;
<span style="white-space:pre"> </span> mView.layout(left,top,right,bottom) ;
<span style="white-space:pre"> </span> //....
}
//回调View视图里的onLayout过程 ,该方法只由ViewGroup类型实现
private void onLayout(int left , int top , right , bottom){
<span style="white-space:pre"> </span>//如果该View不是ViewGroup类型
<span style="white-space:pre"> </span>//调用setFrame()方法设置该控件的在父视图上的坐标轴
<span style="white-space:pre"> </span>setFrame(l ,t , r ,b) ;
<span style="white-space:pre"> </span>//--------------------------
<span style="white-space:pre"> </span>//如果该View是ViewGroup类型,则对它的每个子View进行layout()过程
<span style="white-space:pre"> </span>int childCount = getChildCount() ;
<span style="white-space:pre"> </span>for(int i=0 ;i<childCount ;i++){
<span style="white-space:pre"> </span>//2.1、获得每个子View对象引用
<span style="white-space:pre"> </span>View child = getChildAt(i) ;
<span style="white-space:pre"> </span>//整个layout()过程就是个递归过程
<span style="white-space:pre"> </span>child.layout(l, t, r, b) ;
<span style="white-space:pre"> </span>}
}
流程三 . draw()绘制过程
由ViewRoot对象的performTraversals()方法调用draw()方法发起绘制该View树,值得注意的是每次发起绘图时,并不需要绘制每个View树的视图,而只会重新绘制需要绘制的视图,View类内部变量包含了一个标志位DRAWN,当该视图需要重新绘制的时候,就会为View添加该标志位.
调用流程 :
mView.draw()开始绘制,draw()方法实现功能如下:
1. 绘制该View的背景
2. 为显示渐变框做一些准备操作(大多情况下不需要改变渐变框)
3. 调用onDraw()方法绘制视图本身(每个View都需要重载该方法,ViewGroup不需要实现该方法)
4. 调用dispatchDraw()方法绘制姿势突(如果View类型不为ViewGroup,即不包含子视图,不需要重载该方法)
4.1 dispatchDraw()方法内部会遍历每个子视图,调用每个子视图的draw()方法(注意,这个地方"需要重新绘制的视图才会调用draw()方法).值得说明的时,ViewGroup类已经为我们重写了dispatchDraw()的功能实现,应用程序一不需要重写该方法但可以重载富勒函数实现具体的功能.
5. 绘制滚动条
强调一点就是,在上述的三个流程,measure(),layout()和draw()中,骨骼已经帮忙把dispatchDraw()过程框架写好了,自定义的ViewGroup只需要实现measure()过程和layout()过程即可.
这三种情况最终会直接或者简介调用三个函数,分别时invalidate(),requestLayout()以requestFocus(),接着这三个函数最终会调用刀ViewRoot中的scheduleTraversale()方法,该函数接着会发起一个异步消息,消息处理中调用performTraverser()方法对整个View进行遍历.
invalidate()方法:
说明: 请求重绘View树,即draw()过程,假如视图大小没有发生变化,则不会调用layout()过程,并且只绘制那些"需要绘制的视图",即谁调用invalidate(),就绘制该视图(如果是View调用,则重绘该View,如果时ViewGroup调用该方法,则重绘整个ViewGroup)请求invalidate()方法).
一般引起invalidate()操作的函数如下:
1. 直接调用invalidate()方法,请求重新draw(),但只会绘制调用者本身.
2. 调用setSelection()方法,请求重新draw(),但只会绘制调用者本身.
3. 调用setVisibility()方法,当View可忽视状态在INVISIBLE转换VISIBLE时,会简介调用invalidate()方法,继而绘制该View.
4. 调用setEnabled()方法,请求重新draw(),但不会重新绘制任何视图包括调用者.
requestLayout()方法:会导致调用measure()过程和layout()过程.
说明: 只是对View树重新布局layout过程包括measure()和layout()过程,不会调用draw()过程,但不会重新绘制任何视图包括调用者本身.
一般引起invalidate()操作的函数如下:
1. 调用setVisibility()方法,当视图状态在INVISIBLE/VISIBLE转换为GONE状态时,会间接调用requestLayout()和invalidate方法.同时,由于整个View树大小发生了变化,会请求measure()过程以及draw()过程,同样的只绘制需要重新绘制的视图.
requestFocus()函数说明:
说明: 请求View树的draw()过程,但只绘制需要重绘的视图.
由ViewRoot对象的performTraversals()方法调用draw()方法发起绘制该View树,值得注意的是每次发起绘图时,并不需要绘制每个View树的视图,而只会重新绘制需要绘制的视图,View类内部变量包含了一个标志位DRAWN,当该视图需要重新绘制的时候,就会为View添加该标志位.
调用流程 :
mView.draw()开始绘制,draw()方法实现功能如下:
1. 绘制该View的背景
2. 为显示渐变框做一些准备操作(大多情况下不需要改变渐变框)
3. 调用onDraw()方法绘制视图本身(每个View都需要重载该方法,ViewGroup不需要实现该方法)
4. 调用dispatchDraw()方法绘制姿势突(如果View类型不为ViewGroup,即不包含子视图,不需要重载该方法)
4.1 dispatchDraw()方法内部会遍历每个子视图,调用每个子视图的draw()方法(注意,这个地方"需要重新绘制的视图才会调用draw()方法).值得说明的时,ViewGroup类已经为我们重写了dispatchDraw()的功能实现,应用程序一不需要重写该方法但可以重载富勒函数实现具体的功能.
5. 绘制滚动条
强调一点就是,在上述的三个流程,measure(),layout()和draw()中,骨骼已经帮忙把dispatchDraw()过程框架写好了,自定义的ViewGroup只需要实现measure()过程和layout()过程即可.
这三种情况最终会直接或者简介调用三个函数,分别时invalidate(),requestLayout()以requestFocus(),接着这三个函数最终会调用刀ViewRoot中的scheduleTraversale()方法,该函数接着会发起一个异步消息,消息处理中调用performTraverser()方法对整个View进行遍历.
invalidate()方法:
说明: 请求重绘View树,即draw()过程,假如视图大小没有发生变化,则不会调用layout()过程,并且只绘制那些"需要绘制的视图",即谁调用invalidate(),就绘制该视图(如果是View调用,则重绘该View,如果时ViewGroup调用该方法,则重绘整个ViewGroup)请求invalidate()方法).
一般引起invalidate()操作的函数如下:
1. 直接调用invalidate()方法,请求重新draw(),但只会绘制调用者本身.
2. 调用setSelection()方法,请求重新draw(),但只会绘制调用者本身.
3. 调用setVisibility()方法,当View可忽视状态在INVISIBLE转换VISIBLE时,会简介调用invalidate()方法,继而绘制该View.
4. 调用setEnabled()方法,请求重新draw(),但不会重新绘制任何视图包括调用者.
requestLayout()方法:会导致调用measure()过程和layout()过程.
说明: 只是对View树重新布局layout过程包括measure()和layout()过程,不会调用draw()过程,但不会重新绘制任何视图包括调用者本身.
一般引起invalidate()操作的函数如下:
1. 调用setVisibility()方法,当视图状态在INVISIBLE/VISIBLE转换为GONE状态时,会间接调用requestLayout()和invalidate方法.同时,由于整个View树大小发生了变化,会请求measure()过程以及draw()过程,同样的只绘制需要重新绘制的视图.
requestFocus()函数说明:
说明: 请求View树的draw()过程,但只绘制需要重绘的视图.
下面有一个小Demo:
1. MyViewGroup.java自定义ViewGroup类型
public class MyViewGroup extends ViewGroup{
private static String TAG = "MyViewGroup";
private Context mContext;
public MyViewGroup(Context context) {
super(context);
mContext = context;
init();
}
//xml定义的属性,需要该构造函数
public MyViewGroup(Context context, AttributeSet attrs) {
super(context);
mContext = context;
init();
}
//为MyViewGroup添加三个子View
private void init() {
//调用ViewGroup父类addView()方法添加子View
//child 对象一: Button
Button btn = new Button(mContext);
btn.setTest("I am Button");
this.addView(btn);
//child 对象二: ImageView
ImageView img = new ImageView(mConetxt);
img.setBackgroundResource(R.drawable.icon);
this.addView(img);
//child 对象三: TextView
TextView txt = new TextView(mContext);
txt.setText("Only text");
this.addView(txt);
//child 对象四: 自定义View
MyView myView = new MyView(mContext);
this.addView(myView);
}
@Override
//对每个子View进行measure():设置每个子View的大小,即实际宽和高
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//通过init()方法,我们为该ViewGroup对象添加了三个视图,Button,ImageView,TextView
int childCount = getChildCount();
Log.i(TAG, "the size of this ViewGroup is ----> " + childCount);
Log.i(TAG, "**** onMeasure start ****");
//获取该ViewGroup的实际长和宽 设计刀MeasureSpec类的使用
int specSize_Width = MeasureSpec.getSize(widthMeasureSpec);
int SpecSize_Height = MeasureSpec.getSize(htightMeasureSpec);
Log.i(TAG, "**** specSize_Width" + specSize_Width + " * specSize_Height * ";
//設置本ViewGroup的寬高
setMeasuredDimension(specSize_Width, specSize_Height);
for (int i=0;i<childCount;i++) {
View child = getChildAt(i);
child.measure(50, 50); //简单的设置每个子View对象的宽高为50px, 50px
//或者可以调用ViewGroup父类方法measureChild()或者measureChildWithMargins()方法
//this.measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
}
@Override
//对每个子View视图进行布局
protected void onLayout(boolean changed, int l, int t, int r, int b) {
//TODO Auto-generated method stub
//通过init()方法,我们为该ViewGroup对象添加了三个视图
int childCount = getChildCount();
int startLeft = 0; //设置每个子View的起始横坐标
int startTop = 10; //每个子View距离父视图的位置, 简单设置为10px,可以理解为android:margin = 10px;
Log.i(TAG, "**** onLayout start ****");
for (int i=0; i<childCount; i++) {
View child = getChildAt(i);
child.layout(startLeft, startTop, startLeft.child.getMeasuredWidth(), startTop+child.getMeasuredHeight());
startLeft = startLeft + child.getMeasuredWidth() + 10; //校准startLeft值,View之间的间距设为10px;
Log.i(TAG, "**** onLayout startLeft ****" + startLeft);
}
}
//绘制过程Android已经为我们封装好了,在这里只打出Log
protected void dispatchDraw(Canvas canvas) {
Log.i(TAG, "**** dispatchDraw start ****");
super.dispatchDraw(canvas);
}
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
Log.i(TAG, "*** drawChild start****");
return super.drawChild(canvas, child, drawingTime);
}
}
2、MyView.java 自定义View类型,重写onDraw()方法
//自定义View对象
public class MyView extends View{
private Paint paint = new Paint() ;
public MyView(Context context) {
super(context);
// TODO Auto-generated constructor stub
}
public MyView(Context context , AttributeSet attrs){
super(context,attrs);
}
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
//设置该View大小为 80 80
setMeasuredDimension(50 , 50) ;
}
//存在canvas对象,即存在默认的显示区域
@Override
public void onDraw(Canvas canvas) {
// TODO Auto-generated method stub
super.onDraw(canvas);
Log.i("MyViewGroup", "MyView is onDraw ") ;
//加粗
paint.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD));
paint.setColor(Color.RED);
canvas.drawColor(Color.BLUE) ;
canvas.drawRect(0, 0, 30, 30, paint);
canvas.drawText("MyView", 10, 40, paint);
}
}
主Activity只是显示了该xml文件,在此也不罗嗦了。 大家可以查看该ViewGroup的Log仔细分析下View的绘制流程以及
相关方法的使用。第一次启动后捕获的Log如下,网上找了些资料,第一次View树绘制过程会走几遍,具体原因可能是某些
View 发生了改变,请求重新绘制,但这根本不影响我们的界面显示效果 。