从源码看 AlertDialog.getButton(DialogInterface.BUTTON_POSITIVE) 为什么是 null

我们在使用 AlertDialog 的时候,如果想改变 POSITIVE_BUTTON 或者 NEGATIVE_BUTTON 的字体颜色、大小时,可能会注意到 AlertDialog.getButton(DialogInterface.BUTTON_POSITIVE) 这个方法,但是当我们在创建了AlertDialog,想要拿到这 Button 的时候会发现返回的结果是个null
尝试之后会发现,在AlertDialogshow()执行过之后,这个Button返回的就不再是null了,此时就可以对Button的属性进行设置:

 final AlertDialog alertDialog = new AlertDialog.Builder(this).setTitle("Title")
                .setMessage("meaasge")
                .setPositiveButton("确定", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                    }
                }).setNegativeButton("取消", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                    }
                }).create();
        alertDialog.show();
        Button button = alertDialog.getButton(DialogInterface.BUTTON_POSITIVE);
        button.setTextColor(Color.parseColor("#00c8aa"));
        button.setTextSize(10);

或者在onShow(DialogInterface dialog)的回调中设置:

alertDialog.setOnShowListener(new DialogInterface.OnShowListener() {
            @Override
            public void onShow(DialogInterface dialog) {
                Button button = alertDialog.getButton(DialogInterface.BUTTON_POSITIVE);
                button.setTextColor(Color.parseColor("#00c8aa"));
                button.setTextSize(10);
            }
        });
alertDialog.show();

接下来我们就看一下为什么AlertDialog.Builder.create()已经构建了一个AlertDialog对象,再去getButton(int which) 会是null

有了AlertDialog实体而getButton(int which)null,那我们就从getButton(int which) 入手看一下源码的处理。点进去getButton(int which) 我们会发现返回的是AlertController类中的成员变量 Button mButtonPositive

//AlertDialog中的getButton(int which)
public Button getButton(int whichButton) {
    return mAlert.getButton(whichButton);
}

//AlertController类中的getButton(int which)
public Button getButton(int whichButton) {
    switch (whichButton) {
        case DialogInterface.BUTTON_POSITIVE:
            return mButtonPositive;
        case DialogInterface.BUTTON_NEGATIVE:
            return mButtonNegative;
        case DialogInterface.BUTTON_NEUTRAL:
            return mButtonNeutral;
        default:
            return null;
        }
}

AlertController,这个类几乎包含了AlertDialog所有的属性。返回的buttonnull,说明这个button还没有初始化,我们来看一下这个button是在哪里被初始化的:

private void setupButtons(ViewGroup buttonPanel) {
        int BIT_BUTTON_POSITIVE = 1;
        int BIT_BUTTON_NEGATIVE = 2;
        int BIT_BUTTON_NEUTRAL = 4;
        int whichButtons = 0;
        mButtonPositive = (Button) buttonPanel.findViewById(android.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) buttonPanel.findViewById(android.R.id.button2);
        mButtonNegative.setOnClickListener(mButtonHandler);
    }

buttonAlertController类的setupButtons(ViewGroup buttonPanel)方法中被初始化,通过追踪,setupButtons(ViewGroup buttonPanel)方法在setupView()中被执行,setupView()installContent()中被执行:

public void installContent() {
        final int contentView = selectContentView();
        mDialog.setContentView(contentView);
        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);
        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);

        final ViewGroup topPanel = resolvePanel(customTopPanel, defaultTopPanel);
        final ViewGroup contentPanel = resolvePanel(customContentPanel, defaultContentPanel);
        final ViewGroup buttonPanel = resolvePanel(customButtonPanel, defaultButtonPanel);

        setupContent(contentPanel);
        setupButtons(buttonPanel);
        setupTitle(topPanel);
}

这个installContent()方法是公有的且在AlertController中没有被执行,那必定是在其它类中被AlertController的实例执行了,AlertController类是AlertDialog的一个辅助类且AlertDialog也持有一个AlertController类的实例,所以我们在AlertController类中找这个installContent()方法:

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

通过查找,在AlertDialog中的onCreate(Bundle savedInstanceState)方法中找到了这个方法。onCreate(Bundle savedInstanceState)方法是重写了父类Dialog的方法,那具体的执行过程就要去Dialog类中找了。
Dialog类中能看到onCreate(Bundle savedInstanceState)dispatchOnCreate(Bundle savedInstanceState)中执行:

void dispatchOnCreate(Bundle savedInstanceState) {
    if (!mCreated) {
        onCreate(savedInstanceState);
        mCreated = true;
    }
}

dispatchOnCreate(Bundle savedInstanceState)方法在Dialog类中被多次执行,而与AlertDialog直接相关的则是show()方法:

