AlertDialog源码分析

构造方法

protected AlertDialog(Context context) {
    this(context, resolveDialogTheme(context, 0), true);
}

protected AlertDialog(Context context, int theme) {
    this(context, theme, true);
}

AlertDialog(Context context, int theme, boolean createThemeContextWrapper) {
    super(context, resolveDialogTheme(context, theme), createThemeContextWrapper);

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

protected AlertDialog(Context context, boolean cancelable, OnCancelListener cancelListener) {
    super(context, resolveDialogTheme(context, 0));
    mWindow.alwaysReadCloseOnTouchAttr();
    setCancelable(cancelable);
    setOnCancelListener(cancelListener);
    mAlert = new AlertController(context, this, getWindow());
}

AlertDialog的构造函数都是protected或者默认修饰的,这就说明我们无法直接创建AlertDialog的对象实例。其内部的主要实现是:创建一个AlertController对象,该类单纯听名字就可猜出它的大概作用:AlertDialog的控制器

AlertController

在AlertDialog的构造函数中创建了一个AlertController对象,这个类的作用是什么呢?我们从它的构造函数开始分析:

public AlertController(Context context, DialogInterface di, Window window) {
    mContext = context;
    mDialogInterface = di;
    mWindow = window;
    mHandler = new ButtonHandler(di);

    TypedArray a = context.obtainStyledAttributes(null,
            com.android.internal.R.styleable.AlertDialog,
            com.android.internal.R.attr.alertDialogStyle, 0);

    mAlertDialogLayout = a.getResourceId(com.android.internal.R.styleable.AlertDialog_layout,
            com.android.internal.R.layout.alert_dialog);
    mButtonPanelSideLayout = a.getResourceId(
            com.android.internal.R.styleable.AlertDialog_buttonPanelSideLayout, 0);

    mListLayout = a.getResourceId(
            com.android.internal.R.styleable.AlertDialog_listLayout,
            com.android.internal.R.layout.select_dialog);
    mMultiChoiceItemLayout = a.getResourceId(
            com.android.internal.R.styleable.AlertDialog_multiChoiceItemLayout,
            com.android.internal.R.layout.select_dialog_multichoice);
    mSingleChoiceItemLayout = a.getResourceId(
            com.android.internal.R.styleable.AlertDialog_singleChoiceItemLayout,
            com.android.internal.R.layout.select_dialog_singlechoice);
    mListItemLayout = a.getResourceId(
            com.android.internal.R.styleable.AlertDialog_listItemLayout,
            com.android.internal.R.layout.select_dialog_item);

    a.recycle();
}

这里主要做了两个操作:
- 创建了一个ButtonHandler实例
- 初始化一些系统提供的默认样式

ButtonHandler

private static final class ButtonHandler extends Handler {
    // Button clicks have Message.what as the BUTTON{1,2,3} constant
    private static final int MSG_DISMISS_DIALOG = 1;

    private WeakReference<DialogInterface> mDialog;

    public ButtonHandler(DialogInterface dialog) {
        mDialog = new WeakReference<DialogInterface>(dialog);
    }

    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {

            case DialogInterface.BUTTON_POSITIVE:
            case DialogInterface.BUTTON_NEGATIVE:
            case DialogInterface.BUTTON_NEUTRAL:
                ((DialogInterface.OnClickListener) msg.obj).onClick(mDialog.get(), msg.what);
                break;

            case MSG_DISMISS_DIALOG:
                ((DialogInterface) msg.obj).dismiss();
        }
    }
}

可以看到,它是继承与Handler类的。再来看下它的实例在哪里被使用:
1. 用于按钮被点击时,关闭dialog(其类型为MSG_DISMISS_DIALOG)

private final View.OnClickListener mButtonHandler = new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        final Message m;
        if (v == mButtonPositive && mButtonPositiveMessage != null) {
            m = Message.obtain(mButtonPositiveMessage);
        } else if (v == mButtonNegative && mButtonNegativeMessage != null) {
            m = Message.obtain(mButtonNegativeMessage);
        } else if (v == mButtonNeutral && mButtonNeutralMessage != null) {
            m = Message.obtain(mButtonNeutralMessage);
        } else {
            m = null;
        }

        if (m != null) {
            m.sendToTarget();
        }

        // Post a message so we dismiss after the above handlers are executed
        mHandler.obtainMessage(ButtonHandler.MSG_DISMISS_DIALOG, mDialogInterface)
                .sendToTarget();
    }
};
  1. 当三种按钮的其中一种被点击时,调用DialogInterface.OnClickListener的onClick()函数
