阅读徐宜生《Android群英传》的笔记——第3章 Android控件架构与自定义控件详解(3.1-3.5)

3.1 Android控件架构

这里写图片描述

View 树结构
图3.1:View 树结构

UI界面架构图
图3.2:UI界面架构图

这里写图片描述

标准视图树
图3.3:标准视图树

这里写图片描述

而在代码中,当程序在 onCreate() 方法中调用 setContentView() 方法后,ActivityManagerService 会回调 onResume() 方法,此时系统才会把整个 DecorView 添加到 PhoneWindow 中,并让其显示出来,从而最终完成界面的绘制。

3.2 View的测量

Android 系统在绘制View前,也必须对View进行测量,即告诉系统该画一个多大的View。这个过程在 onMeasure() 方法中进行。
Android 系统给我们提供了一个设计短小精悍却功能强大的类—— MeasureSpec 类,通过它来帮助我们测量View。MeasureSpec 是一个32位的int值,其中高2位为测量的模式,低30位为测量的大小,在计算中使用位运算的原因是为了提高并优化效率。
测量模式可以分为以下三种:

  • EXACTLY:精确模式。当我们将控件的 layout_width 属性或 layout_height 属性指定为具体数值时,比如 android:layout_width=”10dp”,或者指定为 match_parent 属性时(占据父View的大小),系统使用的是EXACTLY模式。
  • AT_MOST:最大值模式。当控件的 layout_width 属性或 layout_height 属性指定为 wrap_content 时,控件大小一般随着控件的子控件或内容的变化而变化,此时控件的尺寸只要不超过父控件允许的最大尺寸即可。
  • UNSPECIFIED:不指定其大小测量模式。View想多大就多大,通常情况下在绘制自定义View时才会使用。

View默认的onMeasure()方法只支持EXACTLY模式,所以如果在自定义控件的时候不重写onMeasure()方法的话,就只能使用EXACTLY模式。控件可以响应你指定的具体宽高值或者是 match_parent 属性。而如果让自定义View支持 wrap_content 属性,就必须重写onMeasure()方法来指定 wrap_content 时的大小。
通过MeasureSpec 这一个类,我们就获取到了View的测量模式和View想要绘制的大小。有了这些信息,我们就可以控制View最后显示的大小。

下面我们来看一个简单的实例。
首先,要重写onMeasure()方法,该方法如下所示:

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

在IDE按住Ctrl键查看 super.onMeasure() 方法,可以发现,系统最终会调用 setMeasuredDimension(int widthMeasureSpec, int heightMeasureSpec) 方法将测量后的宽高值设置进去,从而完成测量工作。所以在重写 onMeasure() 方法后,最终要做的工作就是把测量后的宽高值作为参数设置给 setMeasuredDimension() 方法。
通过上面的分析,重写的 onMeasure() 方法如下所示:

    /**
     * View 的测量
     * @param widthMeasureSpec
     * @param heightMeasureSpec
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec));
    }

    /**
     * 自定义宽度
     *
     * @param widthMeasureSpec 宽的MeasureSpec对象
     * @return 测量后宽的值
     */
    private int measureWidth(int widthMeasureSpec) {
        int result = 0;
        //从MeasureSpec对象中提取出具体的测量模式和测量大小
        int specMode = MeasureSpec.getMode(widthMeasureSpec);
        int specSize = MeasureSpec.getSize(widthMeasureSpec);
        //通过判断测量的模式,给出不同的测量值
        if (specMode == MeasureSpec.EXACTLY) {
            //测量模式为精确模式时,直接使用指定的测量大小即可
            result = specSize;
        } else {
            //当测量模式为其他两种模式时,需要给它一个默认的大小 ,单位是px
            result = 200;
            if (specMode == MeasureSpec.AT_MOST) {
                //特别地,如果指定wrap_content属性,即最大值模式时,
                //需要取出我们指定的大小与测量大小中最小的一个来作为最后的测量值
                result = Math.min(result, specSize);
            }
        }
        return result;
    }

    /**
     * 自定义高度
     *
     * @param heightMeasureSpec 高的MeasureSpec对象
     * @return 测量后高的值
     */
    private int measureHeight(int heightMeasureSpec) {
        int result = 0;
        //从MeasureSpec对象中提取出具体的测量模式和测量大小
        int specMode = MeasureSpec.getMode(heightMeasureSpec);
        int specSize = MeasureSpec.getSize(heightMeasureSpec);
        //通过判断测量的模式,给出不同的测量值
        if (specMode == MeasureSpec.EXACTLY) {
            //测量模式为精确模式时,直接使用指定的测量大小即可
            result = specSize;
        } else {
            //当测量模式为其他两种模式时,需要给它一个默认的大小,单位是px
            result = 200;
            if (specMode == MeasureSpec.AT_MOST) {
                //特别地,如果指定wrap_content属性,即最大值模式时,
                //需要取出我们指定的大小与测量大小中最小的一个来作为最后的测量值
                result = Math.min(result, specSize);
            }
        }
        return result;
    }

