Android源码中的AlertDialog分析

前言:
在Android源码中最常用到的Builder模式,就是AlertDialog.Builder,使用该Builder来
构建复杂的AlertDialog对象。

首先来看使用方法:

    AlertDialog.Builder builder = new AlertDialog.Builder(this);
    builder.setTitle("Title").setMessage("Message").setIcon(R.mipmap.ic_launcher).create().show();

从这个使用方式来看明显就是一个Builder模式,对外隐藏了内部的实现细节,将Dialog的各个组成部分在

Builder中进行组装。下面看AlertDialog的相关源码:

public class AlertDialog extends AppCompatDialog implements DialogInterface {
		//接收Builder成员变量P中的参数
	    private AlertController mAlert;
		
		protected AlertDialog(Context context) {
			this(context, resolveDialogTheme(context, 0), true);
		}
		
		protected AlertDialog(Context context, boolean cancelable, OnCancelListener cancelListener) {
			super(context, resolveDialogTheme(context, 0));
			setCancelable(cancelable);
			setOnCancelListener(cancelListener);
			mAlert = new AlertController(context, this, getWindow());
		}
	
	    @Override
		public void setTitle(CharSequence title) {
			super.setTitle(title);
			mAlert.setTitle(title);
		}
		 public void setCustomTitle(View customTitleView) {
			mAlert.setCustomTitle(customTitleView);
		}
	
		public void setView(View view) {
			mAlert.setView(view);
		}
		
		void setButtonPanelLayoutHint(int layoutHint) {
			mAlert.setButtonPanelLayoutHint(layoutHint);
		}
		......里面类似的这样的Set方法很多。
		
		
		public static class Builder {
			//存储Dialog布局中的各个参数,如:title,Message等
			private final AlertController.AlertParams P;
		
			//经典的Builder模式,通过Builder去构造,把所有的参数都存在P里
			Builder setTitle(CharSequence title) {
				P.mTitle = title;
				return this;
			}
			
		    public Builder setCustomTitle(View customTitleView) {
				P.mCustomTitleView = customTitleView;
				return this;
			}
		
		    public Builder setMessage(int messageId) {
				P.mMessage = P.mContext.getText(messageId);
				return this;
			}
			
			//构建AlertDialog的各个参数
			public AlertDialog create() {
				//实例化一个AlertDialog对象
				final AlertDialog dialog = new AlertDialog(P.mContext, mTheme, false);
				//将P中参数应用到Dialog.mAlert对象中,在这里面就是第三行的mAlert对象。
				P.apply(dialog.mAlert);
				dialog.setCancelable(P.mCancelable);
				if (P.mCancelable) {
					dialog.setCanceledOnTouchOutside(true);
				}
				dialog.setOnCancelListener(P.mOnCancelListener);
				dialog.setOnDismissListener(P.mOnDismissListener);
				if (P.mOnKeyListener != null) {
					dialog.setOnKeyListener(P.mOnKeyListener);
				}
				return dialog;
			}
		}
	}

上面的代码中就是通过Builder类设置AlertDialog的各个布局参数,比如title,message等,然后将这些参数存在

AlertParams中,AlertParams中包含了许多Dialog的布局参数。在调用create()方法的时候,实例化出一个新的

AlertDialog,并且将Builder成员变量中的参数通过apply方法将参数应用到AlerDialog的alert对象中。当我们

获取AlertDialog对象后,通过show函数就可以显示这个对话框。

我们来看Builder的show方法:

public AlertDialog show() {
            AlertDialog dialog = create();
            dialog.show();
            return dialog;
    }
	实际调用的是AlertDialog中的show()方法:
	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;
        //1.没有创建,则调用Dialog的OnCreate方法
        if (!mCreated) {
            dispatchOnCreate(null);
        }
		//2.调用Dialog的onStart()方法
        onStart();
		//获取到最顶层布局
        mDecor = mWindow.getDecorView();

        if (mActionBar == null && mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) {
            final ApplicationInfo info = mContext.getApplicationInfo();
            mWindow.setDefaultIcon(info.icon);
            mWindow.setDefaultLogo(info.logo);
            mActionBar = new WindowDecorActionBar(this);
        }

        WindowManager.LayoutParams l = mWindow.getAttributes();
        if ((l.softInputMode & WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) == 0) {
            WindowManager.LayoutParams nl = new WindowManager.LayoutParams();
            nl.copyFrom(l);//采用了原型模式,后面博文会讲
            nl.softInputMode |= WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
            l = nl;
        }

        try {
			//将mDecor添加到WindowManager中
            mWindowManager.addView(mDecor, l);
            mShowing = true;
			//发送一个显示Dialog的消息
            sendShowMessage();
        } finally {
        }
    }