public void setButton(int whichButton, CharSequence text,
    DialogInterface.OnClickListener listener, Message msg) {

    if (msg == null && listener != null) {
        msg = mHandler.obtainMessage(whichButton, listener);
    }

    switch (whichButton) {

        case DialogInterface.BUTTON_POSITIVE:
            mButtonPositiveText = text;
            mButtonPositiveMessage = msg;
            break;

        case DialogInterface.BUTTON_NEGATIVE:
            mButtonNegativeText = text;
            mButtonNegativeMessage = msg;
            break;

        case DialogInterface.BUTTON_NEUTRAL:
            mButtonNeutralText = text;
            mButtonNeutralMessage = msg;
            break;

        default:
            throw new IllegalArgumentException("Button does not exist");
    }
}

Builder

通常我们使用AlertDialog时,是通过其内部类Builder来实例化的:

AlertDialog dialog = new AlertDialog.Builder(MainActivity.this).create();

那create里边做了什么操作呢?

/**
 * Creates an {@link AlertDialog} with the arguments supplied to this
 * builder.
 * <p>
 * Calling this method does not display the dialog. If no additional
 * processing is needed, {@link #show()} may be called instead to both
 * create and display the dialog.
 */
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);
    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;
}

在其内部,调用了AlertDialog包含三个参数的构造方法,创建了一个实例,并调用了P.apply(dialog.mAlert)方法。那这里的p是什么?apply的作用又是什么?我们接着往下看:

private final AlertController.AlertParams P;

public Builder(Context context) {
    this(context, resolveDialogTheme(context, 0));
}

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

p是一个AlertController.AlertParams类的实例,其内部含有一系列与AlertDialog有关的变量,在Builder构造函数中进行了初始化

我们接着看它的apply()方法:

public void apply(AlertController dialog) {
    if (mCustomTitleView != null) {
        dialog.setCustomTitle(mCustomTitleView);
    } else {
        if (mTitle != null) {
            dialog.setTitle(mTitle);
        }
        if (mIcon != null) {
            dialog.setIcon(mIcon);
        }
        if (mIconId >= 0) {
            dialog.setIcon(mIconId);
        }
        if (mIconAttrId > 0) {
            dialog.setIcon(dialog.getIconAttributeResId(mIconAttrId));
        }
    }
    if (mMessage != null) {
        dialog.setMessage(mMessage);
    }
    if (mPositiveButtonText != null) {
        dialog.setButton(DialogInterface.BUTTON_POSITIVE, mPositiveButtonText,
                mPositiveButtonListener, null);
    }
    if (mNegativeButtonText != null) {
        dialog.setButton(DialogInterface.BUTTON_NEGATIVE, mNegativeButtonText,
                mNegativeButtonListener, null);
    }
    if (mNeutralButtonText != null) {
        dialog.setButton(DialogInterface.BUTTON_NEUTRAL, mNeutralButtonText,
                mNeutralButtonListener, null);
    }
    if (mForceInverseBackground) {
        dialog.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(dialog);
    }
    if (mView != null) {
        if (mViewSpacingSpecified) {
            dialog.setView(mView, mViewSpacingLeft, mViewSpacingTop, mViewSpacingRight,
                    mViewSpacingBottom);
        } else {
            dialog.setView(mView);
        }
    } else if (mViewLayoutResId != 0) {
        dialog.setView(mViewLayoutResId);
    }

    /*
    dialog.setCancelable(mCancelable);
    dialog.setOnCancelListener(mOnCancelListener);
    if (mOnKeyListener != null) {
        dialog.setOnKeyListener(mOnKeyListener);
    }
    */
}

该方法接收一个AlertController类型的参数,在其中设置AlertController的一些属性值

那AlertController的这些属性值是如何被运用到AlertDialog上的呢?

还记得上篇Dialog源码分析分析的show()方法吗?该方法内部调用了dispatchOnCreate(null)方法,而这个方法内部调用了onCreate()方法。AlertDialog继承于Dialog,其重写了Dialog的onCreate()方法,我们进入看看:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    mAlert.installContent();
}

该方法内部调用了AlertController的installContent()方法,我们再进去:

public void installContent() {
    /* We use a custom title so never request a window title */
    mWindow.requestFeature(Window.FEATURE_NO_TITLE);
    int contentView = selectContentView();
    mWindow.setContentView(contentView);
    setupView();
    setupDecor();
}

