这是一个常用的组件,既可以弹出一些简单的提示信息,也可以为AlertDialog自定义界面来实现较为复杂的空能。本篇博文就是准备对AlertDialog做个简单的梳理,毕竟用了这么多次了对它的内部原理还不慎了解,不符合自己的习惯。AlertDialog的使用很简单,几行代码就可以创建起来一个AlertDialog对象:
<pre name="code" class="java">AlertDialog.Builder builder = new AlertDialog.Builder(context); builder.setIcon(R.drawable.icon) builder.setTitle("Android Monkey"); builder.setMessage("can not find girl object error,so the poor monkey change to be a single dog"); //创建一个AlertDialog对象 AlertDialog dialog = builder.create(); dialog.show();
AlertDialog.Builder builder = new AlertDialog.Builder(context); //创建一个自定义View LayoutInflater inflater = LayoutInflater.from(context); View view = inflater.inflate(R.layout.xxx, null); builder.setView(view) //创建一个AlertDialog对象 AlertDialog dialog = builder.create(); dialog.show();
因为在AlertDialog类中,它提供的构造器都是protected的,所以无法直接在项目中使用new AlertDialog(context)来创建一个AlertDialog对象。下面我们就看看这个Builder类都干了些什么:
public AlertDialog create() { final AlertDialog dialog = new AlertDialog(P.mContext, mTheme, false); P.apply(dialog.mAlert); dialog.setCancelable(P.mCancelable); if (P.mCancelable) { dialog.setCanceledOnTouchOutside(true); } dialog.setOnCancelListener(P.mOnCancelListener); if (P.mOnKeyListener != null) { dialog.setOnKeyListener(P.mOnKeyListener); } return dialog; }
1)通过调用AlerDialog的构造器来创建一个AlertDialog对象,这个构造器最终会调用它的父类Dialog的构造器:
Dialog(Context context, int theme, boolean createContextWrapper) { mContext = createContextWrapper ? new ContextThemeWrapper(context, theme) : context; //得到一个mWindowManager mWindowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE); //创建一个Window Window w = PolicyManager.makeNewWindow(mContext); mWindow = w; w.setCallback(this); w.setWindowManager(mWindowManager, null, null); w.setGravity(Gravity.CENTER); mUiThread = Thread.currentThread(); mListenersHandler = new ListenersHandler(this); }上面的代码很简单,但是我们可以发现一个重要的信息, Dialog或者AlertDialog内部的核心工作也是有WindowManager+Window完成的,这点跟Activity或者poupWindow很类似。先把这个放在一边,待会会仔细说明WindowManager或者Window在AlertDialog在哪儿工作的(其实不难猜想)。 到这一步之后只是创建了一个简单的AlerDialog对象空壳,里面什么都没有,在界面上是显示不出来什么的,因为此时还没有设置AlertDialog的布局
2)在create()方法中我们发现了P变量,该参数是个类型为AlertController.AlertParams的变量,它的初始化过程很简单:
public Builder(Context context) { this(context, resolveDialogTheme(context, 0)); } public Builder(Context context, int theme) { P = new AlertController.AlertParams(new ContextThemeWrapper( context, resolveDialogTheme(context, theme))); mTheme = theme; }顾名思义 AlertParams指的就是builder构建AlertDialog对象所需要的参数,比如在上面的代码中设置builder.setTitle方法的实现中就是p这个对象在存储title信息:
public Builder setTitle(int titleId) { P.mTitle = P.mContext.getText(titleId); return this; }其余的方法类似,都是用p这个AlertParams对象来存储的,例如我们的自定义View builder.setView():
public Builder setView(View view) { //该参数在AlertController类的setupView方法中会体会到,详见下文 P.mView = view; P.mViewSpacingSpecified = false; return this; }
2)将AlertParams对象中存贮的元素设置到AlertController中(源码中apply方法的参数名为(AlertController dialog),为了避免造成误解我在此改成dialogController);
public void apply(AlertController dialogController) { if (mCustomTitleView != null) { dialogController.setCustomTitle(mCustomTitleView); } else { if (mTitle != null) { dialogController.setTitle(mTitle); } if (mIcon != null) { dialogController.setIcon(mIcon); } if (mIconId >= 0) { dialogController.setIcon(mIconId); } } if (mMessage != null) { dialogController.setMessage(mMessage); } if (mPositiveButtonText != null) { dialogController.setButton(DialogInterface.BUTTON_POSITIVE, mPositiveButtonText, mPositiveButtonListener, null); } if (mNegativeButtonText != null) { dialogController.setButton(DialogInterface.BUTTON_NEGATIVE, mNegativeButtonText, mNegativeButtonListener, null); } if (mNeutralButtonText != null) { dialogController.setButton(DialogInterface.BUTTON_NEUTRAL, mNeutralButtonText, mNeutralButtonListener, null); } if (mForceInverseBackground) { dialogController.setInverseBackgroundForced(true); } // For a list, the client can either supply an array of items or an // adapter or a cursor if ((mItems != null) || (mCursor != null) || (mAdapter != null)) { createListView(dialogController); } //将自定义View转存给AlertController,在AlertController中的setupView方法中使用,详见下文 if (mView != null) { if (mViewSpacingSpecified) { dialogController.setView(mView, mViewSpacingLeft, mViewSpacingTop, mViewSpacingRight, mViewSpacingBottom); } else { dialogController.setView(mView); } } /* dialog.setCancelable(mCancelable); dialog.setOnCancelListener(mOnCancelListener); if (mOnKeyListener != null) { dialogController.setOnKeyListener(mOnKeyListener); } */ }就这么简单,只是将AlertParams参数存储到AlertController中。在AlertDialog这个类中持有一个类型为AlertController的对象mAert,该对象在create方法中调用AlertDialog构造器的时候得到初始化:
mAlert = new AlertController(getContext(), this, getWindow());注意在此将AlertDialog的window对象传给了AlertController中的mWindow变量,而AlertDialog本身this则传给了AlertController的mDialogInterface这个DialogInterface类型的变量,因为AlertDialog就是实现了DialogInterface接口的类。到此为止apply方法就算简述完毕,但是纵观create方法,并没有把我们设置的自定义View或者title,message方法真正的设置到AlertDialog对象中去, 也就是说create方法创建完成后AlertDialog仍然是个空壳,也没有创建AlertDialog的布局,但是我们调用create()方法之后调用show()方法就可以现实一个窗口出来,很明显是AlertDialog的show方法在作怪,该方法是在其父类Dialog定义的。
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 创建AlertDialog视图 if (!mCreated) { dispatchOnCreate(null); } onStart(); mDecor = mWindow.getDecorView(); //设置参布局参数 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; } //步骤2 try { mWindowManager.addView(mDecor, l); mShowing = true; sendShowMessage(); } finally { } }上面代码中逻辑很清晰,在步骤1中调用了dispatchOnCreate方法,该方法只是简单的调用了onCreate方法():注意在Dialog这个父类中onCreate方法是个空方法,所以我们要去AlertDialog类中来查看:
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //此处是AlertController对象发挥作用的地方 mAlert.installContent(); }
installContent顾名思义,就是安装内容,安装的当然是AlertDialog的内容。继续深入:
//注mWindow对象在初始化AlertController的时候得到初始化,见上文 public void installContent() { /* We use a custom title so never request a window title */ mWindow.requestFeature(Window.FEATURE_NO_TITLE); if (mView == null || !canTextInput(mView)) { mWindow.setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM, WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM); } mWindow.setContentView(mAlertDialogLayout); setupView(); }
mAlertDialogLayout = a.getResourceId(com.android.internal.R.styleable.AlertDialog_layout, com.android.internal.R.layout.alert_dialog);alert_dialog.xml文件这个布局很简单,不过有点长,在此为了阅读会在博文的最后一并贴上来,不过可以提供一下Dialog的大致结构图:
其实在这一点上就可以发现Dialog和AlertDialog在源码或者使用上的区别:因为Dialog的onCreate方法为空方法,所以Dialog并没有像Alert方法一样有如上的界面的布局,简单的讲到这里,为了便于理解和博文的继续做一个简单的总结:
1)AlertDialog最根本的核心是通过mWindow和mWindowManager这两个对象来完成工作的,这两个对象在Builder的create方法初始化AlertDialog的时候得到的初始化
2)在构建AlertDialog的window对象传给了AlertController.并在installContent方法中为mWinow对象设置了一个contentView,确切说是创建了AlertDialog的View(rootVIew或者decorView)布局.
但是到此位置,我们的自定义View具体在哪儿设置的呢?目前位置还没有体现出来。不用着急,下面就来说明,
AlertDialog的自定义布局是怎么设置到布局里面去的?在调用完成了mWindow.setContentView之后紧接着调用了setupView. so just reading the fucking code:
(建议setupView代码结合着文章最后的alert_dialog.xml文件来看比较清晰)
private void setupView() { LinearLayout contentPanel = (LinearLayout) mWindow.findViewById(R.id.contentPanel); setupContent(contentPanel); //处理Dialog中最下面的上那个按钮,是否显示,如果仅有一个按钮的话,设置该按钮Button居中显示 boolean hasButtons = setupButtons(); LinearLayout topPanel = (LinearLayout) mWindow.findViewById(R.id.topPanel); TypedArray a = mContext.obtainStyledAttributes( null, com.android.internal.R.styleable.AlertDialog, com.android.internal.R.attr.alertDialogStyle, 0); boolean hasTitle = setupTitle(topPanel); //设置最下面中按钮区域的可见性 View buttonPanel = mWindow.findViewById(R.id.buttonPanel); if (!hasButtons) { buttonPanel.setVisibility(View.GONE); mWindow.setCloseOnTouchOutsideIfNotSet(true); } //处理用户自定义的View FrameLayout customPanel = null; if (mView != null) { customPanel = (FrameLayout) mWindow.findViewById(R.id.customPanel); FrameLayout custom = (FrameLayout) mWindow.findViewById(R.id.custom); //将用户设置的视图View添加到customPannel中去 custom.addView(mView, new LayoutParams(MATCH_PARENT, MATCH_PARENT)); if (mViewSpacingSpecified) { custom.setPadding(mViewSpacingLeft, mViewSpacingTop, mViewSpacingRight, mViewSpacingBottom); } if (mListView != null) { ((LinearLayout.LayoutParams) customPanel.getLayoutParams()).weight = 0; } } else {//如果用户没有自定义视图,那么就将自定义视图区域给隐藏掉 mWindow.findViewById(R.id.customPanel).setVisibility(View.GONE); } ....... }
1)创建Builder.调用builder.setView将用户自定义View设置到AlertParams这个对象中去:(详细代码见上文)
2)AlertParams类调用apply方法将自定义View传给AlertController(详细代码见上文)
3)setupView方法完成了自定义View添加到AlertDialog的过程。
到此为止show方法中的dispatchOnCreate()方法讲解完毕,该方法简而言之就是一步步构建AlertDialog的视图.在show方法的最后调用了mWindowManager的addView方法将AlertView的整个布局DecorView添加到了WindowManager,此时用户就可以在界面中看到一个完成的AlertDialog界面了,show方法做的工作可谓不少,在调用show方法的时候做了这么多处理也是起到了延迟初始化的作用。当然在现实的最后会发送一个消息,让用户自定义显示后的处理,限于博文篇幅再次就不多做说明。
最后附上alert_dialog.xml布局文件的代码:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/parentPanel" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:paddingTop="9dip" android:paddingBottom="3dip" android:paddingLeft="3dip" android:paddingRight="1dip"> <LinearLayout android:id="@+id/topPanel" android:layout_width="match_parent" android:layout_height="wrap_content" android:minHeight="54dip" android:orientation="vertical"> <LinearLayout android:id="@+id/title_template" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:gravity="center_vertical" android:layout_marginTop="6dip" android:layout_marginBottom="9dip" android:layout_marginLeft="10dip" android:layout_marginRight="10dip"> <ImageView android:id="@+id/icon" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="top" android:paddingTop="6dip" android:paddingRight="10dip" android:src="@drawable/ic_dialog_info" /> <com.android.internal.widget.DialogTitle android:id="@+id/alertTitle" style="?android:attr/textAppearanceLarge" android:singleLine="true" android:ellipsize="end" android:layout_width="match_parent" android:layout_height="wrap_content" /> </LinearLayout> <ImageView android:id="@+id/titleDivider" android:layout_width="match_parent" android:layout_height="1dip" android:visibility="gone" android:scaleType="fitXY" android:gravity="fill_horizontal" android:src="@android:drawable/divider_horizontal_dark" /> <!-- If the client uses a customTitle, it will be added here. --> </LinearLayout> <LinearLayout android:id="@+id/contentPanel" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_weight="1" android:orientation="vertical"> <ScrollView android:id="@+id/scrollView" android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingTop="2dip" android:paddingBottom="12dip" android:paddingLeft="14dip" android:paddingRight="10dip" android:overScrollMode="ifContentScrolls"> <TextView android:id="@+id/message" style="?android:attr/textAppearanceMedium" android:layout_width="match_parent" android:layout_height="wrap_content" android:padding="5dip" /> </ScrollView> </LinearLayout> <FrameLayout android:id="@+id/customPanel" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_weight="1"> <FrameLayout android:id="@+android:id/custom" android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingTop="5dip" android:paddingBottom="5dip" /> </FrameLayout> <LinearLayout android:id="@+id/buttonPanel" android:layout_width="match_parent" android:layout_height="wrap_content" android:minHeight="54dip" android:orientation="vertical" > <LinearLayout style="?android:attr/buttonBarStyle" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:paddingTop="4dip" android:paddingLeft="2dip" android:paddingRight="2dip" android:measureWithLargestChild="true"> <LinearLayout android:id="@+id/leftSpacer" android:layout_weight="0.25" android:layout_width="0dip" android:layout_height="wrap_content" android:orientation="horizontal" android:visibility="gone" /> <Button android:id="@+id/button1" android:layout_width="0dip" android:layout_gravity="left" android:layout_weight="1" style="?android:attr/buttonBarButtonStyle" android:maxLines="2" android:layout_height="wrap_content" /> <Button android:id="@+id/button3" android:layout_width="0dip" android:layout_gravity="center_horizontal" android:layout_weight="1" style="?android:attr/buttonBarButtonStyle" android:maxLines="2" android:layout_height="wrap_content" /> <Button android:id="@+id/button2" android:layout_width="0dip" android:layout_gravity="right" android:layout_weight="1" style="?android:attr/buttonBarButtonStyle" android:maxLines="2" android:layout_height="wrap_content" /> <LinearLayout android:id="@+id/rightSpacer" android:layout_width="0dip" android:layout_weight="0.25" android:layout_height="wrap_content" android:orientation="horizontal" android:visibility="gone" /> </LinearLayout> </LinearLayout> </LinearLayout>这个 布局文件也很简单,我们设置的setTitle,setIcon,setMessage正式为上面布局文件的某一个组件设置具体的内容。
总之追踪下来AlertDialog内部创建细节还是很繁琐的,但是我们仅仅通过Builder就可以一步步的创建出AlertDialog,创建者模式的作用算是得到了体现。