为了账号安全,请及时绑定邮箱和手机立即绑定

Android源码解析之AlertDialog,彻底搞懂AlertDialog的实现原理

标签:
Android
  • 文章目录

    • AlertDialog使用

    • AlertDialog源码解析

    • 总结

  • 在Activity中展示AlertDialog

                  new AlertDialog.Builder(this)                        .setTitle("标题")                        .setMessage("内容")                        .setPositiveButton("确定", new DialogInterface.OnClickListener() {                            @Override                            public void onClick(DialogInterface dialog, int i) {                                dialog.dismiss();                            }                        })                        .setNegativeButton("取消", new DialogInterface.OnClickListener() {                            @Override                            public void onClick(DialogInterface dialog, int i) {                                dialog.dismiss();                            }                        })                        .create()                        .show();

    效果:spacer.gif

spacer.gif5b9bbf44000132d503360172.jpg

  • AlertDialog源码解析:

    • 查看AlertDialog的继承(使用V7包中的)

    • spacer.gif5b9bbf440001d32704950279.jpg

    • 查看AlertDialog的构造函数


      1.发现AlertDialog的构造函数都是protected修饰的,所以可以知道在外部不能直接new出对象,需要使用静态内部类Builder,接下来会详细介绍

      2.第一和第三个构造函数都会调用第二个构造函数,在第二个构造函数中主要处理了

  1.    protected AlertDialog(@NonNull Context context) {

  2.        this(context, 0);

  3.    }

  4.    /**

  5.     * Construct an AlertDialog that uses an explicit theme.  The actual style

  6.     * that an AlertDialog uses is a private implementation, however you can

  7.     * here supply either the name of an attribute in the theme from which

  8.     * to get the dialog's style (such as {@link R.attr#alertDialogTheme}.

  9.     */

  10.    protected AlertDialog(@NonNull Context context, @StyleRes int themeResId) {

  11.        super(context, resolveDialogTheme(context, themeResId));

  12.        mAlert = new AlertController(getContext(), this, getWindow());

  13.    }

  14.    protected AlertDialog(@NonNull Context context, boolean cancelable,

  15.            @Nullable OnCancelListener cancelListener) {

  16.        this(context, 0);

  17.        setCancelable(cancelable);

  18.        setOnCancelListener(cancelListener);

  19.    }

                    a.调用方法resolveDialogThrem(),确定Dialog主题,如果调用者没有设置自己的主题,就使用系统默认主题

                    b.实例化全局变量mAlert = new AlertController();该变量主要用来数据


  1.    static int resolveDialogTheme(@NonNull Context context, @StyleRes int resid) {

  2.        if (resid >= 0x01000000) {   // start of real resource IDs.

  3.            return resid;

  4.        } else {

  5.            TypedValue outValue = new TypedValue();

  6.            context.getTheme().resolveAttribute(R.attr.alertDialogTheme, outValue, true);

  7.            return outValue.resourceId;

  8.        }

  9.    }

