1. Android控件架构
ViewGroup控件与View控件组成控件树,findViewById()
就是在控件树中DFS查找元素。
通过设置requestWindowFeature(Window.FEATURE_NO_TITLE)
来设置全屏显示,视图树中的布局就只有Content了,这就是调用requestWindowFeature()
方法一定要在调用setContentView()
方法之前才能生效的原因。
当程序在onCreate()
方法中调用setContentView()
方法之后,ActivityManagerService会回调onResume()
方法,此时系统才会把整个DecorView添加到PhoneWindow中,并让其显示出来,从而最终完成界面的绘制。
2. View的测量
onMeasure()
:绘制View前,对View进行测量。MeasureSpec
类(实质是32位int值):测量模式 + 测量大小
测量模式:EXACTLY
(具体数值或match_parent), AT_MOST
(wrap_content), UNSPECIFIED
对自定义View重写onMeasure方法,根据测量模式指定宽和高,便可在布局文件对自定义View使用android:layout_width和android:layout_height属性
3. View的绘制
onDraw(Canvas)
:测量好View后,通过重写onDraw()
方法在Canvas上绘制所需图形
装载画布:Canvas canvas = new Canvas(bitmap);
这个bitmap用来存储所有绘制在Canvas上的像素信息,调用所有的canvas.drawXXX方法都发生在这个bitmap上,即使将bitmap装载在别的画布而非onDraw()
指定的那块画布,改变了bitmap,View重绘后也会显示改变后的bitmap。
4. ViewGroup的测量
当ViewGroup的大小为wrap_content时,ViewGroup遍历子View的Measure方法来决定自己的大小。
子View测量完毕后,遍历调用子View的Layout,并指定其具体位置。
自定义ViewGroup时,通常重写ViewGroup的onLayout()
方法来控制子View位置。如果需要支持wrap_content属性,必须重写onMeasure()
方法。
5. ViewGroup的绘制
通常不需要绘制,除非指定背景色等会调用onDraw()
方法。
使用dispatchDraw()
来遍历绘制子View,并调用子View的绘制方法。
6. 自定义View
适当使用自定义View,因为Android控件已经经过打磨,用户熟悉,bug少,适配效果好
6.1. 对现有控件进行拓展
继承系统原生控件,重写onDraw()
方法,在调用super.onDraw()
方法前后实现自己的逻辑
6.2. 创建复合组件
通常继承一个合适的ViewGroup,再给它添加指定功能的控件
- 定义属性:在
res/values/attrs.xml
定义属性,通过TypedArray获取自定义属性集,获取完所有属性值后,需要调用TypedArray的recycle
方法完成资源的回收TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.TopBar);
- 组合控件:动态添加控件,即
addView()
,并设置属性值
给按钮设计点击事件:定义接口→暴露接口给调用者→实现接口回调 - 引用UI模板:需要指定引用第三方控件的名字空间
使用系统属性:xmlns:android="http://schemas.android.com/apk/android"
使用自定义属性:xmlns:custom="http://schemas.android.com/apk/res-auto"
,custom:leftText="Back"
,申明控件需要完整的包名
6.3. 重写View来实现全新的控件
通常继承View类,并重写onDraw()
、onMeasure()
等方法
- 弧线展示图:在
onDraw()
方法中一个个去绘制图形 - 音频条形图:动态效果:在
onDraw()
方法中调用invalidate()
通知View重绘,或通过postInvalidateDelayed(300)
进行View的延迟重绘
7. 自定义ViewGroup
通常需要重写onMeasure()
方法对子View进行测量,重写onLayout()
方法确定子View的位置,重写onTouchEvent()
方法增加响应事件
例子:实现ScrollView的上下滑动功能,在滑动过程中,增加一个黏性的效果,即当一个子View向上滑动大于一定的距离后,松开手指,它将自动向上滑动,显示下一个子View。同理,如果滑动距离小于一定的距离,松开手指,它将自动滑动到开始的位置。
为实现黏性效果,在onTouchEvent()
方法中利用ACTION_DOWN
事件、ACTION_MOVE
事件和ACTION_UP
事件。
8. 事件拦截机制分析
实例层级:MyViewGroupA → MyViewGroupB → MyView
事件传递顺序:MyViewGroupA → MyViewGroupB → View
事件处理顺序:View → MyViewGroupB → MyViewGroupA
事件传递(onInterceptTouchEvent
)返回值:True,拦截,自己处理;False,不拦截,继续流程
事件处理(onTouchEvent
)返回值:True,处理了,不用给上级;False,给上级处理
正常情况:
ViewGroupA dispatchTouchEvent
ViewGroupA onInterceptTouchEvent
ViewGroupB dispatchTouchEvent
ViewGroupB onInterceptTouchEvent
View dispatchTouchEvent
View onTouchEvent
ViewGroupB onTouchEvent
ViewGroupA onTouchEvent
事件拦截:
事件处理: