Android自定义控件-UI绘制流程

一、布局加载

一、探秘setContentView(activity屏幕安装的位置)

Android自定义控件-UI绘制流程

 

其实就是调用了Window中的setContentView

getWindow()方法获取到的Window实现类是PhoneWindow

Android自定义控件-UI绘制流程

最终调用的还是PhoneWindow中的setContentView方法

二、Window(电子屏幕)

Window的类型

Android自定义控件-UI绘制流程

 

三、PhoneWindow(手机屏幕)

Android自定义控件-UI绘制流程

mContentParent为空的时候会调用如下方法

Android自定义控件-UI绘制流程

installDecor的作用:就是创建一个DecorView,并将窗体布局添加到DecorView中,然后返回ID为content的帧布局

初始化一个DecorView

Android自定义控件-UI绘制流程

然后调用generateLayout方法

Android自定义控件-UI绘制流程

generateLayout方法的作用就是设置一些窗体的属性值,然后窗体布局添加到DecorView中,并返回窗体布局中ID为content的帧布局

四、DecorView(屏幕显示的内容)

DecorView是PhoneWindow中的内部类,继承帧布局

Android自定义控件-UI绘制流程

 

五、关系图

Android自定义控件-UI绘制流程

 

二、UI绘制

UI绘制的起始点

在PhoneWindow中调用setContentView的时候调用了mContentParent的addView方法,其实调用就是ViewGroup中的addView方法。

Android自定义控件-UI绘制流程

 

1、requestLayout

Android自定义控件-UI绘制流程

最终调用的ViewRootImpl中的requestLayout方法

Android自定义控件-UI绘制流程

最终调用了scheduleTraversals方法

2、invalidate

ViewGroup中的invalidate方法调用了View中的invalidate方法,该方法必须在UI线程中被调用

View中invalidate方法如下

 

Android自定义控件-UI绘制流程

invalidateInternal关键代码如下

Android自定义控件-UI绘制流程

上述关键代码中最终调用的是ViewGroup中的invalidateChild方法

ViewGroup中invalidateChild方法的关键代码如下

 

Android自定义控件-UI绘制流程

通过do-while循环找到根布局(ViewRootImpl),并调用其中的invalidateChildInParent

3、ViewRootImpl

Android自定义控件-UI绘制流程

 

Android自定义控件-UI绘制流程

 

Android自定义控件-UI绘制流程

Android自定义控件-UI绘制流程

Android自定义控件-UI绘制流程

performTraversals关键代码

Android自定义控件-UI绘制流程

Android自定义控件-UI绘制流程

Android自定义控件-UI绘制流程

Android自定义控件-UI绘制流程

performLayout关键代码

Android自定义控件-UI绘制流程

Android自定义控件-UI绘制流程

performDraw关键代码

Android自定义控件-UI绘制流程

4、流程图

Android自定义控件-UI绘制流程

5、masure

Android自定义控件-UI绘制流程

5.1、view的测量

 

Android自定义控件-UI绘制流程

方法中的两个参数是由父容器传递进来的,是测量规则。

测量规则由两部分组成,高2位是MODE,低30位是size

测量规则由三种类型

 

 

  1. EXACTLY: 精确的。
  2. AT_MOST: 最大
  3. UNSPECIFIED: 不确定

 

onMeasure方法代码如下

Android自定义控件-UI绘制流程

5.2、ViewGroup的测量

Android自定义控件-UI绘制流程

作用:获取子view的LayoutParams,通过LayoutParams获取到子view的尺寸,然后通过父容器的测量规则和子view的尺寸通过getChildMeasureSpec方法生成一个测量规则传递给子view进行测量

 

getChildMeasureSpec方法如下

Android自定义控件-UI绘制流程

 

6、layout

6.1、View的layout

 

Android自定义控件-UI绘制流程

 

 

在View中onLayout是空实现

Android自定义控件-UI绘制流程

留给子类自己需要的时候去实现

 

 

6.2、ViewGroup的layout

Android自定义控件-UI绘制流程

实际上ViewGroup中的layout方法调用的是父类View的layout方法

ViewGroup中的onLayout方法是一个抽象类,子类必须要去实现

Android自定义控件-UI绘制流程

RelativeLayout中的onLayout方法如下

Android自定义控件-UI绘制流程

最终还是调用子view的layout方法

7、draw

在ViewRootImpl类中的drawSoftware方法中创建了canvas并调用了view.draw(canvas)

View中的draw方法如下

Android自定义控件-UI绘制流程

第一步绘制背景

Android自定义控件-UI绘制流程

Android自定义控件-UI绘制流程

第二步

第三步绘制内容

Android自定义控件-UI绘制流程

Android自定义控件-UI绘制流程

因为每个View内容不同,所以onDraw留给子类实现

第四步:绘制子view

Android自定义控件-UI绘制流程

Android自定义控件-UI绘制流程

View类中dispatchDraw是空实现,如果有子view再去复写该方法,如ViewGroup

通过源码我们可以看到ViewGroup中通过for循环调用了drawchild方法

Android自定义控件-UI绘制流程

第五步:

第六步:绘制滚动条

Android自定义控件-UI绘制流程

Android自定义控件-UI绘制流程

8、postInvalidate

通过该方法注释可以知道这个方法是在子线程中被调用

Android自定义控件-UI绘制流程

Android自定义控件-UI绘制流程

最终调用了ViewRootImpl中的dispatchInvalidateDelayed方法

Android自定义控件-UI绘制流程

Android自定义控件-UI绘制流程

Android自定义控件-UI绘制流程

最终还是通过handle调用了在主线程中invalidate方法

 

三、套路自定义控件

继承View

重新onMeasure方法测量自己的宽高,然后调用setMeasuredDimension设置宽高

Android自定义控件-UI绘制流程

具体可以参考TextView源码中的onMeasure()方法的写法,其中就是按照上述顺序完成的

继承ViewGroup

在onMeasure方法中需要测量子控件大小,还需要测量自身大小,然后调用setMeasuredDimension设置自身的宽高,在测量子view是可用使用ViewGrou提供的测量子view的方法,也可以自定义测量规则,最后在onLayout方法中对子view进行布局

Android自定义控件-UI绘制流程

具体可以参考LinearLayout或RelativeLayout中的onMeasure()方法,其中子view的获取都是通过for循环完成了

四、问题

如何让一个ScrollView里面的ListView全部展开?

通常的方法是继承ListView,重写onMeasure方法:

Android自定义控件-UI绘制流程

为什么要这么做?

1.设置mode为什么是 MeasureSpec.AT_MOST?

2.value为什么是Integer.MAX_VALUE >> 2?

 

五、TODO

自定义控件:高仿华为应用市场的标签页

上一篇:UVA 1412 Fund Management (预处理+状压dp)


下一篇:自定义ViewGroup练习之流式布局