接着我们再看下AlertDialog的父类Dialog的构造函数做了那些操作


  • 我们方法在Dialog也是做了初始化的操作,其中最重要的是处理化了Window对象

  1.    public Dialog(@NonNull Context context) {

  2.        this(context, 0, true);

  3.    }

  4.    

  5.    public Dialog(@NonNull Context context, @StyleRes int themeResId) {

  6.        this(context, themeResId, true);

  7.    }

  8.    Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {

  9.        if (createContextThemeWrapper) {

  10.            if (themeResId == 0) {

  11.                final TypedValue outValue = new TypedValue();

  12.                context.getTheme().resolveAttribute(R.attr.dialogTheme, outValue, true);

  13.                themeResId = outValue.resourceId;

  14.            }

  15.            mContext = new ContextThemeWrapper(context, themeResId);

  16.        } else {

  17.            mContext = context;

  18.        }

  19.         //通过上下文拿到窗体管理器WindowManager

  20.        mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);

  21.         //初始化Window,使用的是其子类PhoneWindow

  22.        final Window w = new PhoneWindow(mContext);

  23.        mWindow = w;

  24.        w.setCallback(this);

  25.        w.setOnWindowDismissedCallback(this);

  26.        w.setWindowManager(mWindowManager, null, null);

  27.        w.setGravity(Gravity.CENTER);

  28.         //初始化Handler

  29.        mListenersHandler = new ListenersHandler(this);

  30.    }


  • Builder源码解析

    • Builder是AlertDialog中的一个静态内部类->查看构造函数


  1.    public static class Builder {

  2.        private final AlertController.AlertParams P;

  3.        private final int mTheme;

  4.        public Builder(@NonNull Context context) {

  5.            this(context, resolveDialogTheme(context, 0));

  6.        }

  7.        public Builder(@NonNull Context context, @StyleRes int themeResId) {

  8.            P = new AlertController.AlertParams(new ContextThemeWrapper(

  9.                    context, resolveDialogTheme(context, themeResId)));

  10.            mTheme = themeResId;

  11.        }

  12.    }

                1.构造函数中主要初始化变量P = new AlertController.AlertParams(),该变量也是用来保存封装数据用的

  • 接下来就是很多设置方法,并且这些设置的数据都是放在变量P中.


  1.        /**

  2.         * 设置title,数据保存在P中的mTitle属性中

  3.         */

  4.        public Builder setTitle(@StringRes int titleId) {

  5.            P.mTitle = P.mContext.getText(titleId);

  6.            return this;

  7.        }

  8.        public Builder setTitle(@Nullable CharSequence title) {

  9.            P.mTitle = title;

  10.            return this;

  11.        }

  12.        /**

  13.         * 设置自己想要的title布局样式

  14.         */

  15.        public Builder setCustomTitle(@Nullable View customTitleView) {

  16.            P.mCustomTitleView = customTitleView;

  17.            return this;

  18.        }

  19.        /**

  20.         * 设置展示内容

  21.         */

  22.        public Builder setMessage(@StringRes int messageId) {

  23.            P.mMessage = P.mContext.getText(messageId);

  24.            return this;

  25.        }

  26.        public Builder setMessage(@Nullable CharSequence message) {

  27.            P.mMessage = message;

  28.            return this;

  29.        }

  30.        /**

  31.         * 设置title位置的icon

  32.         */

  33.        public Builder setIcon(@DrawableRes int iconId) {

  34.            P.mIconId = iconId;

  35.            return this;

  36.        }

  37.        public Builder setIcon(@Nullable Drawable icon) {

  38.            P.mIcon = icon;

  39.            return this;

  40.        }

  41.        /**

  42.          * 分别设置按钮文字和点击事件

  43.         */

  44.        public Builder setPositiveButton(CharSequence text, final OnClickListener listener) {

  45.            P.mPositiveButtonText = text;

  46.            P.mPositiveButtonListener = listener;

  47.            return this;

  48.        }

  49.        public Builder setNegativeButton(CharSequence text, final OnClickListener listener) {

  50.            P.mNegativeButtonText = text;

  51.            P.mNegativeButtonListener = listener;

  52.            return this;

  53.        }

  54.        public Builder setNeutralButton(CharSequence text, final OnClickListener listener) {

  55.            P.mNeutralButtonText = text;

  56.            P.mNeutralButtonListener = listener;

  57.            return this;

  58.        }

  59.        /**

  60.         * 设置Dialog是否可以在外部点击消失,默认为true,在外部点击消失会回调接口OnCancelListener

  61.         */

  62.        public Builder setCancelable(boolean cancelable) {

  63.            P.mCancelable = cancelable;

  64.            return this;

  65.        }

  66.        /**

  67.         * 在外部点击消失会回调接口OnCancelListener

  68.         */

  69.        public Builder setOnCancelListener(OnCancelListener onCancelListener) {

  70.            P.mOnCancelListener = onCancelListener;

  71.            return this;

  72.        }

  73.        /**

  74.         * Dialog消失时调用dismiss()方法,进行回调

  75.         */

  76.        public Builder setOnDismissListener(OnDismissListener onDismissListener) {

  77.            P.mOnDismissListener = onDismissListener;

  78.            return this;

  79.        }

  80.        /**

  81.         * 按键点击事件监听,有返回boolean值,默认为false,表示dialog可以消失,如果返回true按返回键dialog不会消失,有特殊请求的可以在该回调方法中处理

  82.         */

  83.        public Builder setOnKeyListener(OnKeyListener onKeyListener) {

  84.            P.mOnKeyListener = onKeyListener;

  85.            return this;

  86.        }

  87. =============================================================================

  88.                        .setOnKeyListener(new DialogInterface.OnKeyListener() {

  89.                            @Override

  90.                            public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {

  91.                                Logger.d("setOnKeyListener keyCode:"+keyCode);

  92.                                return false;

  93.                            }

  94.                        })

  95. =============================================================================


  1.        /**

  2.         * 设置展示单选列表

  3.         */

  4.        public Builder setItems(CharSequence[] items, final OnClickListener listener) {

  5.            P.mItems = items;

  6.            P.mOnClickListener = listener;

  7.            return this;

  8.        }

  9.        /**

  10.         * 也是单选,和上一种有区别

  11.         */

  12.        public Builder setSingleChoiceItems(CharSequence[] items, int checkedItem, final OnClickListener listener) {

  13.            P.mItems = items;

  14.            P.mOnClickListener = listener;

  15.            P.mCheckedItem = checkedItem;

  16.            P.mIsSingleChoice = true;

  17.            return this;

  18.        }

  19.        /**

  20.         * 直接设置一个设配器,Dialog会展示一个listView

  21.         */

  22.        public Builder setAdapter(final ListAdapter adapter, final OnClickListener listener) {

  23.            P.mAdapter = adapter;

  24.            P.mOnClickListener = listener;

  25.            return this;

  26.        }

  27.        /**

  28.         * 直接传入一个游标

  29.         */

  30.        public Builder setCursor(final Cursor cursor, final OnClickListener listener,

  31.                String labelColumn) {

  32.            P.mCursor = cursor;

  33.            P.mLabelColumn = labelColumn;

  34.            P.mOnClickListener = listener;

  35.            return this;

  36.        }

  37.        /**

  38.         * 设置多选数据,和已选项

  39.         */

  40.        public Builder setMultiChoiceItems(CharSequence[] items, boolean[] checkedItems,

  41.                final OnMultiChoiceClickListener listener) {

  42.            P.mItems = items;

  43.            P.mOnCheckboxClickListener = listener;

  44.            P.mCheckedItems = checkedItems;

  45.            P.mIsMultiChoice = true;

  46.            return this;

  47.        }

  48.      


