33、Android--自定义控件流程

自定义控件流程

View 和 ViewGroup

View的工作流程主要是指measure、layout和draw三大流程,即测量、布局和绘制。

33、Android--自定义控件流程

View的位置参数

View的位置参数如下图所示:

33、Android--自定义控件流程

通过上图,我们可以很方便的了解View的位置参数,View和MotionEvent提供的获取坐标的方法如下表所示:

方法 描述
View的获取坐标的方法:
getTop() 获取View自身的定边到其父布局顶边的距离。
getLeft() 获取View自身的左边到父布局左边的距离。
getRight() 获取View自身的右边到父布局左边的距离。
getBottom() 获取View自身的底边到父布局顶边的距离。
MotionEvent获取坐标的方法:
getX() 获取点击事件距离控件左边的距离,即视图坐标。
getY() 获取点击事件距离控件顶边的距离,即视图坐标。
getRawX() 获取点击事件距离整个屏幕左边的距离,即绝对坐标。
getRawY() 获取点击事件距离屏幕顶边的距离,即绝对坐标。

View的测量

当我们对View和ViewGroup进行测量时,首先是获取它的宽高信息,获取的方式有如下三种:

方法 描述
getMeasuredWidth() 对View上的内容进行测量后得到的View内容占据的宽度。
getWidth() View在设定好布局后整个View的宽度,也就是在onLayout之后。
getLayoutParams().width 测量后就确定值,getLayoutParams.width比getMeasureWidth多了margin和padding。

所以,在自定义控件时,有时候会获取到宽高信息为0的情况,就要对照上面的表格进行排查。

(1)View的测量

在Measure过程中,系统会将View的LayoutParams根据父容器所施加的规则转换成对应的MeasureSpec,然后再根据这个MeasureSpec来测量View的宽高。

MeasureSpec代表一个32位的int值,高2位代表SpecMode(测量模式),低30位代表SpecSize(规格大小),可以通过getSize()和getMode()来获取相应的值。

测量的模式可以分为以下三种:

模式 描述
EXACTLY 即精确模式,当控件的layout_width或layout_height为具体数值时,系统使用的是EXACTLY模式。
AT_MOST 最大值模式,当控件的layout_width或layout_height为wrap_content或match_parent时,控件的尺寸不能超过父控件允许的最大尺寸即可。
UNSPECIFIED 未指定模式,它不指定其大小测量模式,View想多大就多大。

系统最终会调用setMeasuredDimension(int measuredWidth,int measuredHeight)方法将测量后的宽高传递进去,以完成测量操作。

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    setMeasuredDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec));
}
/**测量宽度的模板代码*/
private int measureWidth(int measureSpec){
    int result = 0;
    int specMode = MeasureSpec.getMode(measureSpec);
    int specSize = MeasureSpec.getSize(measureSpec);
    if(specMode == MeasureSpec.EXACTLY){    // 精确数值
        result = specSize;
    }else{                                    // 非精确数值
        result = 200;
        if(specMode == MeasureSpec.AT_MOST){// 自动包含
            result = Math.min(result, specSize);// 取出指定大小与specSize中最小一个作为最后测量值。
        }
    }
    return result;
}
/**测量高度的模板代码*/
private int measureHeight(int measureSpec){
    int result = 0;
    int specMode = MeasureSpec.getMode(measureSpec);
    int specSize = MeasureSpec.getSize(measureSpec);
    
    if(specMode == MeasureSpec.EXACTLY){
        result = specSize;
    }else{
        result = 200;
        if(specMode == MeasureSpec.AT_MOST){
            result = Math.min(result, specSize);
        }
    }
    return result;
} 

a) 布局文件中指定精确的宽高值是400px时,View会根据指定的宽高进行设定。

b) 当指定宽高属性为match_parent时,View会填充整个父布局。

c) 当指定宽高属性为wrap_content时,如果不重写onMeasure()方法则会填充整个父布局,重写的话则会根据内容自动包含。

(2)ViewGroup的测量

当ViewGroup大小为wrap_content时,需要对子View进行遍历,以便根据所有子View的大小,来确定自身的大小。

ViewGroup调用子View的measure()方法遍历测量后,获取到子View的测量结果,然后打包成MeasureSpec传递给子View。

// measureChild(View, int, int)为子组件添加Padding   
measureChild(child, parentWidthMeasureSpec, parentHeightMeasureSpec);
// measureChildren(int, int)根据指定的高和宽来测量所有子View中显示参数非GONE的组件。  
measureChildren(widthMeasureSpec, heightMeasureSpec);
// measureChildWithMargins(View, int, int, int, int)测量指定的子组件,为子组件添加Padding和Margin。
measureChildWithMargins(child, parentWidthMeasureSpec, widthUsed, parentHeightMeasureSpec, heightUsed);  

View的绘制

装载画布

Android中,创建画布有两种方式:

Canvas canvas = new Canvas();   或    Canvas canvas = new Canvas(bitmap);

当在创建画布传入bitmap对象时,bitmap和画布是紧紧相连的,这个过程我们称之为装载画布。

这个bitmap用来存储所有绘制在Canvas上的像素信息。且Canvas调用所有的Canvas.drawXXX方法都发生在该bitmap上。

装载画布时,当Canvas将绘制效果作用在bitmap时,刷新view就会改变bitmap,如果非装载画布模式下,改变的是bitmap对象,并让view重绘。

绘制解析

Android系统中要自定义view,首先需要了解Android的view加载机制。主要有三个方法:

1、onMeasure() //计算出view自身大小
2、onLayout() //仅在ViewGroup中,用来为子view指定位置(left,top)
3、onDraw() //view绘制内容

下面根据源码中的相关说明,进一步分析控件的绘制操作及顺序:

/*
  * Draw traversal performs several drawing steps which must be executed
  * in the appropriate order:
  *  1. Draw the background                                             绘制控件设置的背景
  *  2. If necessary, save the canvas' layers to prepare for fading     保存画布的图层和阴影信息
  *  3. Draw view's content                                             重写onDraw(canvas)进行绘制
  *  4. Draw children                                                   绘制子控件,对应方法dispatchDraw(canvas)
  *  5. If necessary, draw the fading edges and restore layers          绘制控件阴影渐变效果
  *  6. Draw decorations (scrollbars for instance)                      绘制滚动条,对应方法onDrawScrollBars(canvas)
  */  

在第四步时,如果当前需要绘制的控件是ViewGroup,则需要通过dispatchDraw()方法绘制子控件,如果是View则不需要。

通常情况下ViewGroup不需要进行绘制,因为其本身没有需要绘制的东西,如果不是指定背景色,那么ViewGroup的onDraw方法不会被调用。

但是,ViewGroup会通过dispatchDraw()方法来绘制其子View。

下面我们看看onDraw()和dispatchDraw()的区别:

  • 绘制View本身内容时,可以调用View.onDraw(Canvas canvas)方法。
  • 绘制View的子View的内容时,可以调用diapatchDraw方法。
上一篇:【STM32F429的DSP教程】第33章 STM32F429不限制点数FFT实现


下一篇:信息安全管理33_防病毒管理策略