public void show() {
        if (!mCreated) {
            dispatchOnCreate(null);
        } else {
            final Configuration config = mContext.getResources().getConfiguration();
            mWindow.getDecorView().dispatchConfigurationChanged(config);
        }

        onStart();
        mDecor = mWindow.getDecorView();
        WindowManager.LayoutParams l = mWindow.getAttributes();

        mWindowManager.addView(mDecor, l);
        mShowing = true;
        sendShowMessage();
    }

简化之后的show()方法,mCreated是一个布尔变量,标记是否被创建,首次调用show()时,mCreatedfalse,会执行dispatchOnCreate(null),就会执行onCreate(Bundle savedInstanceState)。到此,为什么AlertDialog.getButton(DialogInterface.BUTTON_POSITIVE)null,就很清楚了。AlertDialog的逻辑是show()方法中初始化了AlertDialog上面的控件,然后展示出来,所以在show()方法执行之后才能得到button

那我们也来看一下AlertDialog.Builder.create()中都干了什么:

public AlertDialog create() {
            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;
        }

这个方法里面做了实例化一个AlertDialog 和一些参数的传递,在AlertDialog的构造方法中初始化了AlertController mAlert

protected AlertDialog(@NonNull Context context, @StyleRes int themeResId) {
        super(context, resolveDialogTheme(context, themeResId));
        mAlert = new AlertController(getContext(), this, getWindow());
}

AlertController 的构造方法中也只是传参和初始化布局id,并没有做初始化控件的操作。

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

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

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

        mListLayout = a.getResourceId(R.styleable.AlertDialog_listLayout, 0);
        mMultiChoiceItemLayout = a.getResourceId(R.styleable.AlertDialog_multiChoiceItemLayout, 0);
        mSingleChoiceItemLayout = a
                .getResourceId(R.styleable.AlertDialog_singleChoiceItemLayout, 0);
        mListItemLayout = a.getResourceId(R.styleable.AlertDialog_listItemLayout, 0);
        mShowTitle = a.getBoolean(R.styleable.AlertDialog_showTitle, true);

        a.recycle();
        di.supportRequestWindowFeature(Window.FEATURE_NO_TITLE);
    }

所以在AlertDialog.Builder.create()之后是得不到具体的控件实例的。

show()之后我们能得到Button,那我们能不能得到dialog上面其他的控件呢?
我们看AlertController的源码会发现,有多处使用findViewById()这个方法,使用的是mWindow,比如:

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);

        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);

        final ViewGroup topPanel = resolvePanel(customTopPanel, defaultTopPanel);
        final ViewGroup contentPanel = resolvePanel(customContentPanel, defaultContentPanel);
        final ViewGroup buttonPanel = resolvePanel(customButtonPanel, defaultButtonPanel);

        setupContent(contentPanel);
        setupButtons(buttonPanel);
        setupTitle(topPanel);
    }

这个mWindow变量是通过构造方法传进来的,我们在看下AlertDialog的构造方法:

protected AlertDialog(@NonNull Context context, @StyleRes int themeResId) {
        super(context, resolveDialogTheme(context, themeResId));
        mAlert = new AlertController(getContext(), this, getWindow());
}

mWindowDialoggetWindow()返回的对象,查看Dialog的构造方法,会发现这个mWindow是一个PhoneWindow实例:

Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
        mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);

        final Window w = new PhoneWindow(mContext);
        mWindow = w;
        w.setCallback(this);
        w.setOnWindowDismissedCallback(this);
        w.setWindowManager(mWindowManager, null, null);
        w.setGravity(Gravity.CENTER);

        mListenersHandler = new ListenersHandler(this);
    }



    public @Nullable Window getWindow() {
        return mWindow;
    }

源码使用Window.findViewById(int id),那我们应该也能通过获取DialogWindow来获取其他控件。利用这个方法得到AlertDialog上面的控件来设置各种属性,包括titlemessage都可以设置颜色、大小等属性,可以看下我的上篇博客 Builder模式设置AlertDialog字体大小、颜色等属性

回顾一下AlertDialog的创建流程:

首先,通过AlertDialog的内部类Builder,设置了各种属性,然后通过create()方法实例化了AlertDialog,在这个过程中创建了DialogPhoneWindow,再然后是执行show()方法,在这个方法中,执行了onCreate(Bundle savedInstanceState)方法,接着是执行AlertController.installContent(),在这个方法中初始化了各种控件,并设置属性。最后才是show()方法中的WindowManager.addView(PhoneWindow.getDecorView, WindowManager.LayoutParams)展示出来。

看一下AlertController.installContent()这个方法:

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

Dialog类中的setContentView(contentView)方法:

public void setContentView(@LayoutRes int layoutResID) {
    mWindow.setContentView(layoutResID);
}

所以在mDialog.setContentView(contentView)这句话的执行之后才能在后面使用mWindow.findViewById(int id),也就是这样我们才能通过Dialog.getWindow().findViewById(int id)得到具体的控件。

你可能感兴趣的:(源码)