美文网首页
AlertDialog源码分析

AlertDialog源码分析

作者: yuLiangC | 来源:发表于2018-12-05 11:42 被阅读15次

    AlertDialog采用了builder建造者模式,今天咱们来分析下它是怎样与view关联起来并进行绘制的。
    先看看大致源码。

    public class AlertDialog extends AppCompatDialog implements DialogInterface {
    
        final AlertController mAlert;
    
        protected AlertDialog(@NonNull Context context) {
            this(context, 0);
        }
    
        protected AlertDialog(@NonNull Context context, @StyleRes int themeResId) {
            super(context, resolveDialogTheme(context, themeResId));
            mAlert = new AlertController(getContext(), this, getWindow());
        }
    
        protected AlertDialog(@NonNull Context context, boolean cancelable,
                @Nullable OnCancelListener cancelListener) {
            this(context, 0);
            setCancelable(cancelable);
            setOnCancelListener(cancelListener);
        }
    
    
        @Override
        public void setTitle(CharSequence title) {
            super.setTitle(title);
            mAlert.setTitle(title);
        }
    
        public void setView(View view) {
            mAlert.setView(view);
        }
    
        public void setView(View view, int viewSpacingLeft, int viewSpacingTop, int viewSpacingRight,
                int viewSpacingBottom) {
            mAlert.setView(view, viewSpacingLeft, viewSpacingTop, viewSpacingRight, viewSpacingBottom);
        }
    
        //设置其他属性...
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            mAlert.installContent();
        }
    
    
        public static class Builder {
            private final AlertController.AlertParams P;
            private final int mTheme;
     
            public Builder(@NonNull Context context) {
                this(context, resolveDialogTheme(context, 0));
            }
    
            public Builder(@NonNull Context context, @StyleRes int themeResId) {
                P = new AlertController.AlertParams(new ContextThemeWrapper(
                        context, resolveDialogTheme(context, themeResId)));
                mTheme = themeResId;
            }
    
           
            public Builder setTitle(@StringRes int titleId) {
                P.mTitle = P.mContext.getText(titleId);
                return this;
            }
    
            public Builder setCustomTitle(@Nullable View customTitleView) {
                P.mCustomTitleView = customTitleView;
                return this;
            }
    
            public Builder setView(int layoutResId) {
                P.mView = null;
                P.mViewLayoutResId = layoutResId;
                P.mViewSpacingSpecified = false;
                return this;
            }
    
            //设置其他属性...
    
            public AlertDialog create() {
                // We can't use Dialog's 3-arg constructor with the createThemeContextWrapper param,
                // so we always have to re-set the theme
                final AlertDialog dialog = new AlertDialog(P.mContext, mTheme);
                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;
            }
    
            public AlertDialog show() {
                final AlertDialog dialog = create();
                dialog.show();
                return dialog;
            }
        }
    
    }
    

    如上,大致构造就是AlertDialog内部有一个Builder内部类,通过它可以设置dialog的各种属性,Builder内部有一个AlertController.AlertParams变量P,负责把外部设置的属性保存下来,然后在create()方法里面将这些属性传递给AlertDialog的AlertController变量mAlert,具体方法是P.apply(dialog.mAlert)。实际将AlertParams就是AlertController的内部类,为什么P的类型不直接设置成AlertController呢?因为从名字上我们可以看出,AlertParams显然只负责保存和设置参数,而AlertController还有更多,所以用了一个内部类。
    看看P.apply(dialog.mAlert)方法。

     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);
                }
           //设置其他属性...
                */
            }
    

    可以看出,这个方法就是将title、icon、layout等各种属性传回给AlertController本身。
    属性设置后,再调用show()函数就可以创建出对话框了。因此我们再看show()方法。

        public void show() {
            if (mShowing) {
                if (mDecor != null) {
                    if (mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) {
                        mWindow.invalidatePanelMenu(Window.FEATURE_ACTION_BAR);
                    }
                    mDecor.setVisibility(View.VISIBLE);
                }
                return;
            }
    
            mCanceled = false;
    
            if (!mCreated) {
                dispatchOnCreate(null);
            } else {
                // Fill the DecorView in on any configuration changes that
                // may have occured while it was removed from the WindowManager.
                final Configuration config = mContext.getResources().getConfiguration();
                mWindow.getDecorView().dispatchConfigurationChanged(config);
            }
    
            onStart();
            mDecor = mWindow.getDecorView();
    
            if (mActionBar == null && mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) {
                final ApplicationInfo info = mContext.getApplicationInfo();
                mWindow.setDefaultIcon(info.icon);
                mWindow.setDefaultLogo(info.logo);
                mActionBar = new WindowDecorActionBar(this);
            }
    
            WindowManager.LayoutParams l = mWindow.getAttributes();
            if ((l.softInputMode
                    & WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) == 0) {
                WindowManager.LayoutParams nl = new WindowManager.LayoutParams();
                nl.copyFrom(l);
                nl.softInputMode |=
                        WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
                l = nl;
            }
    
            mWindowManager.addView(mDecor, l);
            mShowing = true;
    
            sendShowMessage();
        }
    

    show()函数主要做了这几个事情:
    1、先判断当前的显示状态,如果是已经show了,则返回。如果不是,那么通过dispatchOnCreate()调用onCreate函数,然后调用onStart函数。
    2、将decorView添加到windowManager里面,并发送一个显示dialog的消息。
    很显然,这就是一系列生命周期的函数(onCreate()、onStart()),因此视图的创建应该在onCreate()函数里。看下是不是:

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

    父类dialog的onCreate方法是一个空实现,因此看AlertDialog的onCreate方法,而具体还看mAlert.installContent():

        public void installContent() {
            final int contentView = selectContentView();
            mDialog.setContentView(contentView);
            setupView();
        }
    

    setContentView是不是很熟悉,这个和activity的setContentView几乎一模一样,继续追踪我们发现,最终都是调用了window对象的setContentView方法。
    继续看setupView():

      private void setupView() {
            final View parentPanel = mWindow.findViewById(R.id.parentPanel);
            final View defaultTopPanel = parentPanel.findViewById(R.id.topPanel);
            final View defaultContentPanel = parentPanel.findViewById(R.id.contentPanel);
            final View defaultButtonPanel = parentPanel.findViewById(R.id.buttonPanel);
    
            // Install custom content before setting up the title or buttons so
            // that we can handle panel overrides.
            final ViewGroup customPanel = (ViewGroup) parentPanel.findViewById(R.id.customPanel);
            setupCustomContent(customPanel);
    
            final View customTopPanel = customPanel.findViewById(R.id.topPanel);
            final View customContentPanel = customPanel.findViewById(R.id.contentPanel);
            final View customButtonPanel = customPanel.findViewById(R.id.buttonPanel);
    
            // Resolve the correct panels and remove the defaults, if needed.
            final ViewGroup topPanel = resolvePanel(customTopPanel, defaultTopPanel);
            final ViewGroup contentPanel = resolvePanel(customContentPanel, defaultContentPanel);
            final ViewGroup buttonPanel = resolvePanel(customButtonPanel, defaultButtonPanel);
    
            setupContent(contentPanel);
            setupButtons(buttonPanel);
            setupTitle(topPanel);
    
            final boolean hasCustomPanel = customPanel != null
                    && customPanel.getVisibility() != View.GONE;
            final boolean hasTopPanel = topPanel != null
                    && topPanel.getVisibility() != View.GONE;
            final boolean hasButtonPanel = buttonPanel != null
                    && buttonPanel.getVisibility() != View.GONE;
    
            // Only display the text spacer if we don't have buttons.
            if (!hasButtonPanel) {
                if (contentPanel != null) {
                    final View spacer = contentPanel.findViewById(R.id.textSpacerNoButtons);
                    if (spacer != null) {
                        spacer.setVisibility(View.VISIBLE);
                    }
                }
            }
    
            if (hasTopPanel) {
                // Only clip scrolling content to padding if we have a title.
                if (mScrollView != null) {
                    mScrollView.setClipToPadding(true);
                }
    
                // Only show the divider if we have a title.
                View divider = null;
                if (mMessage != null || mListView != null) {
                    divider = topPanel.findViewById(R.id.titleDividerNoCustom);
                }
    
                if (divider != null) {
                    divider.setVisibility(View.VISIBLE);
                }
            } else {
                if (contentPanel != null) {
                    final View spacer = contentPanel.findViewById(R.id.textSpacerNoTitle);
                    if (spacer != null) {
                        spacer.setVisibility(View.VISIBLE);
                    }
                }
            }
    
            if (mListView instanceof RecycleListView) {
                ((RecycleListView) mListView).setHasDecor(hasTopPanel, hasButtonPanel);
            }
    
            // Update scroll indicators as needed.
            if (!hasCustomPanel) {
                final View content = mListView != null ? mListView : mScrollView;
                if (content != null) {
                    final int indicators = (hasTopPanel ? ViewCompat.SCROLL_INDICATOR_TOP : 0)
                            | (hasButtonPanel ? ViewCompat.SCROLL_INDICATOR_BOTTOM : 0);
                    setScrollIndicators(contentPanel, content, indicators,
                            ViewCompat.SCROLL_INDICATOR_TOP | ViewCompat.SCROLL_INDICATOR_BOTTOM);
                }
            }
    
            final ListView listView = mListView;
            if (listView != null && mAdapter != null) {
                listView.setAdapter(mAdapter);
                final int checkedItem = mCheckedItem;
                if (checkedItem > -1) {
                    listView.setItemChecked(checkedItem, true);
                    listView.setSelection(checkedItem);
                }
            }
        }
    

    此方法就是设置了alertDialog的各个区域布局,如标题、按钮、标题icon以及内容等等。当用户调用show函数时,windowManager会将window对象的DecorView添加到用户的窗口上,并显示出来,至此,整个dialog就出现在屏幕上了。

    相关文章

      网友评论

          本文标题:AlertDialog源码分析

          本文链接:https://www.haomeiwen.com/subject/ihrvcqtx.html