- 3.1 Android控件架构
- 3.2 View的测量
- 3.3 View的绘制
- 3.4 ViewGroup的测量
- 3.5 ViewGroup的绘制
- 3.6 自定义View
- 3.6.1 对现有的空间进行拓展
- 3.6.2 创建复合控件
- 3.6.3 重写View来实现全新的空间
- 3.7 自定义ViewGroup
- 3.8 事件拦截机制分析
控件大致非为两类:
- view控件:视图控件
- viewGroup控件:包含多个View控件,并管理其包含的View控件
- 两者之间的关系:上层控件负责下层子控件的测量与绘制,并传递交互事件
UI界面架构:
- Activity都包含一个Window对象,通常由PhoneWindow来实现
- PhoneWindow将一个DecorView设置为整个应用窗口的根View
- DecorView为整个Window界面的最顶层View
- DecorView只有一个子元素为LinearLayout,代表整个Window界面,包含通知栏,标题栏,内容显示栏三块区域
- LinearLayout里有两个FrameLayout子元素:
- 标题栏显示界面。只有一个TextView显示应用的名称
- 内容栏显示界面。就是setContentView()方法载入的布局界面
MeasureSpec类:32位的int值,高2位为测量模式,低30位为测量大小
MeasureSpec模式:
- EXACTLY:精确模式 ,当控件的layout_width属性或layout_height属性指定为具体值,控件大小也是该具体值
- AT_MOST:最大值模式,当控件layout_width属性或layout_height属性指定为warp_content时,控件的尺寸不要超过父控件允许的最大尺寸
- UNSPECIFIED:未指定模式,控件要多大就多大,通常情况下再绘制自定义View中才会使用
View类默认的onMeasure()方法只支持EXACTLY模式,View需要支持warp_content属性,那么就必须重写onMeasure()方法,来制定warp_content的大小
下面我们通过一个简单的实例,演示如何进行View的测量,首先,需要重写onMeasure()方法:
可以发现,onMeasure方法调用了父类的onMeasure方法,代码跟踪父类onMeasure方法
可以发现,系统最终会调用setMeasuredDimension(int measuredWidth,int measuredHeight)方法将测量后的宽高值设置进去,我们调用自定义的measureWidth()方法和measureHeight()方法,分别对宽高进行重新定义
下面以measureWidth()方法为例:
第一步:从MeasureSpec对象中提取出具体的测量模式和大小
第二步:通过不同的测量模式给出不同的测量值:
- EXACTLY:使用指定的specSize即可
- AT_MOST:取出我们指定的大小和SpecSize的最小值
- UNSPECIFIED:200px
下面这段代码基本上可以作为模板代码:
可以发现,当指定warp_content属性时,View就获得一个默认值200px
当测量好了一个View之后,我们通过重写View类中的onDraw()方法来绘图,要想绘制相应的图像,就必须在Canvas上进行绘制
Canvas就像是一个画板,我们传进去一个bitmap,通过这个bitmap创建的Canvas画布紧紧联系在一起,这个过程我们称之为装载画布,这个bitmap用来存储所有绘制在Canvas上的像素信息,所以当你在后面调用所有的Canvas.drawxxx方法都会发生在这个bitmap上
ViewGroup在测量时通过遍历所有子View,从而调用子View的Measure方法来获得每一个子View的结果
ViewGroup测量完毕后,通常会去重写onLayout()方法来控制其子View显示位置的逻辑
ViewGroup通常不需要绘制,如果不是指定ViewGroup的背景颜色,那么ViewGroup的onDraw()方法都不会被调用,但是,ViewGroup会使用dispatchDraw()方法来绘制子View
在View中通常有以下一些重要的回调方法:
- onFinishInflate():从XML加载组件后回调
- onSizeChanged():组件大小改变时回调
- onMeasure():回调该方法来进行测量
- onLayout():回调该方法来确定显示的位置
- onTouchEvent():监听到触摸事件时回调
通常情况下,有以下三种方法来实现自定义的控件:
- 对现有的控件进行拓展
- 通过组合来实现新的控件
- 重写View来实现全新的控件
3.6.1 对现有的控件进行拓展
- 自定义修改TextView……见经典代码回顾,案例一
- 闪动的文字效果……见经典代码回顾,案例二
3.6.2 创建复合控件
- 自定义ToolBar的实现……见经典代码回顾,案例三
3.6.3 重写View来实现全新的控件
- 弧线展示图……见经典代码回顾,案例四
- 音频条形图……见经典代码回顾,案例五
- 自定义ViewGroup,仿ScrollView……见经典代码回顾,案例六
事件拦截机制三个重要方法
- dispatchTouchEvent():分发事件
- onInterceptTouchEvent():拦截事件
- onTouchEvent():处理事件
举一个例子说明事件分发机制:
- ViewGroupA:处于视图最下层
- ViewGroupB:处于视图中间层
- View:处于视图最上层
正常的事件分发机制流程:
- ViewGroupA dispatchTouchEvent
- ViewGroupA onInterceptTouchEvent
- ViewGroupB dispatchTouchEvent
- ViewGroupB onInterceptTouchEvent
- View dispatchTouchEvent
- View onTouchEvent
- ViewGroupB onTouchEvent
- ViewGroupA onTouchEvent
若ViewGroupB的onInterceptTouchEvent()方法返回true的分发机制流程:
- ViewGroupA dispatchTouchEvent
- ViewGroupA onInterceptTouchEvent
- ViewGroupB dispatchTouchEvent
- ViewGroupB onInterceptTouchEvent
- ViewGroupB onTouchEvent
- ViewGroupA onTouchEvent
若View的onTouchEvent()方法返回true的分发机制流程:
- ViewGroupA dispatchTouchEvent
- ViewGroupA onInterceptTouchEvent
- ViewGroupB dispatchTouchEvent
- ViewGroupB onInterceptTouchEvent
- View dispatchTouchEvent
- View onTouchEvent
若ViewGroupB的onTouchEvent()方法返回true的分发机制流程:
- ViewGroupA dispatchTouchEvent
- ViewGroupA onInterceptTouchEvent
- ViewGroupB dispatchTouchEvent
- ViewGroupB onInterceptTouchEvent
- View dispatchTouchEvent
- View onTouchEvent
- ViewGroupB onTouchEvent
简单的说dispatchTouchEvent()和onInterceptTouchEvent()是从下往上一层一层分发下去的,而onTouchEvent()是从上往下一层一层分发下去的
在values文件夹中创建一个attrs.xml文件来自定义属性
开始创建我们的ToolBar
在布局文件中使用
当用户不指定具体的比例值时,可以调用以下代码来设置相应的比例值
自定义的ScrollView没有系统自带的性能好,毕竟很多因素都没考虑到,这里只是适用于练手使用
在布局中使用
经典回顾源码下载