Android自定义控件-UI绘制流程
目录
一、探秘setContentView(activity屏幕安装的位置)
一、Activity加载显示基本流程
二、布局加载
一、探秘setContentView(activity屏幕安装的位置)
其实就是调用了Window中的setContentView
getWindow()方法获取到的Window实现类是PhoneWindow
最终调用的还是PhoneWindow中的setContentView方法
二、Window(电子屏幕)
Window的类型
三、PhoneWindow(手机屏幕)
mContentParent为空的时候会调用如下方法
installDecor的作用:就是创建一个DecorView,并将窗体布局添加到DecorView中,然后返回ID为content的帧布局
初始化一个DecorView
然后调用generateLayout方法
generateLayout方法的作用就是设置一些窗体的属性值,然后窗体布局添加到DecorView中,并返回窗体布局中ID为content的帧布局
四、DecorView(屏幕显示的内容)
DecorView是PhoneWindow中的内部类,继承帧布局
五、关系图
三、UI绘制
UI绘制的起始点
在PhoneWindow中调用setContentView的时候调用了mContentParent的addView方法,其实调用就是ViewGroup中的addView方法。
1、requestLayout
最终调用的ViewRootImpl中的requestLayout方法
最终调用了scheduleTraversals方法
2、invalidate
ViewGroup中的invalidate方法调用了View中的invalidate方法,该方法必须在UI线程中被调用
View中invalidate方法如下
invalidateInternal关键代码如下
上述关键代码中最终调用的是ViewGroup中的invalidateChild方法
ViewGroup中invalidateChild方法的关键代码如下
通过do-while循环找到根布局(ViewRootImpl),并调用其中的invalidateChildInParent
3、ViewRootImpl
performTraversals关键代码
performLayout关键代码
performDraw关键代码
4、流程图
5、masure
5.1、View的测量
方法中的两个参数是由父容器传递进来的,是测量规则。
测量规则由两部分组成,高2位是MODE,低30位是size
测量规则由三种类型
- EXACTLY: 精确的。
- AT_MOST: 最大
- UNSPECIFIED: 不确定
onMeasure方法代码如下
5.2、ViewGroup的测量
作用:获取子view的LayoutParams,通过LayoutParams获取到子view的尺寸,然后通过父容器的测量规则和子view的尺寸通过getChildMeasureSpec方法生成一个测量规则传递给子view进行测量
getChildMeasureSpec方法如下
6、layout
6.1、View的layout
在View中onLayout是空实现
留给子类自己需要的时候去实现
6.2、ViewGroup的layout
实际上ViewGroup中的layout方法调用的是父类View的layout方法
ViewGroup中的onLayout方法是一个抽象类,子类必须要去实现
RelativeLayout中的onLayout方法如下
最终还是调用子view的layout方法
7、draw
在ViewRootImpl类中的drawSoftware方法中创建了canvas并调用了view.draw(canvas)
View中的draw方法如下
第一步:绘制背景
第二步:
第三步:绘制内容
因为每个View内容不同,所以onDraw留给子类实现
第四步:绘制子view
View类中dispatchDraw是空实现,如果有子view再去复写该方法,如ViewGroup
通过源码我们可以看到ViewGroup中通过for循环调用了drawchild方法
第五步:
第六步:绘制滚动条
8、postInvalidate
通过该方法注释可以知道这个方法是在子线程中被调用
最终调用了ViewRootImpl中的dispatchInvalidateDelayed方法
最终还是通过handle调用了在主线程中invalidate方法
三、套路自定义控件
继承View
重新onMeasure方法测量自己的宽高,然后调用setMeasuredDimension设置宽高
具体可以参考TextView源码中的onMeasure()方法的写法,其中就是按照上述顺序完成的
继承ViewGroup
在onMeasure方法中需要测量子控件大小,还需要测量自身大小,然后调用setMeasuredDimension设置自身的宽高,在测量子view是可用使用ViewGrou提供的测量子view的方法,也可以自定义测量规则,最后在onLayout方法中对子view进行布局
具体可以参考LinearLayout或RelativeLayout中的onMeasure()方法,其中子view的获取都是通过for循环完成了
四、问题
如何让一个ScrollView里面的ListView全部展开?
通常的方法是继承ListView,重写onMeasure方法:
为什么要这么做?
1.设置mode为什么是 MeasureSpec.AT_MOST?
2.value为什么是Integer.MAX_VALUE >> 2?
五、TODO
自定义控件实践