Android view的绘制流程(二)

本文详细解析了Android中View的绘制流程,包括layout布局、draw绘图等关键步骤,介绍了如何通过invalidate等方法触发视图更新。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

流程二 、 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 

 
  1.  /* final 标识符 , 不能被重载 , 参数为每个视图位于父视图的坐标轴 
  2.  * @param l Left position, relative to parent 
  3.  * @param t Top position, relative to parent 
  4.  * @param r Right position, relative to parent 
  5.  * @param b Bottom position, relative to parent 
  6.  */  
  7. public final void layout(int l, int t, int r, int b) {  
  8.     boolean changed = 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.   
  14.         onLayout(changed, l, t, r, b);//回调onLayout函数 ,设置每个子视图的布局   
  15.         mPrivateFlags &= ~LAYOUT_REQUIRED;  
  16.     }  
  17.     mPrivateFlags &= ~FORCE_LAYOUT;  
  18. }  

 

同样地, 将上面layout调用流程,用伪代码描述如下:   

 
  1. // layout()过程  ViewRoot.java   
  2. // 发起layout()的"发号者"在ViewRoot.java里的performTraversals()方法, mView.layout()   
  3.   
  4. private void  performTraversals(){  
  5.    
  6.     //...   
  7.       
  8.     View mView  ;  
  9.        mView.layout(left,top,right,bottom) ;  
  10.       
  11.     //....   
  12. }  
  13.   
  14. //回调View视图里的onLayout过程 ,该方法只由ViewGroup类型实现   
  15. private void onLayout(int left , int top , right , bottom){  
  16.   
  17.  //如果该View不是ViewGroup类型   
  18.  //调用setFrame()方法设置该控件的在父视图上的坐标轴   
  19.    
  20.  setFrame(l ,t , r ,b) ;  
  21.    
  22.  //--------------------------   
  23.    
  24.  //如果该View是ViewGroup类型,则对它的每个子View进行layout()过程   
  25.  int childCount = getChildCount() ;  
  26.    
  27.  for(int i=0 ;i<childCount ;i++){  
  28.   //2.1、获得每个子View对象引用   
  29.   View child = getChildAt(i) ;  
  30.   //整个layout()过程就是个递归过程   
  31.   child.layout(l, t, r, b) ;  
  32.  }  
  33. }  


 

流程三、 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、 绘制滚动条

 

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

    

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

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

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

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

引起View树重新绘制的因素有如下几种:

 

         1、 导致视图大小发生变化 ;

         2、导致ViewGroup重新为子视图分配位置

         3、视图显示情况发生变化需要重绘

 

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

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

     方法对整个View进行遍历。

 

   invalidate()方法

      说明:请求重绘View树,并且只绘制那些“需要重绘的”视图,即谁(View的话,只绘制该View ;ViewGroup,则绘制整个

          ViewGroup)请求invalidate()方法,就绘制该视图。

 

      一般引起invalidate()操作的函数如下:

                1、直接调用invalidate()方法,请求重新draw(),但只会绘制调用者本身。

                2、setSelection()方法 :请求重新draw(),但只会绘制调用者本身。

                3、setVisibility()方法 : 当View可视状态在INVISIBLE转换VISIBLE时,会间接调用invalidate()方法,继而绘制该View。

                4 、setEnabled()方法 : 请求重新draw(),但不会重新绘制任何视图包括该调用者本身。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值