View 绘制原理

作者: PeOS | 来源:发表于2017-03-13 15:15 被阅读66次

    基本操作由三个函数完成:measure()、layout()、draw(),其内部又分别包含了onMeasure()、onLayout()、onDraw()三个子方法。

    measure操作

    measure操作主要用于计算视图的大小,即视图的宽度(对应属性:mMeasureWidth)和高度(对应属性:mMeasuredHeight)。

    在view中定义为final类型,要求子类不能修改。每个View的控件的实际宽高都是由父视图和本身视图决定的。measure()函数中又会调用下面的函数:

    (1)onMeasure(),视图大小的将在这里最终确定,也就是说measure只是对onMeasure的一个包装,子类可以覆写onMeasure()方法实现自己的计算视图大小的方式,并通过setMeasuredDimension(width, height)保存计算结果。

    measure流程伪代码

    //回调View视图里的onMeasure过程  
    private void onMeasure(int height , int width){  
     //设置该view的实际宽(mMeasuredWidth)高(mMeasuredHeight)  
     //1、该方法必须在onMeasure调用,否者报异常。  
     setMeasuredDimension(h , l) ;  
       
     //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操作

    layout操作用于设置视图在屏幕中显示的位置。在view中定义为final类型,要求子类不能修改。layout()函数中有两个基本操作:

    (1)setFrame(l,t,r,b),l, t, r, b即子视图在父视图中的具体位置,该函数用于将这些参数保存起来;
    (2)onLayout(),在View中这个函数什么都不会做,提供该函数主要是为viewGroup类型布局子视图用的;

    layout流程伪代码

    // layout()过程  ViewRoot.java  
    // 发起layout()的"发号者"在ViewRoot.java里的performTraversals()方法, mView.layout()  
      
    private void  performTraversals(){  
       
        //...  
          
        View mView  ;  
           mView.layout(left,top,right,bottom) ;  
          
        //....  
    }  
      
    //回调View视图里的onLayout过程 ,该方法只由ViewGroup类型实现  
    private void onLayout(int left , int top , right , bottom){  
      
     //如果该View不是ViewGroup类型  
     //调用setFrame()方法设置该控件的在父视图上的坐标轴  
       
     setFrame(l ,t , r ,b) ;  
       
     //--------------------------  
       
     //如果该View是ViewGroup类型,则对它的每个子View进行layout()过程  
     int childCount = getChildCount() ;  
       
     for(int i=0 ;i<childCount ;i++){  
      //2.1、获得每个子View对象引用  
      View child = getChildAt(i) ;  
      //整个layout()过程就是个递归过程  
      child.layout(l, t, r, b) ;  
     }  
    }
    

    draw操作

    draw操作利用前两部得到的参数,将视图显示在屏幕上,到这里也就完成了整个的视图绘制工作。子类也不应该修改该方法,因为其内部定义了绘图的基本操作:

    (1)绘制背景;
    (2)如果要视图显示渐变框,这里会做一些准备工作;
    (3)绘制视图本身,即调用onDraw()函数。在view中onDraw()是个空函数,也就是说具体的视图都要覆写该函数来实现自己的显示(比如TextView在这里实现了绘制文字的过程)。而对于ViewGroup则不需要实现该函数,因为作为容器是“没有内容“的,其包含了多个子view,而子View已经实现了自己的绘制方法,因此只需要告诉子view绘制自己就可以了,也就是下面的dispatchDraw()方法;
    (4)绘制子视图,即dispatchDraw()函数。在view中这是个空函数,具体的视图不需要实现该方法,它是专门为容器类准备的,也就是容器类必须实现该方法;
    (5)如果需要(应用程序调用了setVerticalFadingEdge或者setHorizontalFadingEdge),开始绘制渐变框;
    (6)绘制滚动条。

    draw流程伪代码

    // draw()过程     ViewRoot.java  
    // 发起draw()的"发号者"在ViewRoot.java里的performTraversals()方法, 该方法会继续调用draw()方法开始绘图  
    private void  draw(){  
       
        //...  
     View mView  ;  
        mView.draw(canvas) ;    
          
        //....  
    }  
      
    //回调View视图里的onLayout过程 ,该方法只由ViewGroup类型实现  
    private void draw(Canvas canvas){  
     //该方法会做如下事情  
     //1 、绘制该View的背景  
     //2、为绘制渐变框做一些准备操作  
     //3、调用onDraw()方法绘制视图本身  
     //4、调用dispatchDraw()方法绘制每个子视图,dispatchDraw()已经在Android框架中实现了,在ViewGroup方法中。  
          // 应用程序程序一般不需要重写该方法,但可以捕获该方法的发生,做一些特别的事情。  
     //5、绘制渐变框    
    }  
      
    //ViewGroup.java中的dispatchDraw()方法,应用程序一般不需要重写该方法  
    @Override  
    protected void dispatchDraw(Canvas canvas) {  
     //   
     //其实现方法类似如下:  
     int childCount = getChildCount() ;  
       
     for(int i=0 ;i<childCount ;i++){  
      View child = getChildAt(i) ;  
      //调用drawChild完成  
      drawChild(child,canvas) ;  
     }       
    }  
    //ViewGroup.java中的dispatchDraw()方法,应用程序一般不需要重写该方法  
    protected void drawChild(View child,Canvas canvas) {  
     // ....  
     //简单的回调View对象的draw()方法,递归就这么产生了。  
     child.draw(canvas) ;  
       
     //.........  
    }  
    

    View绘制原理总概

    View树的绘制是一个递归的过程,从ViewGroup一直向下遍历,直到所有的子view都完成绘制。

    ViewRoot最发起measure、layout和draw的。ViewRoot中包含了窗口的总容器DecorView,ViewRoot中的performTraversal()方法会依次调用decorView的measure、layout、draw方法,从而完成view树的绘制。

    invalidate()方法

    invalidate()方法会导致View树的重新绘制,而且view中的状态标志mPrivateFlags中有一个关于当前视图是否需要重绘的标志位DRAWN,也就是说只有标志位DRAWN置位的视图才需要进行重绘。

    当视图调用invalidate()方法时,首先会将当前视图的DRAWN标志置位,之后有一个循环调用parent.invalidateChildinParent(),这样会导致从当前视图依次向上遍历直到根视图ViewRoot,这个过程会将需要重绘的视图标记DRAWN置位,之后ViewRoot调用performTraversals()方法,完成视图的绘制过程。

    invalidate(),requsetLaytout()以及requestFocus() ,这三个函数最终会调用到ViewRoot中的schedulTraversale()方法,该函数然后发起一个异步消息,消息处理中调用performTraverser()方法对整个View进行遍历。

    参考:
    http://blog.csdn.net/qinjuning/article/details/7110211/
    http://blog.csdn.net/xu_fu/article/details/7829721

    相关文章

      网友评论

        本文标题:View 绘制原理

        本文链接:https://www.haomeiwen.com/subject/vjafnttx.html