上面的这些方法都是一些属性的设置,每个方法都有注释,方便查看,那将Dialog实例化,并展示出来是怎样的呢,我们接着往下看

  1.        /**

  2.         * Dialog内容部分展示自己的布局界面

  3.         */

  4.        public Builder setView(View view) {

  5.            P.mView = view;

  6.            P.mViewLayoutResId = 0;

  7.            P.mViewSpacingSpecified = false;

  8.            return this;

  9.        }

实例化AlertDialog--create()方法


  1.        public AlertDialog create() {

  2.            // 实例化

  3.            final AlertDialog dialog = new AlertDialog(P.mContext, mTheme);

  4.            //将Builder中变量P的数据,设置到AlertDialog的变量mAlert

  5.            P.apply(dialog.mAlert);

  6.             //还是设置

  7.            dialog.setCancelable(P.mCancelable);

  8.            if (P.mCancelable) {

  9.                dialog.setCanceledOnTouchOutside(true);

  10.            }

  11.            dialog.setOnCancelListener(P.mOnCancelListener);

  12.            dialog.setOnDismissListener(P.mOnDismissListener);

  13.            if (P.mOnKeyListener != null) {

  14.                dialog.setOnKeyListener(P.mOnKeyListener);

  15.            }

  16.            return dialog;

  17.        }

我们看下P.apply()方法的实现:==>就是将P中保存的数据设置给AlertDialog的变量AlertController mAlert;


  1.        public void apply(AlertController dialog) {

  2.            if (mCustomTitleView != null) {

  3.                dialog.setCustomTitle(mCustomTitleView);

  4.            } else {

  5.                if (mTitle != null) {

  6.                    dialog.setTitle(mTitle);

  7.                }

  8.                if (mIcon != null) {

  9.                    dialog.setIcon(mIcon);

  10.                }

  11.                if (mIconId != 0) {

  12.                    dialog.setIcon(mIconId);

  13.                }

  14.                if (mIconAttrId != 0) {

  15.                    dialog.setIcon(dialog.getIconAttributeResId(mIconAttrId));

  16.                }

  17.            }

  18.            if (mMessage != null) {

  19.                dialog.setMessage(mMessage);

  20.            }

  21.            if (mPositiveButtonText != null) {

  22.                dialog.setButton(DialogInterface.BUTTON_POSITIVE, mPositiveButtonText,

  23.                        mPositiveButtonListener, null);

  24.            }

  25.            if (mNegativeButtonText != null) {

  26.                dialog.setButton(DialogInterface.BUTTON_NEGATIVE, mNegativeButtonText,

  27.                        mNegativeButtonListener, null);

  28.            }

  29.            if (mNeutralButtonText != null) {

  30.                dialog.setButton(DialogInterface.BUTTON_NEUTRAL, mNeutralButtonText,

  31.                        mNeutralButtonListener, null);

  32.            }

  33.            //如果设置了mItems数据,表示是单选或者多选列表,则创建一个ListView

  34.            if ((mItems != null) || (mCursor != null) || (mAdapter != null)) {

  35.                createListView(dialog);

  36.            }

  37.            //将mView设置给Dialog

  38.            if (mView != null) {

  39.                if (mViewSpacingSpecified) {

  40.                    dialog.setView(mView, mViewSpacingLeft, mViewSpacingTop, mViewSpacingRight,

  41.                            mViewSpacingBottom);

  42.                } else {

  43.                    dialog.setView(mView);

  44.                }

  45.            } else if (mViewLayoutResId != 0) {

  46.                dialog.setView(mViewLayoutResId);

  47.            }

  48.            /*

  49.            dialog.setCancelable(mCancelable);

  50.            dialog.setOnCancelListener(mOnCancelListener);

  51.            if (mOnKeyListener != null) {

  52.                dialog.setOnKeyListener(mOnKeyListener);

  53.            }

  54.            */

  55.        }

              spacer.gif5b9bbf440001dbb312220697.jpg

         从上图中可以发现,AlertParams是AlertController的内部类,且他们都有相同的属性.这里使用的就是Builder设计模式的变种实现方式

    • 再接着看AlertController的构造函数做了什么处理



  1.    public AlertController(Context context, AppCompatDialog di, Window window) {

  2.         //赋值

  3.        mContext = context;

  4.        mDialog = di;

  5.        mWindow = window;

  6.        mHandler = new ButtonHandler(di);

  7.        final TypedArray a = context.obtainStyledAttributes(null, R.styleable.AlertDialog,

  8.                R.attr.alertDialogStyle, 0);

  9.         //获取属性信息

  10.        mAlertDialogLayout = a.getResourceId(R.styleable.AlertDialog_android_layout, 0);

  11.        mButtonPanelSideLayout = a.getResourceId(R.styleable.AlertDialog_buttonPanelSideLayout, 0);

  12.        mListLayout = a.getResourceId(R.styleable.AlertDialog_listLayout, 0);

  13.        mMultiChoiceItemLayout = a.getResourceId(R.styleable.AlertDialog_multiChoiceItemLayout, 0);

  14.        mSingleChoiceItemLayout = a

  15.                .getResourceId(R.styleable.AlertDialog_singleChoiceItemLayout, 0);

  16.        mListItemLayout = a.getResourceId(R.styleable.AlertDialog_listItemLayout, 0);

  17.        mShowTitle = a.getBoolean(R.styleable.AlertDialog_showTitle, true);

  18.        a.recycle();

  19.        /**

  20.           * 我们使用自定义的Title,所以Dialog不需要设置Window title

  21.          */

  22.        di.supportRequestWindowFeature(Window.FEATURE_NO_TITLE);

  23.    }