private int selectContentView() {
    if (mButtonPanelSideLayout == 0) {
        return mAlertDialogLayout;
    }
    if (mButtonPanelLayoutHint == AlertDialog.LAYOUT_HINT_SIDE) {
        return mButtonPanelSideLayout;
    }
    // TODO: use layout hint side for long messages/lists
    return mAlertDialogLayout;
}

private void setupView() {
    final LinearLayout contentPanel = (LinearLayout) mWindow.findViewById(R.id.contentPanel);
    setupContent(contentPanel);
    final boolean hasButtons = setupButtons();

    final LinearLayout topPanel = (LinearLayout) mWindow.findViewById(R.id.topPanel);
    final TypedArray a = mContext.obtainStyledAttributes(
            null, R.styleable.AlertDialog, R.attr.alertDialogStyle, 0);
    final boolean hasTitle = setupTitle(topPanel);

    final View buttonPanel = mWindow.findViewById(R.id.buttonPanel);
    if (!hasButtons) {
        buttonPanel.setVisibility(View.GONE);
        final View spacer = mWindow.findViewById(R.id.textSpacerNoButtons);
        if (spacer != null) {
            spacer.setVisibility(View.VISIBLE);
        }
        mWindow.setCloseOnTouchOutsideIfNotSet(true);
    }

    final FrameLayout customPanel = (FrameLayout) mWindow.findViewById(R.id.customPanel);
    final View customView;
    if (mView != null) {
        customView = mView;
    } else if (mViewLayoutResId != 0) {
        final LayoutInflater inflater = LayoutInflater.from(mContext);
        customView = inflater.inflate(mViewLayoutResId, customPanel, false);
    } else {
        customView = null;
    }

    final boolean hasCustomView = customView != null;
    if (!hasCustomView || !canTextInput(customView)) {
        mWindow.setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
                WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
    }

    if (hasCustomView) {
        final FrameLayout custom = (FrameLayout) mWindow.findViewById(R.id.custom);
        custom.addView(customView, new LayoutParams(MATCH_PARENT, MATCH_PARENT));

        if (mViewSpacingSpecified) {
            custom.setPadding(
                    mViewSpacingLeft, mViewSpacingTop, mViewSpacingRight, mViewSpacingBottom);
        }

        if (mListView != null) {
            ((LinearLayout.LayoutParams) customPanel.getLayoutParams()).weight = 0;
        }
    } else {
        customPanel.setVisibility(View.GONE);
    }

    // Only display the divider if we have a title and a custom view or a
    // message.
    if (hasTitle) {
        final View divider;
        if (mMessage != null || customView != null || mListView != null) {
            divider = mWindow.findViewById(R.id.titleDivider);
        } else {
            divider = mWindow.findViewById(R.id.titleDividerTop);
        }

        if (divider != null) {
            divider.setVisibility(View.VISIBLE);
        }
    }

    setBackground(a, topPanel, contentPanel, customPanel, buttonPanel, hasTitle, hasCustomView,
            hasButtons);
    a.recycle();
}

这几个方法很长,但其主要的作用就是:通过AlertController中保存的属性值,设置AlertDialog显示的内容等。

下边就结合alert_dialog.xml重点分析下setupView()方法:

// alert_dialog.xml

<?xml version="1.0" encoding="utf-8"?>
<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:paddingStart="3dip"
    android:paddingEnd="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_marginStart="10dip"
            android:layout_marginEnd="10dip">
            <ImageView android:id="@+id/icon"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="top"
                android:paddingTop="6dip"
                android:paddingEnd="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"
                android:textAlignment="viewStart" />
        </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:paddingStart="14dip"
            android:paddingEnd="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:paddingStart="2dip"
            android:paddingEnd="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="start"
                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="end"
                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>

1.contentPanel

<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:paddingStart="14dip"
        android:paddingEnd="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>

final LinearLayout contentPanel = (LinearLayout) mWindow.findViewById(R.id.contentPanel);
setupContent(contentPanel);

可以看到,contentPanel里边包含了一个id为scrollView的ScrollView,而ScrollView里边包含了一个id为message的TextView。进入setupContent:

