AlertDialog创建过程详解

这是一个常用的组件,既可以弹出一些简单的提示信息,也可以为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();

 
 
 
 
看到上面的代码,熟悉或者了解设计模式的monkey应该知道这样的代码结构是建造者模式的体现,本篇不会具体创建建造者模式,关于此模式网上可查到好多的资料,在这里就不班门弄斧以至于误导别人,总之它的作用就是使得用户在不了解具体构建细节的情况下来创建一个复杂的对象。而本片博客准备剖析一下这个细节。

    因为在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;
        }

下面就一步步分析这个create方法是怎么构建一个具体的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;
        }

其余的方法就不一一列举, 总之AlertParams这个类存储了AlertDialog所需的元素。在create方法中调用了AlertParams类的apply(AlertController alertController)这个方法,将AlertParams所存储的用来构建AlertDialog对象的元素设置到了AlertController中,查阅apply很简单的就可以体现出来:

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();
    }

这个方法也很简单,但是用一个我们很熟悉的方法setContentView方法, 说它熟悉是因为在Activity中我们调用activity的setContentView方法也是这个方法实现的。追根究底了这么久我们终于找到了AlertDialog的内容布局,该布局由mAlertDialogLayout所代表的布局文件,在初始化AlertView的方法中已经初始化了它:

        mAlertDialogLayout = a.getResourceId(com.android.internal.R.styleable.AlertDialog_layout,
                com.android.internal.R.layout.alert_dialog);
alert_dialog.xml文件这个布局很简单,不过有点长,在此为了阅读会在博文的最后一并贴上来,不过可以提供一下Dialog的大致结构图:

AlertDialog创建过程详解_第1张图片

 其实在这一点上就可以发现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);
        }
      .......
    }

可以发现setupView方法完成了AlertDialog布局中的各个部分,:最上面的icon+标题区域,自定义View区域等。在上面的代码中限于篇幅并没有讲解AlertDialog关于Button的处理,关于setupView方法的中自定义布局mView的初始化过程在此在做总结一下:

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,创建者模式的作用算是得到了体现。







你可能感兴趣的:(android,AlertDialog,windowmanager)