数据都准备好了,dialog也实例化了,最后来看Dialog是如何展示的,这里就是用到了Dialog.show()方法===>最最核心方法


  1.    public void show() {

  2.        //Dialog正在展示的逻辑处理,直接return

  3.        if (mShowing) {

  4.            if (mDecor != null) {

  5.                if (mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) {

  6.                    mWindow.invalidatePanelMenu(Window.FEATURE_ACTION_BAR);

  7.                }

  8.                mDecor.setVisibility(View.VISIBLE);

  9.            }

  10.            return;

  11.        }

  12.        mCanceled = false;

  13.         //1.调用Dialog的onCreate()方法

  14.        if (!mCreated) {

  15.            dispatchOnCreate(null);

  16.        } else {

  17.            // Fill the DecorView in on any configuration changes that

  18.            // may have occured while it was removed from the WindowManager.

  19.            final Configuration config = mContext.getResources().getConfiguration();

  20.            mWindow.getDecorView().dispatchConfigurationChanged(config);

  21.        }

  22.         //2.熟悉的onStart()方法

  23.        onStart();

  24.         //3.拿到Window的DecorView,该mWindow是在Dialog的构造函数中初始化得到

  25.        mDecor = mWindow.getDecorView();

  26.        if (mActionBar == null && mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) {

  27.            final ApplicationInfo info = mContext.getApplicationInfo();

  28.            mWindow.setDefaultIcon(info.icon);

  29.            mWindow.setDefaultLogo(info.logo);

  30.            mActionBar = new WindowDecorActionBar(this);

  31.        }

  32.         //4.拿到窗体属性

  33.        WindowManager.LayoutParams l = mWindow.getAttributes();

  34.        if ((l.softInputMode

  35.                & WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) == 0) {

  36.            WindowManager.LayoutParams nl = new WindowManager.LayoutParams();

  37.            nl.copyFrom(l);

  38.            nl.softInputMode |=

  39.                    WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;

  40.            l = nl;

  41.        }

  42.         //5.将mDecor添加到WindowManager中

  43.        mWindowManager.addView(mDecor, l);

  44.        mShowing = true;

  45.         //6.发送消息,展示

  46.        sendShowMessage();

  47.    }

