View 绘制原理

基本操作由三个函数完成: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

    原文作者:PeOS
    原文地址: https://www.jianshu.com/p/279b6530f9e4
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