美文网首页
AlertDialog源码分析

AlertDialog源码分析

作者: 肚皮怪_Sun | 来源:发表于2019-06-10 20:11 被阅读0次

    在日常开发过程中,Dialog的使用频率应该是仅次于Activity,不管是出于UI交互原因还是业务需求,页面内经常会需要各种弹窗。这篇文章中并不是讲Dialog的使用也不是如何自定义各式各样弹窗。而是从源码的角度分析Dialog是如何展示在我们的屏幕中,在Android系统中Dialog如何创建视图、如何填充数据,如何添加Window中去。通过这篇文章我算是记录自己对Android视图结构的笔记。

    那就直接开始,从具体的使用实例开始入手:

        public void showDialog() {
            AlertDialog.Builder builder = new AlertDialog.Builder(this);//步骤一
            //步骤二
            builder.setTitle("Title").
                    setMessage("message").
                    setIcon(R.drawable.ic_launcher_background).
                    setPositiveButton("Button1", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                        }
                    }).
                    setNeutralButton("Button2", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                        }
                    }).
                    setNegativeButton("Button3", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                        }
                    }).
                    create(). //步骤三
                    show(); //步骤四
    
        }
    

    通过对上诉的代码的观察,AlertDialog明显是个Builder构建模式,AlertDialog.Builder同时扮演builder、ConcreteBuild、Director的角色,把AlertDialog对象的构建过程和创建过程分开。具体是怎么做的,我把上述分成了四给步骤:

    步骤一 :通过AlertDialog.Builder构建了AlertController.AlertParams(以下简称为P)对象,从命名上可以看出AlertParams——弹窗的各种参数。

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

    步骤二:Builder的一些列setXXX方法,给第一步中构建的P设置参数。

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

    步骤三:Builder的create方法,到这里才真正创建AlertDialog对象,并且在其构造函数中创建了AlertController实例,然后再调用P的apply方法,把变量P中保存的各类参数设置到AlertDialog的AlertController对象中,在Dialog的构造方法中还获取了一个重要的对象WindowManager,在后面我们需要拿WindowManager添加视图到Window 上。

            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);
               //代码...
                return dialog;
            }
    
          AlertDialog(Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
          //在父类Dialog构造函数获得一个重要的对象WindowManager
            super(context, createContextThemeWrapper ? resolveDialogTheme(context, themeResId) : 0,
                    createContextThemeWrapper);
    
            mWindow.alwaysReadCloseOnTouchAttr();
            mAlert = AlertController.create(getContext(), this, getWindow());
          }
    
           public void apply(AlertController dialog) {
                if (mCustomTitleView != null) {
                    dialog.setCustomTitle(mCustomTitleView);
                } else {
                   //代码...
                }
                if (mMessage != null) {
                    dialog.setMessage(mMessage);
                }
                //代码...
            }
    
    

    第四步:调用AlertDialog的show方法显示弹窗,也是我们分析的重点,主要做了如下几个事情;

    1. 通过dispatchOnCreate调用AlertDialog的onCreate方法:
    2. 然后在调用AlertDialog的onStart
    3. 最后将Dialog的DecorView添加到WindowManager
     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) {
                //调用生命周期onCreate
                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
            onStart();
            mDecor = mWindow.getDecorView();
            //代码...
    
            WindowManager.LayoutParams l = mWindow.getAttributes();
            //代码...
            //windowManager.addView 添加视图
            mWindowManager.addView(mDecor, l);
            if (restoreSoftInputMode) {
                l.softInputMode &=
                        ~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
            }
            mShowing = true;
            sendShowMessage();
        }
    
    

    在show方法中执行了Dialog的一系列生命周期方法,按照以往Activity的经验,而AlertDialog的内容视图构建也应该在onCreate方法中,进入AlertDialog 的onCreate方法

      @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            mAlert.installContent();
        }
        public void installContent() {
            int contentView = selectContentView();
            mWindow.setContentView(contentView);
            setupView();
        }
    
        private int selectContentView() {
            if (mButtonPanelSideLayout == 0) {
                return mAlertDialogLayout;
            }
            if (mButtonPanelLayoutHint == AlertDialog.LAYOUT_HINT_SIDE) {
                return mButtonPanelSideLayout;
            }
            return mAlertDialogLayout;
        }
    

    其中主要调用AlertController的installContent方法,在installContent中调用我们熟悉的setContentView方法,这里和Activity的设置视图一模一样,最终都是调用window的setContentView。而这里的contentView,也是就是mAlertDialogLayout弹窗视图布局,其值是在AlerController构造函数中进行的初始化。

    protected AlertController(Context context, DialogInterface di, Window window) {
           mContext = context;
           mDialogInterface = di;
           mWindow = window;
           mHandler = new ButtonHandler(di);
           //代码...
           //alert_dialog.xml  
           final TypedArray a = context.obtainStyledAttributes(null,
                   R.styleable.AlertDialog, R.attr.alertDialogStyle, 0);
    
           mAlertDialogLayout = a.getResourceId(
                   R.styleable.AlertDialog_layout, R.layout.alert_dialog);
           //代码...
    }
    

    在setContentView中主要做的事情是创建一个DecorView,根据Theme,Feature添加了对应的布局文件,再把我们contentView添加到DecorView中。接下来再看setupView方法:

    private void setupView() {
          // 获取初始化内容视图
            final View parentPanel = mWindow.findViewById(R.id.parentPanel);
        //获取title区域
            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);
            //代码...
            a.recycle();
        }
    

    在setupView中就是初始化Alert Dialog布局中的各个部分,如标题栏、按钮栏、内容区域等等,在这个方法调用完后整个Dialog视图内容部分也就完成了。最后再把在通过WindowManager把DecorView添加到添加到Window上,并显示出来,正Dialog就出现在屏幕当中。

    到这里已经把Dialog的主要流程分析完了。

    相关文章

      网友评论

          本文标题:AlertDialog源码分析

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