show()方法主要有6个核心方法:

  • 通过dispatchOnCreate()方法来调用AlertDialog的onCreate()方法.(Dialog的onCreate()方法是一个空实现)


    我们发现在AlertDialog的onCreate()方法中调用了mAlert的installContent()方法,根据方法名称,我们可以猜测这个方法的作用是内容视图的处理,我们往里面查看

  1.    @Override

  2.    protected void onCreate(Bundle savedInstanceState) {

  3.        super.onCreate(savedInstanceState);

  4.        mAlert.installContent();

  5.    }

  • AlertController.installContent():


  1.    public void installContent() {

  2.        final int contentView = selectContentView();

  3.        mDialog.setContentView(contentView);

  4.        setupView();

  5.    }

                    在installContent()方法中又调用了三个方法,这三个方法是核心方法,

                    a.调用selectContentView()方法,获取Dialog的contentView,就是AlertDialogLayout的布局


  1.    private int selectContentView() {

  2.        if (mButtonPanelSideLayout == 0) {

  3.            return mAlertDialogLayout;

  4.        }

  5.        if (mButtonPanelLayoutHint == AlertDialog.LAYOUT_HINT_SIDE) {

  6.            return mButtonPanelSideLayout;

  7.        }

  8.        return mAlertDialogLayout;

  9.    }

                    b.dialog.setContentView();将上个方法获取的布局,设置给dialog,其实就是调用了Window的setContentView()方法,这一块和Activity的setContentView()方法的逻辑一样


  1.    public void setContentView(@LayoutRes int layoutResID) {

  2.        mWindow.setContentView(layoutResID);

  3.    }

                    c.最后调用了setupView()方法,就是布局控件的一些展示处理

  • 首先我们看下AlertDialog的默认xml布局文件


  1. <?xml version="1.0" encoding="utf-8"?>

  2. <!--

  3. /* //device/apps/common/res/layout/alert_dialog.xml

  4. **

  5. ** Copyright 2006, The Android Open Source Project

  6. **

  7. ** Licensed under the Apache License, Version 2.0 (the "License");

  8. ** you may not use this file except in compliance with the License.

  9. ** You may obtain a copy of the License at

  10. **

  11. **     http://www.apache.org/licenses/LICENSE-2.0

  12. **

  13. ** Unless required by applicable law or agreed to in writing, software

  14. ** distributed under the License is distributed on an "AS IS" BASIS,

  15. ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

  16. ** See the License for the specific language governing permissions and

  17. ** limitations under the License.

  18. */

  19. -->

  20. <LinearLayout

  21.    xmlns:android="http://schemas.android.com/apk/res/android"

  22.    android:id="@+id/parentPanel"  //xml文件的根view

  23.    android:layout_width="match_parent"

  24.    android:layout_height="wrap_content"

  25.    android:orientation="vertical"

  26.    android:paddingTop="9dip"

  27.    android:paddingBottom="3dip"

  28.    android:paddingStart="3dip"

  29.    android:paddingEnd="1dip">

  30.     //Dialog的顶部控件包括(icon和title)

  31.    <LinearLayout android:id="@+id/topPanel"

  32.        android:layout_width="match_parent"

  33.        android:layout_height="wrap_content"

  34.        android:minHeight="54dip"

  35.        android:orientation="vertical">

  36.        <LinearLayout android:id="@+id/title_template"

  37.            android:layout_width="match_parent"

  38.            android:layout_height="wrap_content"

  39.            android:orientation="horizontal"

  40.            android:gravity="center_vertical"

  41.            android:layout_marginTop="6dip"

  42.            android:layout_marginBottom="9dip"

  43.            android:layout_marginStart="10dip"

  44.            android:layout_marginEnd="10dip">

  45.            <ImageView android:id="@+id/icon"

  46.                android:layout_width="wrap_content"

  47.                android:layout_height="wrap_content"

  48.                android:layout_gravity="top"

  49.                android:paddingTop="6dip"

  50.                android:paddingEnd="10dip"

  51.                android:class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="@drawable/ic_dialog_info" />

  52.            <com.android.internal.widget.DialogTitle

  53.                android:id="@+id/alertTitle"

  54.                style="?android:attr/textAppearanceLarge"

  55.                android:singleLine="true"

  56.                android:ellipsize="end"

  57.                android:layout_width="match_parent"

  58.                android:layout_height="wrap_content"

  59.                android:textAlignment="viewStart" />

  60.        </LinearLayout>


  61.         //分割线

  62.        <ImageView android:id="@+id/titleDivider"

  63.            android:layout_width="match_parent"

  64.            android:layout_height="1dip"

  65.            android:visibility="gone"

  66.            android:scaleType="fitXY"

  67.            android:gravity="fill_horizontal"

  68.            android:class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="@android:drawable/divider_horizontal_dark" />

  69.        <!-- If the client uses a customTitle, it will be added here. -->

  70.    </LinearLayout>

  71.     //默认的内容展示控件,内部使用ScrollView嵌套TextView进行文本展示

  72.    <LinearLayout android:id="@+id/contentPanel"

  73.        android:layout_width="match_parent"

  74.        android:layout_height="wrap_content"

  75.        android:layout_weight="1"

  76.        android:orientation="vertical">

  77.        <ScrollView android:id="@+id/scrollView"

  78.            android:layout_width="match_parent"

  79.            android:layout_height="wrap_content"

  80.            android:paddingTop="2dip"

  81.            android:paddingBottom="12dip"

  82.            android:paddingStart="14dip"

  83.            android:paddingEnd="10dip"

  84.            android:overScrollMode="ifContentScrolls">

  85.            <TextView android:id="@+id/message"

  86.                style="?android:attr/textAppearanceMedium"

  87.                android:layout_width="match_parent"

  88.                android:layout_height="wrap_content"

  89.                android:padding="5dip" />

  90.        </ScrollView>

  91.    </LinearLayout>

  92.     //自定义的布局添加到这个FrameLayout控件中

  93.    <FrameLayout android:id="@+id/customPanel"

  94.        android:layout_width="match_parent"

  95.        android:layout_height="wrap_content"

  96.        android:layout_weight="1">

  97.        <FrameLayout android:id="@+android:id/custom"

  98.            android:layout_width="match_parent"

  99.            android:layout_height="wrap_content"

  100.            android:paddingTop="5dip"

  101.            android:paddingBottom="5dip" />

  102.    </FrameLayout>

  103.     //底部的按钮展示控件

  104.    <LinearLayout android:id="@+id/buttonPanel"

  105.        android:layout_width="match_parent"

  106.        android:layout_height="wrap_content"

  107.        android:minHeight="54dip"

  108.        android:orientation="vertical" >

  109.        <LinearLayout

  110.            style="?android:attr/buttonBarStyle"

  111.            android:layout_width="match_parent"

  112.            android:layout_height="wrap_content"

  113.            android:orientation="horizontal"

  114.            android:paddingTop="4dip"

  115.            android:paddingStart="2dip"

  116.            android:paddingEnd="2dip"

  117.            android:measureWithLargestChild="true">

  118.            <LinearLayout android:id="@+id/leftSpacer"

  119.                android:layout_weight="0.25"

  120.                android:layout_width="0dip"

  121.                android:layout_height="wrap_content"

  122.                android:orientation="horizontal"

  123.                android:visibility="gone" />

  124.            <Button android:id="@+id/button1"

  125.                android:layout_width="0dip"

  126.                android:layout_gravity="start"

  127.                android:layout_weight="1"

  128.                style="?android:attr/buttonBarButtonStyle"

  129.                android:maxLines="2"

  130.                android:layout_height="wrap_content" />

  131.            <Button android:id="@+id/button3"

  132.                android:layout_width="0dip"

  133.                android:layout_gravity="center_horizontal"

  134.                android:layout_weight="1"

  135.                style="?android:attr/buttonBarButtonStyle"

  136.                android:maxLines="2"

  137.                android:layout_height="wrap_content" />

  138.            <Button android:id="@+id/button2"

  139.                android:layout_width="0dip"

  140.                android:layout_gravity="end"

  141.                android:layout_weight="1"

  142.                style="?android:attr/buttonBarButtonStyle"

  143.                android:maxLines="2"

  144.                android:layout_height="wrap_content" />

  145.            <LinearLayout android:id="@+id/rightSpacer"

  146.                android:layout_width="0dip"

  147.                android:layout_weight="0.25"

  148.                android:layout_height="wrap_content"

  149.                android:orientation="horizontal"

  150.                android:visibility="gone" />

  151.        </LinearLayout>

  152.     </LinearLayout>

  153. </LinearLayout>