private void setupContent(LinearLayout contentPanel) {
    mScrollView = (ScrollView) mWindow.findViewById(R.id.scrollView);
    mScrollView.setFocusable(false);

    // Special case for users that only want to display a String
    mMessageView = (TextView) mWindow.findViewById(R.id.message);
    if (mMessageView == null) {
        return;
    }

    if (mMessage != null) {
        mMessageView.setText(mMessage);
    } else {
        mMessageView.setVisibility(View.GONE);
        mScrollView.removeView(mMessageView);

        if (mListView != null) {
            contentPanel.removeView(mWindow.findViewById(R.id.scrollView));
            contentPanel.addView(mListView,
                    new LinearLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
            contentPanel.setLayoutParams(new LinearLayout.LayoutParams(MATCH_PARENT, 0, 1.0f));
        } else {
            contentPanel.setVisibility(View.GONE);
        }
    }
}

这里边有几种情况:
- 假如mMessage不为空,设置mMessageView
- 假如mMessage为空,且mListiew不为空,则移除scrollview并添加mListView进去
- 假如mMessage为空,且mListiew也为空,则将contentPanel隐藏
2. setupButton

<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:paddingStart="2dip"
        android:paddingEnd="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="start"
            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="end"
            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>

private boolean setupButtons() {
    int BIT_BUTTON_POSITIVE = 1;
    int BIT_BUTTON_NEGATIVE = 2;
    int BIT_BUTTON_NEUTRAL = 4;
    int whichButtons = 0;
    mButtonPositive = (Button) mWindow.findViewById(R.id.button1);
    mButtonPositive.setOnClickListener(mButtonHandler);

    if (TextUtils.isEmpty(mButtonPositiveText)) {
        mButtonPositive.setVisibility(View.GONE);
    } else {
        mButtonPositive.setText(mButtonPositiveText);
        mButtonPositive.setVisibility(View.VISIBLE);
        whichButtons = whichButtons | BIT_BUTTON_POSITIVE;
    }

    mButtonNegative = (Button) mWindow.findViewById(R.id.button2);
    mButtonNegative.setOnClickListener(mButtonHandler);

    if (TextUtils.isEmpty(mButtonNegativeText)) {
        mButtonNegative.setVisibility(View.GONE);
    } else {
        mButtonNegative.setText(mButtonNegativeText);
        mButtonNegative.setVisibility(View.VISIBLE);

        whichButtons = whichButtons | BIT_BUTTON_NEGATIVE;
    }

    mButtonNeutral = (Button) mWindow.findViewById(R.id.button3);
    mButtonNeutral.setOnClickListener(mButtonHandler);

    if (TextUtils.isEmpty(mButtonNeutralText)) {
        mButtonNeutral.setVisibility(View.GONE);
    } else {
        mButtonNeutral.setText(mButtonNeutralText);
        mButtonNeutral.setVisibility(View.VISIBLE);

        whichButtons = whichButtons | BIT_BUTTON_NEUTRAL;
    }

    if (shouldCenterSingleButton(mContext)) {
        /*
         * If we only have 1 button it should be centered on the layout and
         * expand to fill 50% of the available space.
         */
        if (whichButtons == BIT_BUTTON_POSITIVE) {
            centerButton(mButtonPositive);
        } else if (whichButtons == BIT_BUTTON_NEGATIVE) {
            centerButton(mButtonNegative);
        } else if (whichButtons == BIT_BUTTON_NEUTRAL) {
            centerButton(mButtonNeutral);
        }
    }

    return whichButtons != 0;
}

通过mButtonPositiveText、mButtonNeutralText和mButtonNegativeText是否为空来控制显示相应的Button,并设置相应的点击事件
3. setupTitle

<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_marginStart="10dip"
        android:layout_marginEnd="10dip">
        <ImageView android:id="@+id/icon"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="top"
            android:paddingTop="6dip"
            android:paddingEnd="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"
            android:textAlignment="viewStart" />
    </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>

final LinearLayout topPanel = (LinearLayout) mWindow.findViewById(R.id.topPanel);
final TypedArray a = mContext.obtainStyledAttributes(
        null, R.styleable.AlertDialog, R.attr.alertDialogStyle, 0);
final boolean hasTitle = setupTitle(topPanel);

private boolean setupTitle(LinearLayout topPanel) {
        boolean hasTitle = true;

    if (mCustomTitleView != null) {
        // Add the custom title view directly to the topPanel layout
        LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
                LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);

        topPanel.addView(mCustomTitleView, 0, lp);

        // Hide the title template
        View titleTemplate = mWindow.findViewById(R.id.title_template);
        titleTemplate.setVisibility(View.GONE);
    } else {
        mIconView = (ImageView) mWindow.findViewById(R.id.icon);

        final boolean hasTextTitle = !TextUtils.isEmpty(mTitle);
        if (hasTextTitle) {
            // Display the title if a title is supplied, else hide it.
            mTitleView = (TextView) mWindow.findViewById(R.id.alertTitle);
            mTitleView.setText(mTitle);

            // Do this last so that if the user has supplied any icons we
            // use them instead of the default ones. If the user has
            // specified 0 then make it disappear.
            if (mIconId != 0) {
                mIconView.setImageResource(mIconId);
            } else if (mIcon != null) {
                mIconView.setImageDrawable(mIcon);
            } else {
                // Apply the padding from the icon to ensure the title is
                // aligned correctly.
                mTitleView.setPadding(mIconView.getPaddingLeft(),
                        mIconView.getPaddingTop(),
                        mIconView.getPaddingRight(),
                        mIconView.getPaddingBottom());
                mIconView.setVisibility(View.GONE);
            }
        } else {
            // Hide the title template
            final View titleTemplate = mWindow.findViewById(R.id.title_template);
            titleTemplate.setVisibility(View.GONE);
            mIconView.setVisibility(View.GONE);
            topPanel.setVisibility(View.GONE);
            hasTitle = false;
        }
    }
    return hasTitle;
}
  • 假如mCustomTitleView(可通过setCustomTitle设置)不为空,则直接将该view设置给topPanel
  • 假如mCustomTitleView为空,并且设置了mTitle,则通过AlertController中的属性设置相应的视图
  • 假如mCustomTitleView为空,并且mTitle为空,则隐藏标题栏
    1. customPanel
