AlertDialog源码分析

在日常开发过程中,Dialog的使用频率应该是仅次于Activity,不管是出于UI交互原因还是业务需求,页面内经常会需要各种弹窗。这篇文章中并不是讲Dialog的使用也不是如何自定义各式各样弹窗。而是从源码的角度分析Dialog是如何展示在我们的屏幕中,在Android系统中Dialog如何创建视图、如何填充数据,如何添加Window中去。通过这篇文章我算是记录自己对Android视图结构的笔记。

那就直接开始,从具体的使用实例开始入手:

    public void showDialog() {
        AlertDialog.Builder builder = new AlertDialog.Builder(this);//步骤一
        //步骤二
        builder.setTitle("Title").
                setMessage("message").
                setIcon(R.drawable.ic_launcher_background).
                setPositiveButton("Button1", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                    }
                }).
                setNeutralButton("Button2", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                    }
                }).
                setNegativeButton("Button3", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                    }
                }).
                create(). //步骤三
                show(); //步骤四

    }

通过对上诉的代码的观察,AlertDialog明显是个Builder构建模式,AlertDialog.Builder同时扮演builder、ConcreteBuild、Director的角色,把AlertDialog对象的构建过程和创建过程分开。具体是怎么做的,我把上述分成了四给步骤:

步骤一 :通过AlertDialog.Builder构建了AlertController.AlertParams(以下简称为P)对象,从命名上可以看出AlertParams——弹窗的各种参数。

        public Builder(Context context) {
            this(context, resolveDialogTheme(context, ResourceId.ID_NULL));
        }
        public Builder(Context context, int themeResId) {
            P = new AlertController.AlertParams(new ContextThemeWrapper(
                    context, resolveDialogTheme(context, themeResId)));
        }

步骤二:Builder的一些列setXXX方法,给第一步中构建的P设置参数。

        public Builder setTitle(CharSequence title) {
            P.mTitle = title;
            return this;
        }

步骤三:Builder的create方法,到这里才真正创建AlertDialog对象,并且在其构造函数中创建了AlertController实例,然后再调用P的apply方法,把变量P中保存的各类参数设置到AlertDialog的AlertController对象中,在Dialog的构造方法中还获取了一个重要的对象WindowManager,在后面我们需要拿WindowManager添加视图到Window 上。

        public AlertDialog create() {
            // Context has already been wrapped with the appropriate theme.
            final AlertDialog dialog = new AlertDialog(P.mContext, 0, false);
            P.apply(dialog.mAlert);
           //代码...
            return dialog;
        }

      AlertDialog(Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
      //在父类Dialog构造函数获得一个重要的对象WindowManager
        super(context, createContextThemeWrapper ? resolveDialogTheme(context, themeResId) : 0,
                createContextThemeWrapper);

        mWindow.alwaysReadCloseOnTouchAttr();
        mAlert = AlertController.create(getContext(), this, getWindow());
      }

       public void apply(AlertController dialog) {
            if (mCustomTitleView != null) {
                dialog.setCustomTitle(mCustomTitleView);
            } else {
               //代码...
            }
            if (mMessage != null) {
                dialog.setMessage(mMessage);
            }
            //代码...
        }

第四步:调用AlertDialog的show方法显示弹窗,也是我们分析的重点,主要做了如下几个事情;

  1. 通过dispatchOnCreate调用AlertDialog的onCreate方法:
  2. 然后在调用AlertDialog的onStart
  3. 最后将Dialog的DecorView添加到WindowManager
 public void show() {
        if (mShowing) {
            if (mDecor != null) {
                if (mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) {
                   mWindow.invalidatePanelMenu(Window.FEATURE_ACTION_BAR);
                }
                mDecor.setVisibility(View.VISIBLE);
            }
            return;
        }
        mCanceled = false;
        if (!mCreated) {
            //调用生命周期onCreate
            dispatchOnCreate(null);
        } else {
            // Fill the DecorView in on any configuration changes that
            // may have occured while it was removed from the WindowManager.
            final Configuration config = mContext.getResources().getConfiguration();
            mWindow.getDecorView().dispatchConfigurationChanged(config);
        }
        //调用onStart
        onStart();
        mDecor = mWindow.getDecorView();
        //代码...

        WindowManager.LayoutParams l = mWindow.getAttributes();
        //代码...
        //windowManager.addView 添加视图
        mWindowManager.addView(mDecor, l);
        if (restoreSoftInputMode) {
            l.softInputMode &=
                    ~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
        }
        mShowing = true;
        sendShowMessage();
    }

