1:View 工作原理,

整个View 树的绘制是在ViewRoot.java 类的performTraversals() 函数展开的,该函数做的执行过程可简单概括为

根据之前设置的状态 判断是否需要重新计算视图大小(measure)是否重新需要安置视图的位置(layout) 以及是否需要重绘(draw)


mView :DecorView 对象

ViewGroup

ViewGroup view view

view view view



Mesaure() 过程

主要作用:为整个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视图位于父视图的坐标轴,即mLeft,mTop,mLeft,mBottom(调用setFrame()函数去实现)

接下来回调onLayout()方法(如果该View是ViewGroup对象,需要实现该方法,对每个子视图进行布局) ;

2、如果该View是个ViewGroup类型,需要遍历每个子视图chiildView,调用该子视图的layout()方法去设置它的坐标值。

layout函数原型为 ,位于View.java

[java] view plain copy
  1. /*final标识符,不能被重载,参数为每个视图位于父视图的坐标轴
  2. *@paramlLeftposition,relativetoparent
  3. *@paramtTopposition,relativetoparent
  4. *@paramrRightposition,relativetoparent
  5. *@parambBottomposition,relativetoparent
  6. */
  7. publicfinalvoidlayout(intl,intt,intr,intb){
  8. booleanchanged=setFrame(l,t,r,b);//设置每个视图位于父视图的坐标轴
  9. if(changed||(mPrivateFlags&LAYOUT_REQUIRED)==LAYOUT_REQUIRED){
  10. if(ViewDebug.TRACE_HIERARCHY){
  11. ViewDebug.trace(this,ViewDebug.HierarchyTraceType.ON_LAYOUT);
  12. }
  13. onLayout(changed,l,t,r,b);//回调onLayout函数,设置每个子视图的布局
  14. mPrivateFlags&=~LAYOUT_REQUIRED;
  15. }
  16. mPrivateFlags&=~FORCE_LAYOUT;
  17. }

流程三、 draw()绘图过程

由ViewRoot对象的performTraversals()方法调用draw()方法发起绘制该View树,值得注意的是每次发起绘图时,并不

会重新绘制每个View树的视图,而只会重新绘制那些“需要重绘”的视图,View类内部变量包含了一个标志位DRAWN,当该

视图需要重绘时,就会为该View添加该标志位。

调用流程 :

mView.draw()开始绘制,draw()方法实现的功能如下:

1 、绘制该View的背景

2 、为显示渐变框做一些准备操作(见5,大多数情况下,不需要改渐变框)

3、调用onDraw()方法绘制视图本身 (每个View都需要重载该方法,ViewGroup不需要实现该方法)

4、调用dispatchDraw ()方法绘制子视图(如果该View类型不为ViewGroup,即不包含子视图,不需要重载该方法)

值得说明的是,ViewGroup类已经为我们重写了dispatchDraw ()的功能实现,应用程序一般不需要重写该方法,但可以重载父类

函数实现具体的功能。

4.1 dispatchDraw()方法内部会遍历每个子视图,调用drawChild()去重新回调每个子视图的draw()方法(注意,这个

地方“需要重绘”的视图才会调用draw()方法)。值得说明的是,ViewGroup类已经为我们重写了dispatchDraw()的功能

实现,应用程序一般不需要重写该方法,但可以重载父类函数实现具体的功能。

5、绘制滚动条

于是,整个调用链就这样递归下去了。

同样地,使用伪代码描述如下:

[java] view plain copy
  1. //draw()过程ViewRoot.java
  2. //发起draw()的"发号者"在ViewRoot.java里的performTraversals()方法,该方法会继续调用draw()方法开始绘图
  3. privatevoiddraw(){
  4. //...
  5. ViewmView;
  6. mView.draw(canvas);
  7. //....
  8. }
  9. //回调View视图里的onLayout过程,该方法只由ViewGroup类型实现
  10. privatevoiddraw(Canvascanvas){
  11. //该方法会做如下事情
  12. //1、绘制该View的背景
  13. //2、为绘制渐变框做一些准备操作
  14. //3、调用onDraw()方法绘制视图本身
  15. //4、调用dispatchDraw()方法绘制每个子视图,dispatchDraw()已经在Android框架中实现了,在ViewGroup方法中。
  16. //应用程序程序一般不需要重写该方法,但可以捕获该方法的发生,做一些特别的事情。
  17. //5、绘制渐变框
  18. }
  19. //ViewGroup.java中的dispatchDraw()方法,应用程序一般不需要重写该方法
  20. @Override
  21. protectedvoiddispatchDraw(Canvascanvas){
  22. //
  23. //其实现方法类似如下:
  24. intchildCount=getChildCount();
  25. for(inti=0;i<childCount;i++){
  26. Viewchild=getChildAt(i);
  27. //调用drawChild完成
  28. drawChild(child,canvas);
  29. }
  30. }
  31. //ViewGroup.java中的dispatchDraw()方法,应用程序一般不需要重写该方法
  32. protectedvoiddrawChild(Viewchild,Canvascanvas){
  33. //....
  34. //简单的回调View对象的draw()方法,递归就这么产生了。
  35. child.draw(canvas);
  36. //.........
  37. }


