在 AlertDialog 中嵌入自訂佈局包含 EditText,點擊確定按鈕時嘗試取得輸入值,卻拋出 NullPointerException。問題在於 findViewById() 找不到對應的視圖元件。
new AlertDialog.Builder(ActSectors.this)
.setTitle(R.string.menu_new_sector)
.setView(inflater.inflate(R.layout.dlg_sector_add, null)) // 直接 inflate 並傳入
.setPositiveButton(R.string.dlg_save, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
// 錯誤:在 Activity 根佈局中尋找,但 EditText 在 Dialog 佈局中
EditText mNewSectorName = (EditText) findViewById(R.id.dlg_edt_nsector);
String val = mNewSectorName.getText().toString(); // NPE
grabVal(val);
}
})
.create().show();
AlertDialog.Builder.setView(View) 接收的是一個已建立的 View 物件。若直接傳入 inflater.inflate(...),雖然視圖會顯示在對話框中,但該 View 物件未被保存參考。後續在 onClick 中呼叫 findViewById() 時,預設在 Activity 的根視圖層級查找,自然找不到對話框內部的 EditText。
private void showAddSectorDlg() {
if (BuildConfig.DEBUG) {
Log.i(Constants.TAG_ACTSECTORS, "showAddSectorDialog() called.");
}
LayoutInflater inflater = getLayoutInflater();
// 關鍵步驟:先 inflate 並保存參考,加上 final 供內部類使用
final View dialogView = inflater.inflate(R.layout.dlg_sector_add, null);
new AlertDialog.Builder(ActSectors.this)
.setTitle(R.string.menu_new_sector)
.setMessage(R.string.dlg_sect_add_msg)
.setView(dialogView) // 使用保存的參考
.setPositiveButton(R.string.dlg_save, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
// 關鍵:在 dialogView 上查找,而非 Activity
EditText mNewSectorName = (EditText) dialogView.findViewById(R.id.dlg_edt_nsector);
String val = mNewSectorName.getText().toString();
if (BuildConfig.DEBUG) {
Log.i(Constants.TAG_ACTSECTORS, "New Sector Name: " + val);
}
grabVal(val);
dialog.dismiss();
}
})
.setNegativeButton(R.string.dlg_cancel, null)
.create()
.show();
}
Google 建議使用 DialogFragment 替代傳統 AlertDialog,可更好處理生命週期與配置變更:
public class AddSectorDialogFragment extends DialogFragment {
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
View view = LayoutInflater.from(getContext()).inflate(R.layout.dlg_sector_add, null);
final EditText editText = view.findViewById(R.id.dlg_edt_nsector);
return new AlertDialog.Builder(getActivity())
.setTitle(R.string.menu_new_sector)
.setView(view)
.setPositiveButton(R.string.dlg_save, (dialog, which) -> {
String val = editText.getText().toString();
((ActSectors) getActivity()).grabVal(val);
})
.setNegativeButton(R.string.dlg_cancel, null)
.create();
}
}
// 使用方式
new AddSectorDialogFragment().show(getSupportFragmentManager(), "add_sector");
| 錯誤寫法 | 正確寫法 | 說明 |
|---|---|---|
.setView(inflater.inflate(...)) | 先存參考 View v = inflater.inflate(...) 再 .setView(v) | 需保存 View 參考以便後續查找子元件 |
findViewById(R.id.xxx) | dialogView.findViewById(R.id.xxx) | 在對話框視圖層級查找,而非 Activity |
未加 final | 加上 final View dialogView | 匿名內部類需捕獲 final 或 effectively final 變數 |
關鍵在於 先 inflate 保存 View 參考,再傳給 setView(),取值時在該 View 上呼叫 findViewById()。現代開發建議改用 DialogFragment 獲得更完整的生命週期管理。