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

目录

一、Activity加载显示基本流程

二、布局加载

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

二、Window(电子屏幕)

三、PhoneWindow(手机屏幕)

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

五、关系图

三、UI绘制

1、requestLayout

2、invalidate

3、ViewRootImpl

4、流程图

5、masure

5.1、View的测量

5.2、ViewGroup的测量

6、layout

6.1、View的layout

6.2、ViewGroup的layout

7、draw

第一步:绘制背景

第二步:

第三步:绘制内容

第四步:绘制子view

第五步:

第六步:绘制滚动条

8、postInvalidate

三、套路自定义控件

继承View

继承ViewGroup

四、问题

五、TODO


一、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

测量规则由三种类型

  1. EXACTLY: 精确的。
  2. AT_MOST: 最大
  3. 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

自定义控件实践

posted @ 2022-07-18 08:24  javakam  阅读(83)  评论(0编辑  收藏  举报  来源