第三章 Android控件架构(一)

一、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。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,319评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,801评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,567评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,156评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,019评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,090评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,500评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,192评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,474评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,566评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,338评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,212评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,572评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,890评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,169评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,478评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,661评论 2 335

推荐阅读更多精彩内容