美文网首页Android自定义控件
第三章 Android控件架构(一)

第三章 Android控件架构(一)

作者: Bejamin | 来源:发表于2018-03-17 15:57 被阅读3次

    一、Android控件树

    Android控件大致分为两类,即ViewGroup控件和View控件。ViewGroup控件可以包含多个View控件,并且负责管理其中的View控件。如图3.1所示,整个界面上的ViewGroup和View构成了一棵控件树,上层控件负责下层子控件的测量与绘制,并传递交互事件。


    图3.1 View树结构

    在每棵控件树的顶部,都有一个ViewParent对象,这就是整棵树的控制核心,所有的交互管理事件都由它来统一调度和分配,从而可以对整个视图进行整体控制。

    Activity中使用的findViewById()方法,就是在控件树中以深度优先遍历查找对应的元素的。通常调用setContentView()方法设置一个布局,使得布局内容真正地显示出来。

    二、UI界面架构图

    图3.2展示了Android的UI界面架构图。


    图3.2 UI界面架构图

    如图3.2所示,每个Activity都包含了一个Window对象,通常由PhoneWindow来实现。
    DecorWindow是整个应用窗口的根,是窗口界面的顶层视图,封装了一些窗口的通用方法,将要显示的具体内容呈现在PhoneWindow上。这里面所有View的监听事件,都通过WindowManagerService来接收,并通过Activity对象来回调响应的onClickListener。
    DecorWindow将屏幕分成两部分:TitleWindow和ContentWindow。其中ContentWindow是一个ID为content的Framelayout,activity_main.xml就是设置在这样一个Framelayout里。

    通过以上过程,可以建立起一个标准视图树,如图3.3所示。


    图3.3 标准视图树

    图3.3中的视图树的第二层装载了一个LinearLayout,作为ViewGroup,这一层的布局会根据对应的参数设置为不同的布局。
    图3.3所示的是一个最常用的布局——上面TitleBar,下面Content。如果想让界面全屏,则需要通过设置requestWindowFeature(Window.FEATURE_NO_TITLE)来设置布局。在第二层设置布局,所以调用requestWindowFeature()方法一定要在setContentView()方法之前才能生效。

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

    三、View的测量

    这个过程在onMeasure()方法中进行。测量模式可以分为以下三种。

    • EXACTLY

    精确模式。layout_width、layout_height指定为具体数值事或者为match_parent属性时,系统使用该模式。

    • AT_MOST

    最大值模式。layout_width、layout_height指定为wrap_content时。

    • UNSPECIFIED

    不指定大小测量模式。View想多大就多大,通常在绘制自定义View时才会使用。

    View类默认的onMeasure()方法只支持EXACTLY模式,所以如果在自定义组件的时候,要让自定义View支持wrap_content属性,那么就必须重新onMeasure()方法来指定wrap_content时的大小。

    通过MeasureSpec这一个类,就可以获取View的测量模式和View想要绘制的大小。有了这些信息,我们就可以控制View最后显示的大小。

    下面通过一个简单的例子,颜色如何进行View的测量。首先,要重写onMeasure()方法,该方法如下所示。

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

    查看super.onMeasure()方法,可以发现,系统最终会调用serMeasuredDimension(int measuredWidth, int measuredHeight)方法将测量后的宽高值设置进去。因此,示例中重写onMeasure()方法代码如下。

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(
                measureWidth(widthMeasureSpec),
                measureHeight(heightMeasureSpec));
    }
    

    其中,measureWidth()、measureHeight()方法是用来自定义测量值的自定义函数。以measureWidth()方法为例。

    private int measureWidth(int measureSpec) {
        int result = 0;
        int specMode = MeasureSpec.getMode(measureSpec);  // 从MeasureSpec对象中提取具体的测量模式
        int specSize = MeasureSpec.getSize(measureSpec);  // 从MeasureSpec对象中提取具体的大小
    
        if(specMode == MeasureSpec.EXACTLY) {
            result = specSize;
        } else {
            result = 200;  // UNSPECIFIED模式
            if (specMode == MeasureSpec.AT_MOST) {
                result = Math.min(result, specSize);
            }
        }
        return result;
    }
    

    通过这两个方法,可以实现对宽高值的自定义。

    默认情况下,当指定宽高属性为wrap_content时,如果不重写onMeasure()方法,系统会默认填充整个父布局。在上述示例中,当指定wrap_content属性时,View获得了一个默认值200px,所以系统不再默认填充父布局,而是默认使用200px整个大小值。

    四、View的绘制

    当测量好一个View之后,就可以简单地重写onDraw()方法,并在Canvas对象上绘制所需要的图形。

    Canvas对象就像是一个画板,使用Paint可以在上面作画。通常通过继承View并重写它的onDraw()方法来完成绘图。一般情况下,可以使用重写View类中的onDraw()方法来绘图,onDraw()方法有一个参数是Canvas对象,可以使用该参数进行绘图。而在其他地方,则需要创建一个Canvas对象,如下所示

    Canvas  canvas = new Canvas(bitmap);
    

    传入参数bitmap,称为装载画布。这个bitmap用来存储所有绘制在canvas撒花姑娘的像素信息,即后面所有的canvas.drawXXX方法都发生在这个bitmap上。

    虽然使用了Canvas的绘制API——canvas.drawXXX方法,但其实并没有将图形直接绘制在指定的bitmap上,而是通过改变bitmap,然后让View重绘,从而显示改变后的bitmap。

    理解了Canvas对象之后,不管是多么复杂的控件,都可以分解为简单的图形单元,然后调用绘制API进行绘制。

    五、ViewGroup的测量与绘制

    ViewGroup其中一个功能就是负责子View的显示大小。当ViewGroup的大小为wrap_content时,ViewGroup需要遍历所有子View的大小,从而决定自己的大小。其他模式下则会通过其具体的指定值来设置自身大小。

    前面所说的子View的测量就是在ViewGroup遍历时进行的。测量完子View之后,就需要将子View放到合适的位置,这个过程就是View的Layout过程。ViewGroup在执行Layout过程时,同样使用遍历来调用子View的Layout方法,并指定其具体显示的位置。

    在自定义ViewGroup时,通常会去重写onLayout()方法来控制其子View显示位置的逻辑。如果需要支持wrap_content属性,那么它还需要重写onMeasure()方法,这点和View是相同的。

    ViewGroup通常情况下不需要绘制,但是它会使用dispatchDraw()方法来绘制其子View。

    相关文章

      网友评论

        本文标题:第三章 Android控件架构(一)

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