AlertDialog源碼分析

在日常開發(fā)過程中何之,Dialog的使用頻率應(yīng)該是僅次于Activity嚷辅,不管是出于UI交互原因還是業(yè)務(wù)需求煤墙,頁面內(nèi)經(jīng)常會需要各種彈窗。這篇文章中并不是講Dialog的使用也不是如何自定義各式各樣彈窗馍刮。而是從源碼的角度分析Dialog是如何展示在我們的屏幕中信夫,在Android系統(tǒng)中Dialog如何創(chuàng)建視圖、如何填充數(shù)據(jù)卡啰,如何添加Window中去静稻。通過這篇文章我算是記錄自己對Android視圖結(jié)構(gòu)的筆記。

那就直接開始碎乃,從具體的使用實例開始入手:

    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構(gòu)建模式,AlertDialog.Builder同時扮演builder梅誓、ConcreteBuild恰梢、Director的角色佛南,把AlertDialog對象的構(gòu)建過程和創(chuàng)建過程分開。具體是怎么做的嵌言,我把上述分成了四給步驟:

步驟一 :通過AlertDialog.Builder構(gòu)建了AlertController.AlertParams(以下簡稱為P)對象,從命名上可以看出AlertParams——彈窗的各種參數(shù)嗅回。

        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方法,給第一步中構(gòu)建的P設(shè)置參數(shù)摧茴。

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

步驟三:Builder的create方法绵载,到這里才真正創(chuàng)建AlertDialog對象,并且在其構(gòu)造函數(shù)中創(chuàng)建了AlertController實例,然后再調(diào)用P的apply方法苛白,把變量P中保存的各類參數(shù)設(shè)置到AlertDialog的AlertController對象中,在Dialog的構(gòu)造方法中還獲取了一個重要的對象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構(gòu)造函數(shù)獲得一個重要的對象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);
            }
            //代碼...
        }

第四步:調(diào)用AlertDialog的show方法顯示彈窗购裙,也是我們分析的重點懂版,主要做了如下幾個事情;

  1. 通過dispatchOnCreate調(diào)用AlertDialog的onCreate方法:
  2. 然后在調(diào)用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) {
            //調(diào)用生命周期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);
        }
        //調(diào)用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方法中執(zhí)行了Dialog的一系列生命周期方法躏率,按照以往Activity的經(jīng)驗躯畴,而AlertDialog的內(nèi)容視圖構(gòu)建也應(yīng)該在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;
    }

其中主要調(diào)用AlertController的installContent方法薇芝,在installContent中調(diào)用我們熟悉的setContentView方法蓬抄,這里和Activity的設(shè)置視圖一模一樣,最終都是調(diào)用window的setContentView。而這里的contentView夯到,也是就是mAlertDialogLayout彈窗視圖布局嚷缭,其值是在AlerController構(gòu)造函數(shù)中進行的初始化。

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中主要做的事情是創(chuàng)建一個DecorView耍贾,根據(jù)Theme峭状,F(xiàn)eature添加了對應(yīng)的布局文件,再把我們contentView添加到DecorView中逼争。接下來再看setupView方法:

private void setupView() {
      // 獲取初始化內(nèi)容視圖
        final View parentPanel = mWindow.findViewById(R.id.parentPanel);
    //獲取title區(qū)域
        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布局中的各個部分,如標題欄劝赔、按鈕欄誓焦、內(nèi)容區(qū)域等等,在這個方法調(diào)用完后整個Dialog視圖內(nèi)容部分也就完成了着帽。最后再把在通過WindowManager把DecorView添加到添加到Window上杂伟,并顯示出來,正Dialog就出現(xiàn)在屏幕當中仍翰。

到這里已經(jīng)把Dialog的主要流程分析完了赫粥。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市予借,隨后出現(xiàn)的幾起案子越平,更是在濱河造成了極大的恐慌频蛔,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件秦叛,死亡現(xiàn)場離奇詭異晦溪,居然都是意外死亡,警方通過查閱死者的電腦和手機挣跋,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進店門三圆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人避咆,你說我怎么就攤上這事舟肉。” “怎么了查库?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵路媚,是天一觀的道長。 經(jīng)常有香客問我膨报,道長磷籍,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任现柠,我火速辦了婚禮院领,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘够吩。我一直安慰自己比然,他們只是感情好,可當我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布周循。 她就那樣靜靜地躺著强法,像睡著了一般。 火紅的嫁衣襯著肌膚如雪湾笛。 梳的紋絲不亂的頭發(fā)上饮怯,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天,我揣著相機與錄音嚎研,去河邊找鬼蓖墅。 笑死,一個胖子當著我的面吹牛临扮,可吹牛的內(nèi)容都是我干的论矾。 我是一名探鬼主播,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼杆勇,長吁一口氣:“原來是場噩夢啊……” “哼贪壳!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起蚜退,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤闰靴,失蹤者是張志新(化名)和其女友劉穎彪笼,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體传黄,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡杰扫,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了膘掰。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片章姓。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡仇冯,死狀恐怖姥份,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情擦盾,我是刑警寧澤窒舟,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布系忙,位于F島的核電站,受9級特大地震影響惠豺,放射性物質(zhì)發(fā)生泄漏银还。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一蛹疯、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧捺弦,春花似錦、人聲如沸列吼。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽陌选。三九已至,卻和暖如春柠贤,著一層夾襖步出監(jiān)牢的瞬間类缤,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工宴霸, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留囱晴,地道東北人瓢谢。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像枯芬,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子采郎,可洞房花燭夜當晚...
    茶點故事閱讀 42,762評論 2 345

推薦閱讀更多精彩內(nèi)容