关于绘制背景图片详细的过程,请参考我的另外的博客:

<<Android中View(视图)绘制不同状态背景图片原理深入分析以及StateListDrawable使用详解>>


强调一点的就是,在这三个流程中,Google已经帮我们把draw()过程框架已经写好了,自定义的ViewGroup只需要实现

measure()过程和layout()过程即可 。


这三种情况,最终会直接或间接调用到三个函数,分别为invalidate(),requsetLaytout()以及requestFocus() ,接着

这三个函数最终会调用到ViewRoot中的schedulTraversale()方法,该函数然后发起一个异步消息,消息处理中调用

performTraverser()方法对整个View进行遍历。

invalidate()方法 :

说明:请求重绘View树,即draw()过程,假如视图发生大小没有变化就不会调用layout()过程,并且只绘制那些“需要重绘的”

视图,即谁(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()方法:

当View的可视状态在INVISIBLE/ VISIBLE 转换为GONE状态时,会间接调用requestLayout() 和invalidate方法。

同时,由于整个个View树大小发生了变化,会请求measure()过程以及draw()过程,同样地,只绘制需要“重新绘制”的视图。

requestFocus()函数说明:

说明:请求View树的draw()过程,但只绘制“需要重绘”的视图。

下面写个简单的小Demo吧,主要目的是给大家演示绘图的过程以及每个流程里该做的一些功能。截图如下:




1、 MyViewGroup.java 自定义ViewGroup类型

[java] view plain copy
  1. /**
  2. *@authorhttp://http://blog.csdn.net/qinjuning
  3. */
  4. //自定义ViewGroup对象
  5. publicclassMyViewGroupextendsViewGroup{
  6. privatestaticStringTAG="MyViewGroup";
  7. privateContextmContext;
  8. publicMyViewGroup(Contextcontext){
  9. super(context);
  10. mContext=context;
  11. init();
  12. }
  13. //xml定义的属性,需要该构造函数
  14. publicMyViewGroup(Contextcontext,AttributeSetattrs){
  15. super(context,attrs);
  16. mContext=context;
  17. init();
  18. }
  19. //为MyViewGroup添加三个子View
  20. privatevoidinit(){
  21. //调用ViewGroup父类addView()方法添加子View
  22. //child对象一:Button
  23. Buttonbtn=newButton(mContext);
  24. btn.setText("IamButton");
  25. this.addView(btn);
  26. //child对象二:ImageView
  27. ImageViewimg=newImageView(mContext);
  28. img.setBackgroundResource(R.drawable.icon);
  29. this.addView(img);
  30. //child对象三:TextView
  31. TextViewtxt=newTextView(mContext);
  32. txt.setText("OnlyText");
  33. this.addView(txt);
  34. //child对象四:自定义View
  35. MyViewmyView=newMyView(mContext);
  36. this.addView(myView);
  37. }
  38. @Override
  39. //对每个子View进行measure():设置每子View的大小,即实际宽和高
  40. protectedvoidonMeasure(intwidthMeasureSpec,intheightMeasureSpec){
  41. //通过init()方法,我们为该ViewGroup对象添加了三个视图,Button、ImageView、TextView
  42. intchildCount=getChildCount();
  43. Log.i(TAG,"thesizeofthisViewGroupis---->"+childCount);
  44. Log.i(TAG,"****onMeasurestart*****");
  45. //获取该ViewGroup的实际长和宽涉及到MeasureSpec类的使用
  46. intspecSize_Widht=MeasureSpec.getSize(widthMeasureSpec);
  47. intspecSize_Heigth=MeasureSpec.getSize(heightMeasureSpec);
  48. Log.i(TAG,"****specSize_Widht"+specSize_Widht+"*specSize_Heigth*****"+specSize_Heigth);
  49. //设置本ViewGroup的宽高
  50. setMeasuredDimension(specSize_Widht,specSize_Heigth);
  51. for(inti=0;i<childCount;i++){
  52. Viewchild=getChildAt(i);//获得每个对象的引用
  53. child.measure(50,50);//简单的设置每个子View对象的宽高为50px,50px
  54. //或者可以调用ViewGroup父类方法measureChild()或者measureChildWithMargins()方法
  55. //this.measureChild(child,widthMeasureSpec,heightMeasureSpec);
  56. }
  57. }
  58. @Override
  59. //对每个子View视图进行布局
  60. protectedvoidonLayout(booleanchanged,intl,intt,intr,intb){
  61. //TODOAuto-generatedmethodstub
  62. //通过init()方法,我们为该ViewGroup对象添加了三个视图,Button、ImageView、TextView
  63. intchildCount=getChildCount();
  64. intstartLeft=0;//设置每个子View的起始横坐标
  65. intstartTop=10;//每个子View距离父视图的位置,简单设置为10px吧。可以理解为android:margin=10px;
  66. Log.i(TAG,"****onLayoutstart****");
  67. for(inti=0;i<childCount;i++){
  68. Viewchild=getChildAt(i);//获得每个对象的引用
  69. child.layout(startLeft,startTop,startLeft+child.getMeasuredWidth(),startTop+child.getMeasuredHeight());
  70. startLeft=startLeft+child.getMeasuredWidth()+10;//校准startLeft值,View之间的间距设为10px;
  71. Log.i(TAG,"****onLayoutstartLeft****"+startLeft);
  72. }
  73. }
  74. //绘图过程Android已经为我们封装好了,这儿只为了观察方法调用程
  75. protectedvoiddispatchDraw(Canvascanvas){
  76. Log.i(TAG,"****dispatchDrawstart****");
  77. super.dispatchDraw(canvas);
  78. }
  79. protectedbooleandrawChild(Canvascanvas,Viewchild,longdrawingTime){
  80. Log.i(TAG,"****drawChildstart****");
  81. returnsuper.drawChild(canvas,child,drawingTime);
  82. }
  83. }

2、MyView.java自定义View类型,重写onDraw()方法 ,

[java] view plain copy
  1. //自定义View对象
  2. publicclassMyViewextendsView{
  3. privatePaintpaint=newPaint();
  4. publicMyView(Contextcontext){
  5. super(context);
  6. //TODOAuto-generatedconstructorstub
  7. }
  8. publicMyView(Contextcontext,AttributeSetattrs){
  9. super(context,attrs);
  10. }
  11. protectedvoidonMeasure(intwidthMeasureSpec,intheightMeasureSpec){
  12. //设置该View大小为8080
  13. setMeasuredDimension(50,50);
  14. }
  15. //存在canvas对象,即存在默认的显示区域
  16. @Override
  17. publicvoidonDraw(Canvascanvas){
  18. //TODOAuto-generatedmethodstub
  19. super.onDraw(canvas);
  20. Log.i("MyViewGroup","MyViewisonDraw");
  21. //加粗
  22. paint.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD));
  23. paint.setColor(Color.RED);
  24. canvas.drawColor(Color.BLUE);
  25. canvas.drawRect(0,0,30,30,paint);
  26. canvas.drawText("MyView",10,40,paint);
  27. }
  28. }