在show函数中主要做了如下的事情:

(1)通过dispatchOnCreate函数来调用AlertDialog的onCreate函数

(2)然后接着调用onStart函数

(3)最后将Dialog的mDecor添加到WindowManager中(有点不明白这里,为什么要获取到最顶部的布局)

所以视图的创建一定是在onCreate()方法中:
//Dialog中的onCreate方法是个空实现,所以回调的是子类AlertDialog中的onCreate方法

	@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mAlert.installContent();
    }
	
	public void installContent() {
		//设置窗口的内容布局
		final int contentView = selectContentView();
        mDialog.setContentView(contentView);
        setupView();
    }
	
	private void setupView() {
		//调用Window对象的setContentView,实际上最终调用Window对象的setContentView函数
		//这些参数从AlertController的构造函数中可以看到,布局资源为:alert_dialog.xml
        final View parentPanel = mWindow.findViewById(R.id.parentPanel);
        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);
		//下面根据是否有按钮,是否有自定布局来设定相应的状态
        final boolean hasCustomPanel = customPanel != null
                && customPanel.getVisibility() != View.GONE;
        final boolean hasTopPanel = topPanel != null
                && topPanel.getVisibility() != View.GONE;
        final boolean hasButtonPanel = buttonPanel != null
                && buttonPanel.getVisibility() != View.GONE;

        // Only display the text spacer if we don't have buttons.
        if (!hasButtonPanel) {
            if (contentPanel != null) {
                final View spacer = contentPanel.findViewById(R.id.textSpacerNoButtons);
                if (spacer != null) {
                    spacer.setVisibility(View.VISIBLE);
                }
            }
        }

        if (hasTopPanel) {
            // Only clip scrolling content to padding if we have a title.
            if (mScrollView != null) {
                mScrollView.setClipToPadding(true);
            }
        }

        // Update scroll indicators as needed.
        if (!hasCustomPanel) {
            final View content = mListView != null ? mListView : mScrollView;
            if (content != null) {
                final int indicators = (hasTopPanel ? ViewCompat.SCROLL_INDICATOR_TOP : 0)
                        | (hasButtonPanel ? ViewCompat.SCROLL_INDICATOR_BOTTOM : 0);
                setScrollIndicators(contentPanel, content, indicators,
                        ViewCompat.SCROLL_INDICATOR_TOP | ViewCompat.SCROLL_INDICATOR_BOTTOM);
            }
        }
		//如果有listview就初始化
        final ListView listView = mListView;
        if (listView != null && mAdapter != null) {
            listView.setAdapter(mAdapter);
            final int checkedItem = mCheckedItem;
            if (checkedItem > -1) {
                listView.setItemChecked(checkedItem, true);
                listView.setSelection(checkedItem);
            }
        }
    }

setupView的作用就是用来初始化AlertDialog布局中的各个部分,在调用该函数后整个Dialog的视图
内容设置完毕。而这些区域的视图都属于mAlertDialogLayout的子元素,Window对象又关联了它的
整个布局树,当调用完setupView之后整个视图树的数据填充完毕,当用户调用show函数时,WindowManager
会将Window对象的DecorView添加到用户窗口上,并显示。

总结:在AlerDialog的Builder模式中并没有出现Director角色的出现,正式由于这样才使得整个程序显的简洁。
 设计模式是一种思想,不应该去生搬硬套,要灵活使用。
 
参考书籍:
《Android源码设计模式解析与实战》

    原文作者:Android源码分析
    原文地址: https://blog.csdn.net/hpc19950723/article/details/53322799
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