DevWiki

AlertDialog 为什么要先调用 show() 才能 getButton()?
在昨天的每日一问中, 我们有这么一行代码:private void showDialog() { Aler...
扫描右侧二维码阅读全文
30
2018/10

AlertDialog 为什么要先调用 show() 才能 getButton()?

在昨天的每日一问中, 我们有这么一行代码:

private void showDialog() {
    AlertDialog.Builder builder = new AlertDialog.Builder(this);
    builder.setTitle("Title")
            .setMessage("Message")
            .setPositiveButton("OK", (dialog, which) -> Toast.makeText(this, "OK", Toast.LENGTH_SHORT).show())
            .setNegativeButton("Cancel", (dialog, which) -> Toast.makeText(this, "Cancel", Toast.LENGTH_SHORT).show())
            .setNeutralButton("Emmm", (dialog, which) -> Toast.makeText(this, "Emmm", Toast.LENGTH_SHORT).show());
    AlertDialog dialog = builder.create();
    dialog.show();
    //必须先调用show才能getButton(), 否则会空指针异常
    dialog.getButton(AlertDialog.BUTTON_NEGATIVE).setTextColor(getResources().getColor(R.color.colorPrimary));
    dialog.getButton(DialogInterface.BUTTON_NEUTRAL).setTextColor(getResources().getColor(R.color.colorPrimaryDark));
    dialog.getButton(DialogInterface.BUTTON_POSITIVE).setTextColor(getResources().getColor(R.color.colorAccent));
}

那么, 为什么要先调用 show() 方法才能 getButton()呢, 我们看下getButton()源码:

//android.support.v7.app.AlertDialog#getButton
public Button getButton(int whichButton) {
    return this.mAlert.getButton(whichButton);
}

//android.support.v7.app.AlertController#getButton
public Button getButton(int whichButton) {
    switch(whichButton) {
    case -3:
        return this.mButtonNeutral;
    case -2:
        return this.mButtonNegative;
    case -1:
        return this.mButtonPositive;
    default:
        return null;
    }
}

//android.support.v7.app.AlertController#mButtonPositive
//android.support.v7.app.AlertController#mButtonNegative
//android.support.v7.app.AlertController#mButtonNeutral
Button mButtonPositive;
Button mButtonNegative;
Button mButtonNeutral;

这里可以看出是获取的 AlertController 的 Button, 这三个Button 什么时候初始化的呢?

//android.support.v7.app.AlertController#setupButtons
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);

    if (TextUtils.isEmpty(mButtonNegativeText)) {
        mButtonNegative.setVisibility(View.GONE);
    } else {
        mButtonNegative.setText(mButtonNegativeText);
        mButtonNegative.setVisibility(View.VISIBLE);

        whichButtons = whichButtons | BIT_BUTTON_NEGATIVE;
    }

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

    if (TextUtils.isEmpty(mButtonNeutralText)) {
        mButtonNeutral.setVisibility(View.GONE);
    } else {
        mButtonNeutral.setText(mButtonNeutralText);
        mButtonNeutral.setVisibility(View.VISIBLE);

        whichButtons = whichButtons | BIT_BUTTON_NEUTRAL;
    }

    if (shouldCenterSingleButton(mContext)) {
        /*
         * If we only have 1 button it should be centered on the layout and
         * expand to fill 50% of the available space.
         */
        if (whichButtons == BIT_BUTTON_POSITIVE) {
            centerButton(mButtonPositive);
        } else if (whichButtons == BIT_BUTTON_NEGATIVE) {
            centerButton(mButtonNegative);
        } else if (whichButtons == BIT_BUTTON_NEUTRAL) {
            centerButton(mButtonNeutral);
        }
    }

    final boolean hasButtons = whichButtons != 0;
    if (!hasButtons) {
        buttonPanel.setVisibility(View.GONE);
    }
}

setupButtons的调用处为:

private void setupView() {
    //...

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

    //....
}

setupView 的调用处为:

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

//android.support.v7.app.AlertDialog#onCreate
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    this.mAlert.installContent();
}

//android.app.Dialog#onCreate
protected void onCreate(Bundle savedInstanceState) {
}

//android.app.Dialog#dispatchOnCreate(Bundle savedInstanceState)
void dispatchOnCreate(Bundle savedInstanceState) {
    if (!mCreated) {
        onCreate(savedInstanceState);
        mCreated = true;
    }
}

此处 dispatchOnCreate有三处调用:

//android.app.Dialog#create()
public void create() {
    if (!mCreated) {
        dispatchOnCreate(null);
    }
}

//android.app.Dialog#show()
public void show() {
    //...
    
    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);
    }
    
    //...
}

//android.app.Dialog#onRestoreInstanceState
public void onRestoreInstanceState(@NonNull Bundle savedInstanceState) {
    final Bundle dialogHierarchyState = savedInstanceState.getBundle(DIALOG_HIERARCHY_TAG);
    if (dialogHierarchyState == null) {
        // dialog has never been shown, or onCreated, nothing to restore.
        return;
    }
    dispatchOnCreate(savedInstanceState);
    mWindow.restoreHierarchyState(dialogHierarchyState);
    if (savedInstanceState.getBoolean(DIALOG_SHOWING_TAG)) {
        show();
    }
}

以上代码说明, 只有在调用

  • Dialog.create()
  • Dialog.show()
  • Dialog.onRestoreInstanceState()

时才会初始化 Button

现在回头看看我们创建 AlertDialog 过程:

  1. 创建 AlertDialog.Builder
  2. 调用buider.create()

我们看下这两步的源码:

//1. 创建 AlertDialog.Builder
public Builder(@NonNull Context context) {
    this(context, AlertDialog.resolveDialogTheme(context, 0));
}

public Builder(@NonNull Context context, @StyleRes int themeResId) {
    this.P = new AlertParams(new ContextThemeWrapper(context, AlertDialog.resolveDialogTheme(context, themeResId)));
    this.mTheme = themeResId;
}
// 没有调用 create()


//2. 调用buider.create()
public AlertDialog create() {
    AlertDialog dialog = new AlertDialog(this.P.mContext, this.mTheme);
    this.P.apply(dialog.mAlert);
    dialog.setCancelable(this.P.mCancelable);
    if (this.P.mCancelable) {
        dialog.setCanceledOnTouchOutside(true);
    }

    dialog.setOnCancelListener(this.P.mOnCancelListener);
    dialog.setOnDismissListener(this.P.mOnDismissListener);
    if (this.P.mOnKeyListener != null) {
        dialog.setOnKeyListener(this.P.mOnKeyListener);
    }

    return dialog;
}
// 此步调用了 AlertDialog的构造方法, 查看构造方法和其父类方法, 并没有调用 create 方法.

那么, 按照最前面创建 AlertDialog 时, 必须先调用 show() 方法初始化 Button, 才能调用 AlertDialog 的 getButton() 方法, 否则会报返回 null, 如果没有判 null 直接设置 Button, 就会引发空指针异常`

Last modification:November 10th, 2018 at 02:38 pm
If you think my article is useful to you, please feel free to appreciate

Leave a Comment