再看下setupView()的处理逻辑


  1.    private void setupView() {

  2.         //拿到根view

  3.        final View parentPanel = mWindow.findViewById(R.id.parentPanel);

  4.         //默认顶部控件

  5.        final View defaultTopPanel = parentPanel.findViewById(R.id.topPanel);

  6.         //默认内容控件

  7.        final View defaultContentPanel = parentPanel.findViewById(R.id.contentPanel);

  8.         //默认按钮控件

  9.        final View defaultButtonPanel = parentPanel.findViewById(R.id.buttonPanel);

  10.        // 如果有自己设置的布局控件,则调用setupCustomContent()

  11.        final ViewGroup customPanel = (ViewGroup) parentPanel.findViewById(R.id.customPanel);

  12.        setupCustomContent(customPanel);

  13.        final View customTopPanel = customPanel.findViewById(R.id.topPanel);

  14.        final View customContentPanel = customPanel.findViewById(R.id.contentPanel);

  15.        final View customButtonPanel = customPanel.findViewById(R.id.buttonPanel);

  16.        // Resolve the correct panels and remove the defaults, if needed.

  17.        final ViewGroup topPanel = resolvePanel(customTopPanel, defaultTopPanel);

  18.        final ViewGroup contentPanel = resolvePanel(customContentPanel, defaultContentPanel);

  19.        final ViewGroup buttonPanel = resolvePanel(customButtonPanel, defaultButtonPanel);

  20.         //内容区域控件数据设置-底部按钮区域-和顶部title区域数据填充

  21.        setupContent(contentPanel);

  22.        setupButtons(buttonPanel);

  23.        setupTitle(topPanel);

  24.         //展示标示

  25.        final boolean hasCustomPanel = customPanel != null

  26.                && customPanel.getVisibility() != View.GONE;

  27.        final boolean hasTopPanel = topPanel != null

  28.                && topPanel.getVisibility() != View.GONE;

  29.        final boolean hasButtonPanel = buttonPanel != null

  30.                && buttonPanel.getVisibility() != View.GONE;

  31.        // Only display the text spacer if we don't have buttons.

  32.        if (!hasButtonPanel) {

  33.            if (contentPanel != null) {

  34.                final View spacer = contentPanel.findViewById(R.id.textSpacerNoButtons);

  35.                if (spacer != null) {

  36.                    spacer.setVisibility(View.VISIBLE);

  37.                }

  38.            }

  39.        }

  40.        if (hasTopPanel) {

  41.            // Only clip scrolling content to padding if we have a title.

  42.            if (mScrollView != null) {

  43.                mScrollView.setClipToPadding(true);

  44.            }

  45.            // Only show the divider if we have a title.

  46.            View divider = null;

  47.            if (mMessage != null || mListView != null || hasCustomPanel) {

  48.                if (!hasCustomPanel) {

  49.                    divider = topPanel.findViewById(R.id.titleDividerNoCustom);

  50.                }

  51.            }

  52.            if (divider != null) {

  53.                divider.setVisibility(View.VISIBLE);

  54.            }

  55.        } else {

  56.            if (contentPanel != null) {

  57.                final View spacer = contentPanel.findViewById(R.id.textSpacerNoTitle);

  58.                if (spacer != null) {

  59.                    spacer.setVisibility(View.VISIBLE);

  60.                }

  61.            }

  62.        }

  63.        if (mListView instanceof RecycleListView) {

  64.            ((RecycleListView) mListView).setHasDecor(hasTopPanel, hasButtonPanel);

  65.        }

  66.        // Update scroll indicators as needed.

  67.        if (!hasCustomPanel) {

  68.            final View content = mListView != null ? mListView : mScrollView;

  69.            if (content != null) {

  70.                final int indicators = (hasTopPanel ? ViewCompat.SCROLL_INDICATOR_TOP : 0)

  71.                        | (hasButtonPanel ? ViewCompat.SCROLL_INDICATOR_BOTTOM : 0);

  72.                setScrollIndicators(contentPanel, content, indicators,

  73.                        ViewCompat.SCROLL_INDICATOR_TOP | ViewCompat.SCROLL_INDICATOR_BOTTOM);

  74.            }

  75.        }

  76.        final ListView listView = mListView;

  77.        if (listView != null && mAdapter != null) {

  78.            listView.setAdapter(mAdapter);

  79.            final int checkedItem = mCheckedItem;

  80.            if (checkedItem > -1) {

  81.                listView.setItemChecked(checkedItem, true);

  82.                listView.setSelection(checkedItem);

  83.            }

  84.        }

  85.    }

  • 设置Dialog自定义布局的处理逻辑setupCustomContent()


  1.    private void setupCustomContent(ViewGroup customPanel) {

  2.        final View customView;

  3.         //拿到传入进来的自定义布局

  4.        if (mView != null) {

  5.            customView = mView;

  6.        } else if (mViewLayoutResId != 0) {

  7.             //传入的是布局id,使用LayoutInflater.inflate方法拿到填充布局

  8.            final LayoutInflater inflater = LayoutInflater.from(mContext);

  9.            customView = inflater.inflate(mViewLayoutResId, customPanel, false);

  10.        } else {

  11.            customView = null;

  12.        }

  13.        final boolean hasCustomView = customView != null;

  14.        if (!hasCustomView || !canTextInput(customView)) {

  15.            mWindow.setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,

  16.                    WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);

  17.        }

  18.        if (hasCustomView) {

  19.             //拿到custom控件,进行addView()添加

  20.            final FrameLayout custom = (FrameLayout) mWindow.findViewById(R.id.custom);

  21.            custom.addView(customView, new LayoutParams(MATCH_PARENT, MATCH_PARENT));

  22.            if (mViewSpacingSpecified) {

  23.                custom.setPadding(

  24.                        mViewSpacingLeft, mViewSpacingTop, mViewSpacingRight, mViewSpacingBottom);

  25.            }

  26.            if (mListView != null) {

  27.                ((LinearLayout.LayoutParams) customPanel.getLayoutParams()).weight = 0;

  28.            }

  29.        } else {

  30.             //如果没有传入自定义的布局文件,则隐藏

  31.            customPanel.setVisibility(View.GONE);

  32.        }

  33.    }