<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>

final FrameLayout customPanel = (FrameLayout) mWindow.findViewById(R.id.customPanel);
    final View customView;
    if (mView != null) {
        customView = mView;
    } else if (mViewLayoutResId != 0) {
        final LayoutInflater inflater = LayoutInflater.from(mContext);
        customView = inflater.inflate(mViewLayoutResId, customPanel, false);
    } else {
        customView = null;
    }

    final boolean hasCustomView = customView != null;
    if (!hasCustomView || !canTextInput(customView)) {
        mWindow.setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
                WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
    }

    if (hasCustomView) {
        final FrameLayout custom = (FrameLayout) mWindow.findViewById(R.id.custom);
        custom.addView(customView, new LayoutParams(MATCH_PARENT, MATCH_PARENT));

        if (mViewSpacingSpecified) {
            custom.setPadding(
                    mViewSpacingLeft, mViewSpacingTop, mViewSpacingRight, mViewSpacingBottom);
        }

        if (mListView != null) {
            ((LinearLayout.LayoutParams) customPanel.getLayoutParams()).weight = 0;
        }
    } else {
        customPanel.setVisibility(View.GONE);
    }

自定义的视图主要添加在android:id=”@+android:id/custom”的FrameLayout中,假如mListView不为空,则隐藏该布局
5. divider

if (hasTitle) {
    final View divider;
    if (mMessage != null || customView != null || mListView != null) {
        divider = mWindow.findViewById(R.id.titleDivider);
    } else {
        divider = mWindow.findViewById(R.id.titleDividerTop);
    }

    if (divider != null) {
        divider.setVisibility(View.VISIBLE);
    }
}

主要设置标题栏与内容栏的间隔线,假如mMessage、customView或者mListView不为空,则显示
6. 设置背景

setBackground(a, topPanel, contentPanel, customPanel, buttonPanel, hasTitle, hasCustomView,
                hasButtons);

属性设置

我们可以通过两种方式设置AlertController中的属性值
1. 通过AlertDialog的实例

// 例子
AlertDialog dialog = new AlertDialog.Builder(MainActivity.this).create();
dialog.setTitle("1111");

进入setTitle方法,我们可以看到内部是调用了AlertController相应的方法来设置本身的值

@Override
public void setTitle(CharSequence title) {
    super.setTitle(title);
    mAlert.setTitle(title);
}
  1. 通过Builder的实例
// 例子
AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
builder.setTitle("111");

进入setTitle方法,我们可以看到内部是是将p的属性值设置成相应的值。通过我们上边的分析,知道p会通过apply方法把自己变量的值赋予AlertController相应的变量,最终还是通过AlertController来改变属性

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

总结

  1. 假如只需要显示一行文字,可通过调用setMessage()设置需要显示的文字即可
  2. 可自定义title,通过setCustomTitle()方法设置
  3. 可自定义内容view,通过setView方法设置
  4. AlertDialog提供了三种可用按钮,可通过setPositiveButton、setNegativeButton和setNegativeButton设置显示和监听点击
  5. AlertDialog提供了内置的ListView,并支持单选和多选方式
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值