3.3 View的绘制

当测量好一个View之后,我们就可以简单地重写 onDraw() 方法,并在 Canvas 对象上来绘制所需要的图形。首先我们来了解一下利用系统2D绘图API所必须要使用的Canvas对象。
要想在Android的界面中绘制相应的图像,就必须在Canvas上进行绘制,Canvas就像是一个画板,使用Paint(画笔)就可以在上面作画了,通常需要通过继承View并重写它的 onDraw() 方法来完成绘图。如果是在其他地方绘图,通常需要使用代码创建一个Canvas对象,代码为:

Canvas canvas = new Canvas(bitmap);

当创建一个 Canvas 对象时,为什么要传进去一个 bitmap 对象呢?如果不传入一个 bitmap 对象,IDE编译虽然不会报错,但是一般我们不会这样做,这是因为传进去的 bitmap 与通过这个 bitmap 创建的 Canvas 画布是紧紧联系在一起的,这个过程我们称之为装载画布。这个 bitmap 用来存储所有绘制在 Canvas 上的像素信息。所以当你通过这种方式创建了 Canvas 对象后,后面调用所有的 Canvas.drawXXX 方法都发生在这个 bitmap 上。

这里写图片描述

3.4 ViewGroup 的测量

ViewGroup 会去管理其子 View,其中一个管理项目就是负责子 View 的显示大小。当 ViewGroup 的大小为 wrap_content 时,ViewGroup 就需要对子 View 进行遍历,以便获得所有子 View 的大小,从而来决定自己的大小。而在其他模式下则会通过具体的指定值来设置自身的大小。
ViewGroup 在测量时通过遍历所有子 View,从而调用子 View 的 Measure 方法来获得每一个子 View 的测量结果,前面所说的对 View 的测量,就是在这里进行的。当子 View 测量完毕时,就需要将子 View 放到合适的位置,这个过程就是 View 的 Layout 过程。ViewGroup 在执行 Layout 过程时,同样是使用遍历来调用子 View 的 Layout 方法,并指定其具体显示的位置,从而来决定其布局位置。
在自定义 ViewGroup 时,通常会去重写 onLayout() 方法来控制其子 View 显示位置的逻辑。同样,如果需要支持 wrap_content 属性,那么它还必须重写 onMeasure() 方法,这点与 View 是相同的。

3.5 ViewGroup 的绘制

ViewGroup 通常情况下不需要绘制,因为它本身就没有绘制的东西,如果不是指定了 ViewGroup 的背景颜色,那么 ViewGroup 的 onDraw() 方法都不会被调用。但是,ViewGroup 会使用 dispatchDraw() 方法来绘制其子 View,其过程同样是通过遍历所有子 View,并调用子 View 的绘制方法来完成绘制工作。

猜你喜欢

转载自blog.csdn.net/u010102829/article/details/70787806