内容区域数据填充


  1.    private void setupContent(ViewGroup contentPanel) {

  2.         //内容区域外层ScrollView处理

  3.        mScrollView = (NestedScrollView) mWindow.findViewById(R.id.scrollView);

  4.        mScrollView.setFocusable(false);

  5.        mScrollView.setNestedScrollingEnabled(false);

  6.        //文本内容控件TextView

  7.        mMessageView = (TextView) contentPanel.findViewById(android.R.id.message);

  8.        if (mMessageView == null) {

  9.            return;

  10.        }

  11.         //根据文本mMessage判断TextView是否显示

  12.        if (mMessage != null) {

  13.            mMessageView.setText(mMessage);

  14.        } else {

  15.            mMessageView.setVisibility(View.GONE);

  16.            mScrollView.removeView(mMessageView);

  17.             //文本内容是List,则remove调用TextView,添加ListView控件

  18.            if (mListView != null) {

  19.                final ViewGroup scrollParent = (ViewGroup) mScrollView.getParent();

  20.                final int childIndex = scrollParent.indexOfChild(mScrollView);

  21.                scrollParent.removeViewAt(childIndex);

  22.                scrollParent.addView(mListView, childIndex,

  23.                        new LayoutParams(MATCH_PARENT, MATCH_PARENT));

  24.            } else {

  25.                contentPanel.setVisibility(View.GONE);

  26.            }

  27.        }

  28.    }

