第三章终于进入开发的正题了!本章主要介绍自定义控件:
1.控件分为两类:View
和Viewgroup
,通过ViewGroup
整个界面形成一个树形结构,并且ViewGroup
负责对子View
的测量与绘制以及传递交互事件。
2.Activity
包含一个Window
对象,Window
对象又将一个DecorView
设置为整个应用的根View。这里所有View
的监听事件都通过WindowManagerService
来接收,并通过Activity
对象来回调onClickListener
。DecorView
在显示上分为TitleView
和ContentView
两部分。可以通过如下代码获得ContentView
:
ViewGroup content=(ViewGroup)findViewById(android.R.id.content);
3.View
的测量在onMeasure
中进行,系统提供了MeasureSpec
类,是一个32位的int值,其高2位为测量模式,低30位为测量的大小。测量模式有以下三种:
-
EXACTLY
:精确模式,当控件指定精确值(例如android:layout_width="50dp")或者指定为match_parent
属性时系统使用该模式。 -
AT_MOST
:最大值模式,指定wrap_content
时系统使用该属性,View
类默认只支持EXACTLY
,如果想使用wrap_content
需自己在onMeasure
中实现。 -
UNSPECIFIED
:自定义模式,View
想多大就多大,通常在绘制自定义View的时候才使用。
下面是onMeasure
的事例代码:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);// 获取宽度模式
int widthSize = MeasureSpec.getSize(widthMeasureSpec);// 获取宽度值
int width = 0;
if (widthMode == MeasureSpec.EXACTLY) {
width = widthSize;
} else {
width = 200;// 自定义的默认wrap_content值
if (widthMode == MeasureSpec.AT_MOST) {
width = Math.min(widthSize, width);
}
}
int heightMode = MeasureSpec.getMode(heightMeasureSpec);// 获取高度模式
int heightSize = MeasureSpec.getSize(heightMeasureSpec);// 获取高度值
int height = 0;
if (heightMode == MeasureSpec.EXACTLY) {
height = heightSize;
} else {
height = 200;// 自定义的默认wrap_content值
if (heightMode == MeasureSpec.AT_MOST) {
height = Math.min(heightSize, height);
}
}
setMeasuredDimension(width, height);// 最终将测量的值传入该方法完成测量
}
4.View
的绘制是通过onDraw
方法实现的,具体是通过对onDraw
方法中canvas
参数操作执行绘图。在其他地方,则需要自己创建canvas
对象,创建时需传入一个bitmap
对象, bitmap
是用来保存Canvas.drawXXX
绘制的像素信息的,通过这些绘图操作改变的实际上就是bitmap
对象而不是canvas
。
5.当ViewGroup
的大小为wrap_content
时,它就会遍历所有子View,并调用其Measure
方法获得其大小,来决定自身的大小,而在其他模式下则通过指定值来设置自身的大小。然后当View
测量完毕以后,ViewGroup
会执行它的Layout
方法,同样是遍历子View
并调用其Layout
方法来确定布局位置。在自定义ViewGroup
时,通常会重写onLayout()
方法来控制子View
显示位置,若需支持wrap_content
还需重写onMeasure()
方法。ViewGroup
通常情况下不需要绘制,但是会调用dispatchDraw()
方法来绘制其子View
,过程同样是遍历子View
。
6.自定义View时有一些比较重要的回调方法如下:
onFinishInflate();//从xml加载组件后回调
onSizeChanged();//组件大小改变时回调
onMeasure();//回调该方法进行测量
onLayout();//回调该方法来确定显示的位置
onTouchEvent();//监听到触摸事件回调
7.自定义View通常有三种情况:
(1)对现有控件进行拓展:
一般来说,会在onDraw()
方法中对原生控件行为进行拓展
@Override
protected void onDraw(Canvas canvas) {
//在回调父类方法前,实现自己的逻辑
super.onDraw(canvas);
//在回调父类方法后,实现自己的逻辑
}
(2)通过组合来实现新的控件:
这种方式通常需要继承一个合适的ViewGroup
,再给它添加指定功能的控件,从而组合成新的复合控件。
(3)重写View
来实现全新的控件:
当Android系统原生的控件无法满足我们的需求时,就需要创建一个全新的自定义View
了。通常需要继承View
类,并重写它的onDraw()
、onMeasure()
等方法实现绘制逻辑,同时通过重写onTouchEvent()
等触控事件来实现交互逻辑,还可以引入自定义属性,丰富自定义View
的可定制性。
8.本章较为浅显的分析了下事件传递的机制。当ViewGroup
接收到事件,通过调用dispatchTouchEvent()
,由这个方法再调用onInterceptTouchEvent()
方法来判断是否要拦截事件,如果返回true则拦截将事件交给onTouchEvent
处理,返回false则继续向下传递。当View
在接受到事件时,通过调用dispatchTouchEvent()
,由此方法再调用onTouchEvent
方法,如果返回true则拦截事件自己处理,如果返回false则将事件向上传递回ViewGroup
并且调用其onTouchEvent
方法继续做判断。
本章中用代码例举了很多自定义View,但由于本人对一些系统绘制的api尚不熟悉,等以后弄懂了再补充