在show方法中执行了Dialog的一系列生命周期方法,按照以往Activity的经验,而AlertDialog的内容视图构建也应该在onCreate方法中,进入AlertDialog 的onCreate方法

  @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mAlert.installContent();
    }
    public void installContent() {
        int contentView = selectContentView();
        mWindow.setContentView(contentView);
        setupView();
    }

    private int selectContentView() {
        if (mButtonPanelSideLayout == 0) {
            return mAlertDialogLayout;
        }
        if (mButtonPanelLayoutHint == AlertDialog.LAYOUT_HINT_SIDE) {
            return mButtonPanelSideLayout;
        }
        return mAlertDialogLayout;
    }

其中主要调用AlertController的installContent方法,在installContent中调用我们熟悉的setContentView方法,这里和Activity的设置视图一模一样,最终都是调用window的setContentView。而这里的contentView,也是就是mAlertDialogLayout弹窗视图布局,其值是在AlerController构造函数中进行的初始化。

protected AlertController(Context context, DialogInterface di, Window window) {
       mContext = context;
       mDialogInterface = di;
       mWindow = window;
       mHandler = new ButtonHandler(di);
       //代码...
       //alert_dialog.xml  
       final TypedArray a = context.obtainStyledAttributes(null,
               R.styleable.AlertDialog, R.attr.alertDialogStyle, 0);

       mAlertDialogLayout = a.getResourceId(
               R.styleable.AlertDialog_layout, R.layout.alert_dialog);
       //代码...
}

在setContentView中主要做的事情是创建一个DecorView,根据Theme,Feature添加了对应的布局文件,再把我们contentView添加到DecorView中。接下来再看setupView方法:

private void setupView() {
      // 获取初始化内容视图
        final View parentPanel = mWindow.findViewById(R.id.parentPanel);
    //获取title区域
        final View defaultTopPanel = parentPanel.findViewById(R.id.topPanel);
        final View defaultContentPanel = parentPanel.findViewById(R.id.contentPanel);
        final View defaultButtonPanel = parentPanel.findViewById(R.id.buttonPanel);

        // Install custom content before setting up the title or buttons so
        // that we can handle panel overrides.
        final ViewGroup customPanel = (ViewGroup) parentPanel.findViewById(R.id.customPanel);
        setupCustomContent(customPanel);

        final View customTopPanel = customPanel.findViewById(R.id.topPanel);
        final View customContentPanel = customPanel.findViewById(R.id.contentPanel);
        final View customButtonPanel = customPanel.findViewById(R.id.buttonPanel);

        // Resolve the correct panels and remove the defaults, if needed.
        final ViewGroup topPanel = resolvePanel(customTopPanel, defaultTopPanel);
        final ViewGroup contentPanel = resolvePanel(customContentPanel, defaultContentPanel);
        final ViewGroup buttonPanel = resolvePanel(customButtonPanel, defaultButtonPanel);
        /*初始化各视图*/
        setupContent(contentPanel);
        setupButtons(buttonPanel);
        setupTitle(topPanel);
        //代码...
        a.recycle();
    }

在setupView中就是初始化Alert Dialog布局中的各个部分,如标题栏、按钮栏、内容区域等等,在这个方法调用完后整个Dialog视图内容部分也就完成了。最后再把在通过WindowManager把DecorView添加到添加到Window上,并显示出来,正Dialog就出现在屏幕当中。

到这里已经把Dialog的主要流程分析完了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容