底部按钮控件展示出来


  1.    private void setupButtons(ViewGroup buttonPanel) {

  2.        int BIT_BUTTON_POSITIVE = 1;

  3.        int BIT_BUTTON_NEGATIVE = 2;

  4.        int BIT_BUTTON_NEUTRAL = 4;

  5.        int whichButtons = 0;

  6.         //拿到button1控件

  7.        mButtonPositive = (Button) buttonPanel.findViewById(android.R.id.button1);

  8.        mButtonPositive.setOnClickListener(mButtonHandler);

  9.         //根据文本信息判断是否展示--其他按钮也是一样的处理逻辑

  10.        if (TextUtils.isEmpty(mButtonPositiveText)) {

  11.            mButtonPositive.setVisibility(View.GONE);

  12.        } else {

  13.            mButtonPositive.setText(mButtonPositiveText);

  14.            mButtonPositive.setVisibility(View.VISIBLE);

  15.            whichButtons = whichButtons | BIT_BUTTON_POSITIVE;

  16.        }

  17.        mButtonNegative = (Button) buttonPanel.findViewById(android.R.id.button2);

  18.        mButtonNegative.setOnClickListener(mButtonHandler);

  19.        if (TextUtils.isEmpty(mButtonNegativeText)) {

  20.            mButtonNegative.setVisibility(View.GONE);

  21.        } else {

  22.            mButtonNegative.setText(mButtonNegativeText);

  23.            mButtonNegative.setVisibility(View.VISIBLE);

  24.            whichButtons = whichButtons | BIT_BUTTON_NEGATIVE;

  25.        }

  26.        mButtonNeutral = (Button) buttonPanel.findViewById(android.R.id.button3);

  27.        mButtonNeutral.setOnClickListener(mButtonHandler);

  28.        if (TextUtils.isEmpty(mButtonNeutralText)) {

  29.            mButtonNeutral.setVisibility(View.GONE);

  30.        } else {

  31.            mButtonNeutral.setText(mButtonNeutralText);

  32.            mButtonNeutral.setVisibility(View.VISIBLE);

  33.            whichButtons = whichButtons | BIT_BUTTON_NEUTRAL;

  34.        }

  35.         //单个按钮的展示逻辑

  36.        if (shouldCenterSingleButton(mContext)) {

  37.            /*

  38.             * If we only have 1 button it should be centered on the layout and

  39.             * expand to fill 50% of the available space.

  40.             */

  41.            if (whichButtons == BIT_BUTTON_POSITIVE) {

  42.                centerButton(mButtonPositive);

  43.            } else if (whichButtons == BIT_BUTTON_NEGATIVE) {

  44.                centerButton(mButtonNegative);

  45.            } else if (whichButtons == BIT_BUTTON_NEUTRAL) {

  46.                centerButton(mButtonNeutral);

  47.            }

  48.        }

  49.        final boolean hasButtons = whichButtons != 0;

  50.        if (!hasButtons) {

  51.            buttonPanel.setVisibility(View.GONE);

  52.        }

  53.    }

顶部Tiltle控件处理逻辑


  1.    private void setupTitle(ViewGroup topPanel) {

  2.        if (mCustomTitleView != null) {

  3.            // 使用自定义的titleView

  4.            LayoutParams lp = new LayoutParams(

  5.                    LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);

  6.            topPanel.addView(mCustomTitleView, 0, lp);

  7.            // Hide the title template

  8.            View titleTemplate = mWindow.findViewById(R.id.title_template);

  9.            titleTemplate.setVisibility(View.GONE);

  10.        } else {

  11.            mIconView = (ImageView) mWindow.findViewById(android.R.id.icon);

  12.            final boolean hasTextTitle = !TextUtils.isEmpty(mTitle);

  13.             //title控件展示逻辑

  14.            if (hasTextTitle && mShowTitle) {

  15.                // Display the title if a title is supplied, else hide it.

  16.                mTitleView = (TextView) mWindow.findViewById(R.id.alertTitle);

  17.                mTitleView.setText(mTitle);

  18.                // Do this last so that if the user has supplied any icons we

  19.                // use them instead of the default ones. If the user has

  20.                // specified 0 then make it disappear.

  21.                if (mIconId != 0) {

  22.                    mIconView.setImageResource(mIconId);

  23.                } else if (mIcon != null) {

  24.                    mIconView.setImageDrawable(mIcon);

  25.                } else {

  26.                    // Apply the padding from the icon to ensure the title is

  27.                    // aligned correctly.

  28.                    mTitleView.setPadding(mIconView.getPaddingLeft(),

  29.                            mIconView.getPaddingTop(),

  30.                            mIconView.getPaddingRight(),

  31.                            mIconView.getPaddingBottom());

  32.                    mIconView.setVisibility(View.GONE);

  33.                }

  34.            } else {

  35.                // Hide the title template

  36.                final View titleTemplate = mWindow.findViewById(R.id.title_template);

  37.                titleTemplate.setVisibility(View.GONE);

  38.                mIconView.setVisibility(View.GONE);

  39.                topPanel.setVisibility(View.GONE);

  40.            }

  41.        }

  42.    }

    • 处理AlertDialog的控件后,接着调用AlertDialog的onStart()方法

    • 最后将Dialog的DecorView添加到WindowManager中,并且显示出来,到这里Dialog就出现在用户的视野中.

    • Handler接收消息


  1.    private static final class ListenersHandler extends Handler {

  2.        private final WeakReference<DialogInterface> mDialog;

  3.        public ListenersHandler(Dialog dialog) {

  4.             //拿到Dialog的实例,软引用

  5.            mDialog = new WeakReference<>(dialog);

  6.        }

  7.        @Override

  8.        public void handleMessage(Message msg) {

  9.            switch (msg.what) {

  10.                case DISMISS:

  11.                    ((OnDismissListener) msg.obj).onDismiss(mDialog.get());

  12.                    break;

  13.                case CANCEL:

  14.                    ((OnCancelListener) msg.obj).onCancel(mDialog.get());

  15.                    break;

  16.                case SHOW:

  17.                    ((OnShowListener) msg.obj).onShow(mDialog.get());

  18.                    break;

  19.            }

  20.        }

  21.    }

总结:经过前面在AlertDialog源码中的杀入杀出,我们了解了AlertDialog是如何实现的,他主要是使用内部类Builder进行数据设置,接着调用create()方法真正创建AlertDialog,并将之间设置的各种数据apply()到控制器AlertController类中,接着是进行展示,调用show()方法,这快的实现原理和Activity的展示逻辑一样,都使用借用WindowManager来进行处理.

原文链接:http://www.apkbus.com/blog-689749-68182.html

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消