主Activity只是显示了该xml文件,在此也不罗嗦了。 大家可以查看该ViewGroup的Log仔细分析下View的绘制流程以及

相关方法的使用。第一次启动后捕获的Log如下,网上找了些资料,第一次View树绘制过程会走几遍,具体原因可能是某些

View 发生了改变,请求重新绘制,但这根本不影响我们的界面显示效果 。

总的来说: 整个绘制过程还是十分十分复杂地,每个具体方法的实现都是我辈难以立即的,感到悲剧啊。对Android提

供的一些ViewGroup对象,比如LinearLayout、RelativeLayout布局对象的实现也很有压力。 本文重在介绍整个View树的绘制

流程,希望大家在此基础上,多接触源代码进行更深入地扩展。








更多相关文章

  1. android使用豆瓣API出现500错误及解决方法
  2. Android(安卓)jni代码注册本地方法
  3. Android—网络编程
  4. 【Android】从无到有:手把手一步步教你构建并使用RecyclerView
  5. android设置控件背景透明、半透明的方法
  6. Android(安卓)Studio上用真机调试时,无法查看Logcat日志信息解决
  7. webview使用中遇到的坑
  8. Android开发规范实战
  9. Intent详解 (一) : 显式Intent

随机推荐

  1. Android(安卓)VideoView播放视频
  2. Android API开发之TTS开发之Android TTS
  3. Android处理EditText键盘自动隐藏
  4. Android开发艺术探索 第2章 IPC机制(部分
  5. Android中判断网络功能是否可用
  6. Android 系统字体和颜色样式
  7. Android(安卓)面试题及答案(英文)
  8. 在android 中开发java.net.SocketExcepti
  9. Android 网络多线程断点下载
  10. Android 设置系统SystemUI 顶部StatusBar