AlertDialog源码分析

在日常开发过程中,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源码分析)