View 的绘制流程

2016-10-25  本文已影响111人  TTTqiu

转自:http://www.jianshu.com/p/a3014f8442b0


一、简介
不论在学习Android还是在做Android开发,我们都离不开View,所以学好View对一个Android开发人员来说尤为重要。Android中的每个控件都会在界面上得到一块矩形的区域,而在Android中,控件大致被分为两类,即ViewGroup 控件和View控件。ViewGroup控件作为父控件可以包含多个View控件,并管理其包含的View控件。下面分条来对View做一个简单的介绍。

二、View的原理介绍

  1. View表示的的屏幕上的某一块矩形的区域,而且所有的View都是矩形的;

  2. 如同简介中介绍,View是不能添加子View的,而ViewGroup是可以添加子View的。ViewGroup之所以能够添加子View,是因为它实现了两个接口:ViewParentViewManager

  3. Activity之所以能加载并且控制View,是因为它包含了一个Window,所有的图形化界面都是由View显示的而Service之所以称之为没有界面的activity是因为它不包含有Window,不能够加载View;

  4. 一个View有且只能有一个父View;

  5. 在Android中Window对象通常由PhoneWindow来实现的,PhoneWindow将一个DecorView设置为整个应用窗口的根View,即DecorView为整个Window界面的最顶层View。也可以说DecorView将要显示的具体内容呈现在了PhoneWindow上;

  6. DecorView是FrameLayout的子类,它继承了FrameLayout,即顶层的FrameLayout的实现类是Decorview,它是在phoneWindow里面创建的;

  7. 顶层的FrameLayout的父view是Handler,Handler的作用除了线程之间的通讯以外,还可以跟WindowManagerService进行通讯;

  8. windowManagerService是后台的一个服务,它控制并且管理者屏幕;

  9. 一个应用可以有很多个window,其由windowManager来管理,而windowManager又由windowManagerService来管理;

  10. 如果想要显示一个view那么他所要经历三个方法:1.测量measure, 2.布局layout, 3.绘制draw

三、View的测量/布局/绘制过程
显示一个View主要进过以下三个步骤:
1、Measure测量一个View的大小

2、Layout摆放一个View的位置

** 3、Draw画出View的显示内容**其中measure和layout方法都是final的,无法重写,虽然draw不是final的,但是也不建议重写该方法。这三个方法都已经写好了View的逻辑,如果我们想实现自身的逻辑,而又不破坏View的工作流程,可以重写onMeasure、onLayout、onDraw方法。下面来一一介绍这三个方法。

测量/布局/绘制流程
3.1 View的测量

Android系统在绘制View之前,必须对View进行测量,即告诉系统该画一个多大的View,这个过程在onMeasure()方法中进行。测量过程如下图所示:

Measure测量流程

3.1.1 MeasureSpec类
Android系统给我们提供了一个设计小而强的工具类———MeasureSpec类

  1. MeasureSpe描述了父View对子View大小的期望。里面包含了测量模式和大小。
  2. MeasureSpe类把测量模式和大小组合到一个32位的int型的数值中,其中高2位表示模式,低30位表示大小而在计算中使用位运算的原因是为了提高并优化效率。
  3. 我们可以通过以下方式从MeasureSpec中提取模式和大小,该方法内部是采用位移计算。
    int specMode = MeasureSpec.getMode(measureSpec);
    int specSize = MeasureSpec.getSize(measureSpec);
    也可以通过MeasureSpec的静态方法把大小和模式合成,该方法内部只是简单的相加。
    MeasureSpec.makeMeasureSpec(specSize,specMode);

3.1.2 测量模式
在对View进行测量时,Android提供了三种测量模式:

View默认的onMeasure()方法只支持EXACTLY模式,所以如果在自定义控件的时候不重写onMeasure()方法的话,就只能使用EXACTLY模式,且控件只可以响应你指定的具体宽高值或者是match_parent属性。如果要让自定义的View支持wrap_content属性,那么就必须重写onMeasure()方法来指定wrap_content时的大小。

