注:下列源码版本为8.0
AlertDialog的使用
首先回顾下AlertDialog简单使用方法。设置图片,标题,内容,确认和取消按钮,最后调用show()显示出来。
1 | AlertDialog.Builder builder = new AlertDialog.Builder(this); |
运行效果如下:
AlertDialog源码解析
AlertDialog
先整体了解下AlertDialog类的结构。可以大致分为6部分。
- 父类Dialog
- 实现的接口DialogInterface
- AlertDialog的成员变量
- AlertDialog的构造方法
- AlertDialog的方法
- 内部类Builder
接下来我们一步一步深入了解各部分相关知识。首先是AlertDialog类里面的。
成员变量
mAlert
1 |
|
这个是AlertController类型的变量。具体AlertControll的介绍会在下面讲到,这里我们只需要记住AlertControll是AlertBuilder的核心控制类,对AlertDialog的设置都是通过调用mAlert的方法实现的。
主题变量
1 | public static final int THEME_TRADITIONAL = 1; //传统主题 |
这里分别是五个主题,传统、深色HOLO,浅色HOLO,默认深色Device和默认浅色Device主题。
提示布局
1 | public static final int LAYOUT_HINT_NONE = 0; //没有提示布局 |
构造方法
1 | protected AlertDialog(Context context) { |
通过代码中我们可以发现AlertDialog的构造函数都是用protected或者默认来修饰,这就意味着我们不能直接通过new操作来创建AlertDialog的实例。通过一开始的使用我们可以知道,正常我们是通过Builder类来构造一个AlertDialog的实例的。而Builder的分析下面会细讲。
构造方法主要是传入上下文Context,Cancel监听器,样式(这个传入由父类Dialog处理,正常情况下默认为0,即系统指定的样式)。然后三个protected修饰的构造方法都调用了默认的构造方法,这个构造方法为真正的实现。在这个方法中createContextThemeWrapper是传给父类Dialog的,这个涉及到Dialog中的ContextThemeWrapper(Context一个包装类),这里可以暂时不管。resolveDialogTheme为解析主题方法,为AlertDialog的静态方法。然后构造核心控制类的实例。
方法
resolveDialogTheme
这里方法的主要工作就是根据构造方法传入的主题常量,对应相应的主题资源id。
1 | //解析主题风格 |
get系列方法
主要有两个,一个是获取对话框的按钮,比如BUTTON_POSITIVE。一个是获取对话框中的ListView列表。
1 | //根据位置返回对话框的按钮 |
set系列方法
这个系列方法有很多,但是最后都是在里面调用mAlert的方法来实现。这里只贴出一部分。
1 | //设置标题 |
onCreate
这里主要是对mAlert进行初始化。
1 | //初始化AlertController |
key方法
按键事件,如果AlertController不处理则交给父类Dialog处理。
1 |
|
分析完AlertDialog的方法后,我们就来重点分析使用AlertDialog最常见也是AlertDialog中很重要的内部类Builer。
Builder
还是老规矩,先了解下Builder的整体结构,主要分为三个部分来进行分析:
- 成员变量
- 构造方法
- 方法
P
1 |
|
可以发现在内部类中成员变量只有一个,P是AlertController的内部类AlertParams类型的变量。AlertParams中包含了与AlertDialog视图中对应的成员变量,P的主要作用就是保存Builder的一些设置参数。
构造方法
1 | public Builder(Context context) { |
在这里构造方法有两个,我们经常使用的就是第一个,传入上下文Context(正常是活动的上下文),默认的主题id为ResourceId.ID_NULL,即0,故默认的主题风格是系统指定的风格。然后调用具体实现的构造方法。第二个构造方法为具体实现,可以指定风格,并实例化P。
方法
get方法
1 | public Context getContext() { |
在Builder类中get方法只有一个getContext, 为Dialog返回一个在Builder创建时有其对应主题的Context。程序会使用这个Context去获取LayoutInflater,然后去载入一个用于显示在正在生成的dialog的使用正确主题的视图。
set系列方法
1 | public Context getContext() { |
从这些方法可以看到在builder设置的参数都保存到了P的对应的成员变量中,并且这些方法都是返回Builder的,故我们可以使用Builder的构造链来进行连续传参。P存在之后,我们就可以创建对话框了。让我们继续分析应该如何创建对话框。
create
1 | public AlertDialog create() { |
创建AlertDialog的实例,并将保存在P的设置参数赋值给dialog对象的mAlert中。这时候知识创建了对话框,但是还不能将对话框显示出来,故接下去看show的方法。
show
1 | public AlertDialog show() { |
在show方法中调用create得到AlertDialog对象,然后调用对象的show方法,将对话框显示出来。
至此我们已经分析完AlertDialog类中的相关知识。欸,等等AlertControll核心控制类呢?Dialog类呢?看来革命尚未成功啊!让我们来继续分析!
AlertDialog的加载绘制流程
create
通过上面的分析我们知道在创建AlertDialog对象时,需要将参数P赋值到对象的mAlert中。
1 | final AlertDialog dialog = new AlertDialog(P.mContext, 0, false); |
在这里通过new来创建一个AlertDialog对象,来回顾下AlertDialog的最重要的构造方法。
1 | AlertDialog(Context context, int themeResId, boolean createContextThemeWrapper) { |
在这里我们可以很清楚的发现,这个构造方法调用了super,故我们需要看下Dialog的构造方法。
1 | Dialog(int themeResId, boolean createContextThemeWrapper) { Context context, |
从Dialog的构造函数可以发现,直接new出了一个phoneWindow并赋值给mWindow,故当调用create创建AlertDialog对象时Dialog所需要的Window就有了。那么接下来就得分析Window如何与Dialog的视图进行关联呢?
apply
在AlertDialog创建后,就调用了apply这个方法,这个方法是在AlertController类中的方法,我们看一下这个方法。
1 | public void apply(AlertController dialog) { |
从这个方法可以看出,这个方法的任务很简单,就是将保存的参数赋值给dialog的mAlert参数。创建后如何show出来呢,我们知道在show方法中调用了dialog的show,那么dialog的show又是怎样的呢?
show
1 | public void show() { |
通过阅读源码,我们可以发现AlertDialog调用的show方法其实是父类Dialog的show方法。在这个方法中首先要判断Dialog是否已经show过,如果已经show过就重新设置DecorView。如果窗口未被创建,就调用dispatchOnCreate(null)方法,这个方法在Dialog中,我们可以看看:
dispatchOnCreate
1 | void dispatchOnCreate(Bundle savedInstanceState) { |
可以看出实际上就是回调Dialog的onCreate,而在Dialog的onCreate为空方法,具体实现为子类AlertDialog的onCreate方法。
1 |
|
其实在上面对AlertDialog的分析中我们已经得知AlertDialog的onCreate方法主要是对mAlert进行初始化,那么是如何进行初始化的呢?让我们瞧瞧AlertController中这个installContent方法。
installContent
1 | public void installContent() { |
这个方法负责的是Dialog的界面显示工作。其中contentView为窗口的总体布局,具体方法如下:
1 | //得到布局id |
这个方法就是布局选择,默认情况下返回mAlertDialogLayout。而mAlertDialogLayout在AlertController中的构造函数中进行初始化。
1 |
|
有了布局后,在installContent中就执行 mWindow.setContentView(contentView),将这个布局添加到对应的Window中。添加后就开始进行布局组件的初始化,来看看setupView方法
setupView
1 | private void setupView() { |
代码主要逻辑是根据标题,内容,按钮等添加到对应的布局中。根据设置来对布局进行显示和隐藏。最后让我们回到最初的Dialog的show方法中,执行完dispatchOnCreate之后又调用了onStart方法。然后调用以下三个方法最终将Dialog的界面绘制出来。
1 | mDecor = mWindow.getDecorView(); |
总结
AlertDialog跟Activity一样,内部有个Window。在日常情况下,我们首先构造出AlertDialog.Builder对象,然后设置各种属性,这些属性都被保存在AlertController.AlertParams类型的P中,调用Builder的create方法,在create首先构造出AlertDialog对象,构造的同时new PoneWindow对象赋值给AlertDialog的Window。对象构造后调用P.apply方法,将P中的参数设置赋值给AlertDialog对象的AlertController类型的mAlert中。然后我们会调用Builder的show方法来显示出Dialog,在show中会调用dispatchOnCreate方法来回调AlertDialog的onCreate方法,最终会走到setContentView将Window和Dialog的视图关联在一起,最终执行mWindow.addView方法,通过WindowManager将DecroView添加到Window中,于是Dialog显示在我们眼前。