您的位置:首页 > 运维架构 > 网站架构

Android 控件架构与View的绘制

2015-12-25 22:38 369 查看
Android控件架构
Android中的每个控件都会在界面占得一块矩形的区域,控件大致被分为两类:ViewGroup控件与View控件。ViewGroup左为父控件可以包含并管理多个View,让整个界面上的控件形成了一个树形结构(控件树),上层控件负责下层子控件的测量与绘制,并传递交互事件。通常在Activity使用的findViewById()方法,就是在控件树中以树的深度优先遍历来查找对应元素。
树的顶部有一个ViewParent对象,也就是整颗树的控制核心,所有交互管理事件都由它来统一调度和分配,从而控制整个视图(如图1所示)
  



在Activity中使用setContentView()设置一个布局,如图2所示,每个Activity都包含一个Window对象,在安卓中Window通常由PhoneWindow来实现,它将一个DecorView设置为整个窗口的根View,而它作为顶层视图,封装了一些窗口操作的通用方法。可以说,DecorView将要显示的具体内容呈现在了PhoneWindow上,所有View的监听事件,都通过WindowManagerService进行接收,并通过Activity对象来回调相应的onClickListener。在显示上,它将屏幕分为两类:TitleView
和ContentView,


ContentView:它是一个ID为:content 的  FrameLayout
通过以上过程,我们可以建立起这样的一个标准视图树,如图3:



视图树的第二层装载了一个LinearLayout,作为ViewGroup,这一层的布局结构会根据对应的参数设置不同的布局,如最常用的:上面显示TitleBar,下面是Content这样的,而如果我们通过设置requestWindowFeature(Window,FEATURE_NO_TITLE)来设置全屏显示,那布局就只剩下Content了,这就解释了为什么此方法要在 setContentView()之前调用才生效的原因。 
图3所绘制的很粗略,当程序在onCreate方法中调用 setContentView 后,aActivityManagerService会回调onResume,此时系统才会把整个DecorView添加到PhoneWindow中,并让其显示,从而最终完成界面的绘制
View的测量与绘制
View测量
Android在绘制View之前,必须对View进行测量,告诉系统要画一个多大的View,整个过程是在 onMeasure() 方法中进行的
Android提供了一个类-----------MeasureSpec,通过它帮我们测量View,通常在ViewGroup中用到,它可以根据模式调节子View的大小。
MeasureSpec是一个32位的int值,高2位为测量的模式,低30位是测量的大小,使用位运算是为了提高效率
测量的模式可以分为一下三种:
1.EXACTLY  [ɪg'zæk(t)lɪ]
精确值模式,控件宽或高指定为:“xx dp”或是match_parent时使用
2.AT_MOST 
最大值模式,控件宽或高指定为:wrap_content时使用
3.UNSPECIFIED  ['spesifaid]
这个属性比较奇怪,它不指定其大小测量模式,View想多大就多大,通常情况下在绘制自定义View时使用
View类默认的onMeasure方法只支持EXACTLY模式,如果想让自定义View支持wrap_content属性,就必须重写该方法来指定wrap_content时的大小,(设置默认大小)
有了这些信息,我们就可以控制View最后显示的大小了。
int widthSpec = MeasureSpec.makeMeasureSpec(menuView.getLayoutParams().width, MeasureSpec.EXACTLY);
menuView.measure(widthSpec, heightMeasureSpec);
查看onMeasure的源码,发现最终会调用setMeasureDimension方法将测量后的宽高值设置进去。
在重写onMeasure中调用了自定义的方法,分别对宽和高进行重新定义,模板代码如下:
setMeasuredDimension(
measuredWidth(widthMeasureSpec), measuredHeight(heightMeasureSpec));

private int measuredWidth(int widthMeasureSpec) {
int result = 0;
int specMode = MeasureSpec.getMode();
int specSize = MeasureSpec.getSize();
if (specMode == MeasureSpec.EXACTLY) {
//如果为精确值模式,使用指定的size即可
result = specSize;
} else {
result = 200;
if (specMode = MeasureSpec.AT_MOST) {
//取出指定的大小与specSize中最小的一个来作为最后的测量值。
result = Math.min(result, specSize);
}
}
return result;
}
View绘制
当测量好一个View之后,我们就可以简单的重写onDraw方法,并在Canvas上绘制所需要的图形。
Canvas:要想在界面中绘制相应的图像,就必须在Canvas上绘制,它就像一个画板,使用paint就可以作画了,Canvas canvas=new Canvas( bitmap)之所以传入一个Bitmap,是因为需要用来存储所有绘制在Canvas上的像素信息的。这个过程称为装载画布。所以当通过这种方式创建的Canvas对象后,调用的所有Canvas.drawXXX方法都发生在这个bitmap上了。
ViewGroup的测量与绘制
在前面的分析中说了,ViewGroup会去管理其子View,其中一个管理项目就是负责子View的显示大小,当ViewGroup的大小为wrap_content时,ViewGroup就需要对子View进行遍历,以便获得所有子View的大小,从而决定自己的大小,通过遍历子View,并调用measure 方法获得每一个的测量结果
当测量结束后,就需要将子View放置到合适的位置,这个过程就是View的Layout过程,同样使用遍历来调用子View的Layout方法,指定显示位置,从而决定其布局位置
在自定义ViewGroup时,通常会去重写onLayout方法来控制子View显示的逻辑,同样,如果支持wrap_content属性,那么必须重写onMeasure方法,这点与View是相同的
ViewGroup的绘制
通常情况下不需要绘制,因为本身没有需要绘制的东西,如果不是指定了背景颜色,那连onDraw方法都不会被调用。但是,ViewGroup会使用dispatchDraw()方法来绘制其子View。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: