声明:本文为《Android群英传》读书笔记
Android自定义View
Android 控件架构
Android中,控件被分为两类:
- ViewGroup
- View
他们之间的关系是ViewGroup可以包含并管理多个View,下图是View树结构:
再来看看Android界面的架构图:
通过上图,可以建立起这样一个标准视图树:
当程序在onCreate()方法中调用setContentView()方法后,ActivityManagerService会回调onResume()方法,此时系统才会把整个DecorView添加到PhoneWindow中,并让其显示出来,从而最终完成界面的绘制。
View的测量与绘制
View的测量(onMeasure())
Android中通过MeasureSpec类来帮助我们测量View:
MeasureSpec:32位int值,高2位为测量模式,低30位为测量大小。
测量模式:
EXACTLY
精确模式(默认),控件大小指定为具体数值,比如
android:layout_width="100dp"
,或者match_parent
时,使用的就是此模式AT_MOST
最大值模式,指定为
warp_content
时,为此模式,只要不超过父控件允许的最大尺寸即可。UNSPECIFIED
不指定其大小测量模式,View想多大就多大,通常自定义View会用
重写onMeasure()最重要的工作是把测量的宽高值作为参数设置给setMeasureDimension()方法。
View的绘制(onDraw())
要想在Android的界面中绘制相应的图像,就必须在Canvas上进行绘制。
Canvas canvas = new Canvas(bitmpa);
创建一个Canvas对象时,一般传进一个bitmap对象,传进去的bitmap与通过这个bitmap创建的Canvas画布是紧紧联系在一起的,这个过程我们称之为装载画布。这个bitmap用来存储所有绘制在Canvas上的像素信息。虽然我们也使用了Canvas的绘制API,但其实并没有将图形直接绘制在onDraw()方法指定的那块画布上,而是通过改变bitmap,然后让View重绘,从而显示改变之后的bitmap。
ViewGroup的测量与绘制
ViewGroup的测量(onMeasure())
ViewGroup会去管理其子View,其中一个管理项目就是负责子View的显示大小。当ViewGroup的大小为warp_content时,ViewGroup就需要对子View进行遍历,以便获得所有子View的大小,从而来决定自己的大小。而在其他模式下则会通过具体的指定值来设置自身的大小。
ViewGourp在测量时通过遍历所有子View,从而调用子View的Measure方法来获得每一个子View的测量结果,前面所说的对View的测量,就是在这里进行的。
ViewGroup的布局(onLayout())
当子View测量完毕后,就需要将子View放到合适的位置,这个过程就是View的Layout过程。ViewGroup在执行Layout过程时,同样是使用遍历来调用子View的Layout方法,并指定其具体显示的位置,从而来决定其布局位置。
在自定义ViewGroup时,通常会去重写onLayout()方法来控制其子View显示位置的逻辑。同样,如果需要支持wrap_content属性,那么它还必须重写onMeasure()方法,这点与View是相同的。
ViewGroup的绘制(onDraw())
ViewGroup通常情况下不需要绘制,因为它本身就没有需要绘制的东西,如果不是指定了ViewGroup的背景颜色,那么ViewGroup的onDraw()方法都不会被调用。但是,ViewGroup会使用dispatchDraw()方法来绘制其子View,其过程同样是通过遍历所有子View,并调用子View的绘制方法来完成绘制工作。
自定义控件的三种方式
在自定义View时,我们通常会去重写onDraw()方法来绘制View的显示内容。如果该View还需要使用warp_content属性,那么还必须重写onMeasure()方法。另外,通过自定义attrs属性,还可以设置新的属性配置值。需要注意的是,当获取完所有的属性值后,需要调用TypedArray的recyle方法来完成资源的回收。
在View中通常有以下一些比较重要的回调方法。
- onFinishInflate():从XML加载组件后回调。
- onSizeChanged():组件大小改变时回调。
- onMeasure():回调该方法来进行测量。
- onLayout():回调该方法来确定显示的位置。
- onTouchEvent():监听到触摸事件时回调。
通常情况下,有以下三种方法来实现自定义的控件:
- 对现有控件进行拓展
- 通过组合来实现新的控件
- 重写View来实现全新的控件
使用自定义的View与系统原生的View最大区别就是在申明控件时,需要指定完整的包名,而在引用自定义的属性时,需要使用自定义的xmlns名字。
创建一个自定义View,难点在于绘制控件和实现交互,这也是评价一个自定义View优劣的标准之一。通常需要继承View类,并重写它的onDraw()、onMeasure()等方法来实现绘制逻辑,同时通过重写onTouchEvent()等触控事件来实现交互逻辑。当然,我们还可以像实现组合控件方式那样,通过引入自定义属性,丰富自定义View的可定制型。