而通过上面介绍的MeasureSpec这个类,我们就可以获取View的测量模式和View想要绘制的大小。

3.1.3 MeasureSpec判定规则
在自定义View的时候要通过判断测量的模式,给出不同的测量值,下面的一张图表罗列了 MeasureSpec判定规则。

MeasureSpec判定规则

3.1.4 实例演示
** step1 **:自定义一个类继承FrameLayout重写构造方法:

public class CustomView extends FrameLayout {   
     //构造方法省略...
}

** step2 **:重写onMeasure()方法:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {       
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}

通过查看super.onMeasure()方法,发现系统最终会调用setMeasureDimension(int measuredWidth, int measuredHeight)方法将测量后的宽高设置进去,从而完成测量工作。所以接下来要做的就是将最终测量后的宽高值作为参数设置给setMeasureDimension()方法,即重写的onMeasure()方法代码如下:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {   
     setMeasureDimension(measureWidth(widthMeasureSpec), measureHeigth(heightMeasureSpec));
}

因为在上面我们调用了自定义的measureWidth()方法和measureHeight()方法对宽高进行了重新定义,接下来我们就来自定义测量值。

** step3 **:自定义测量值:这里以measureWidth()方法为例,来进行自定义测量值操作。首先,从MeasureSpec对象中获取到测量模式和测量大小值:

int specMode = MeasureSpec.getMode(widthMeasureSpec);
int specSize = MeasureSpec.getSize(widthMeasureSpec);

其次,通过判断测量模式,给出不同的测量值:
①当specMode为EXACTLY时,直接使用指定的specSize即可;
②当specMode为其他两种模式时,需要给它一个默认的大小。
** 注: 如果指定的是wrap_content属性,即AT_MOST模式,则需要取出我们指定的大小与specSize中最小**的一个来作为最后的测量值。参考代码如下:

private int measureWidth(int measureSpec) {        
    int width = 0;       
    /**     
    * 1、从MeasureSpec对象中提出出具体的测量模式和大小     
    */    
    int specMode = MeasureSpec.getMode(widthMeasureSpec);       
    int specSize = MeasureSpec.getSize(widthMeasureSpec);       
    /**     
    * 2、通过判断测量模式,给出不同的测量值     
    */    
    if (specMode == MeasureSpec.EXACTLY) {         
        // match_parent , accurate        
        width = specSize;    
    } else {        
        width = 200;    //给一个默认的大小       
        if (specMode == MeasureSpec.AT_MOST) {             
            // wrap_content            
            width = Math.min(width,specSize); //注意取两者之间小的值       
        }    
    }    
    return width;
}

对于measureHeight()方法基本上与上面的measureWidth()方法一致,此处省略。通过以上三个步骤即可搞定View的测量,接下来简单介绍一下布局。

3.1.5 拓展
如果想在activity中的onCreat()方法中获取控件测量以后的宽跟高,那么可以用下面的方法:

final TextView tv = (TextView) findViewById(R.id.tv);
tv.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {        
    @Override    
    public void onGlobalLayout() {                
        int measuredWidth = tv.getMeasuredWidth();                
        int measuredHeight = tv.getMeasuredHeight();    
    }
});

3.2 View的布局
首先我们来看一下layout布局流程图:

View布局

接下来一一介绍上面流程图中的三个参数:

@Overrideprotected void onLayout(boolean changed, int left, int top, int right, int bottom) {     
    // super.onLayout(changed, left, top, right, bottom);     
    //重写~ ~ ~(略)
}

3.3 View的绘制
draw方法绘制要遵循一定的顺序:

  1. 画背景
  2. 画边缘
  3. 画自身: ondraw方法
  4. 画子View: dispatchDraw方法
  5. 画滚动条

首先我们来看一下draw绘制流程:

draw绘制流程

以下是对上面三个方法的说明:

上一篇 下一篇

猜你喜欢

热点阅读