Google ขอแนะนำให้เราใช้DialogFragment
แทนการที่เรียบง่ายDialog
โดยใช้Fragments API
แต่มันเป็นเรื่องเหลวไหลที่จะใช้แยกDialogFragment
หาง่ายใช่ไม่มีกล่องข้อความยืนยัน แนวปฏิบัติที่ดีที่สุดในกรณีนี้คืออะไร?
Google ขอแนะนำให้เราใช้DialogFragment
แทนการที่เรียบง่ายDialog
โดยใช้Fragments API
แต่มันเป็นเรื่องเหลวไหลที่จะใช้แยกDialogFragment
หาง่ายใช่ไม่มีกล่องข้อความยืนยัน แนวปฏิบัติที่ดีที่สุดในกรณีนี้คืออะไร?
คำตอบ:
ใช่ใช้งานDialogFragment
และในตัวonCreateDialog
คุณสามารถใช้เครื่องมือสร้าง AlertDialog ได้เพื่อสร้างแบบง่าย ๆAlertDialog
ด้วยปุ่มยืนยันใช่ / ไม่ใช่ รหัสไม่มากเลย
ในการจัดการกับเหตุการณ์ในส่วนของคุณจะมีหลายวิธีในการทำมัน แต่ฉันเพียงแค่กำหนดข้อความHandler
ในของฉันFragment
ผ่านมันDialogFragment
ผ่านทางคอนสตรัคเตอร์แล้วส่งข้อความกลับไปยังตัวจัดการส่วนของฉัน อีกหลายวิธีในการทำเช่นนั้น แต่การทำงานต่อไปนี้สำหรับฉัน
ในไดอะล็อกพักข้อความและอินสแตนซ์ของมันใน constructor:
private Message okMessage;
...
okMessage = handler.obtainMessage(MY_MSG_WHAT, MY_MSG_OK);
นำไปใช้onClickListener
ในไดอะล็อกของคุณจากนั้นเรียกตัวจัดการตามความเหมาะสม:
public void onClick(.....
if (which == DialogInterface.BUTTON_POSITIVE) {
final Message toSend = Message.obtain(okMessage);
toSend.sendToTarget();
}
}
แก้ไข
และเป็นMessage
พัสดุที่คุณสามารถบันทึกไว้ในonSaveInstanceState
และเรียกคืนได้
outState.putParcelable("okMessage", okMessage);
จากนั้นใน onCreate
if (savedInstanceState != null) {
okMessage = savedInstanceState.getParcelable("okMessage");
}
target
ซึ่งจะเป็นโมฆะถ้าคุณโหลดจาก Bundle ถ้าเป้าหมายของข้อความนั้นเป็นโมฆะและคุณใช้sendToTarget
คุณจะได้รับ NullPointerException - ไม่ใช่เพราะข้อความนั้นเป็นโมฆะ แต่เพราะเป้าหมายของมันคือ
คุณสามารถสร้างคลาสย่อย DialogFragment ทั่วไปเช่น YesNoDialog และ OkDialog และส่งผ่านหัวเรื่องและข้อความหากคุณใช้กล่องโต้ตอบเป็นจำนวนมากในแอปของคุณ
public class YesNoDialog extends DialogFragment
{
public static final String ARG_TITLE = "YesNoDialog.Title";
public static final String ARG_MESSAGE = "YesNoDialog.Message";
public YesNoDialog()
{
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState)
{
Bundle args = getArguments();
String title = args.getString(ARG_TITLE);
String message = args.getString(ARG_MESSAGE);
return new AlertDialog.Builder(getActivity())
.setTitle(title)
.setMessage(message)
.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener()
{
@Override
public void onClick(DialogInterface dialog, int which)
{
getTargetFragment().onActivityResult(getTargetRequestCode(), Activity.RESULT_OK, null);
}
})
.setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener()
{
@Override
public void onClick(DialogInterface dialog, int which)
{
getTargetFragment().onActivityResult(getTargetRequestCode(), Activity.RESULT_CANCELED, null);
}
})
.create();
}
}
จากนั้นเรียกมันโดยใช้สิ่งต่อไปนี้:
DialogFragment dialog = new YesNoDialog();
Bundle args = new Bundle();
args.putString(YesNoDialog.ARG_TITLE, title);
args.putString(YesNoDialog.ARG_MESSAGE, message);
dialog.setArguments(args);
dialog.setTargetFragment(this, YES_NO_CALL);
dialog.show(getFragmentManager(), "tag");
และจัดการกับผลลัพธ์ที่onActivityResult
ได้
YES_NO_CALL
, getFragmentManager()
และonActivityResult
?
YES_NO_CALL
เป็น int ที่กำหนดเองที่เป็นรหัสคำขอ getFragmentManager()
รับตัวจัดการส่วนสำหรับกิจกรรมและ onActivityResult()
เป็นวิธีการเรียกกลับส่วนวงจรชีวิต
ตั้งแต่เปิดตัว API ระดับ 13 :
showDialogวิธีการจากกิจกรรมเลิก ไม่แนะนำให้เรียกใช้กล่องโต้ตอบที่อื่นในรหัสเนื่องจากคุณจะต้องจัดการกล่องโต้ตอบด้วยตนเอง (เช่นการเปลี่ยนการวางแนว)
การแยกความแตกต่างการจัดเรียงข้อมูล - AlertDialog
พวกเขาแตกต่างกันมาก? จากการอ้างอิงเกี่ยวกับ Android DialogFragment :
DialogFragment เป็นส่วนที่แสดงหน้าต่างโต้ตอบลอยอยู่ด้านบนของหน้าต่างกิจกรรม ส่วนนี้มีวัตถุโต้ตอบซึ่งจะแสดงตามความเหมาะสมขึ้นอยู่กับสถานะของส่วน การควบคุมไดอะล็อก (การตัดสินใจว่าเมื่อใดที่จะแสดง, ซ่อน, ยกเลิกมัน) ควรจะทำผ่าน API ที่นี่ไม่ใช่การโทรโดยตรงบนไดอะล็อก
บันทึกอื่น ๆ
DialogFragment
ฉันจะแนะนำให้ใช้
แน่นอนว่าการสร้างกล่องโต้ตอบ "ใช่ / ไม่ใช่" นั้นค่อนข้างซับซ้อนเนื่องจากควรเป็นงานที่ค่อนข้างเรียบง่าย แต่การสร้างกล่องโต้ตอบที่คล้ายกันซึ่งมีความDialog
ซับซ้อนอย่างน่าประหลาดใจเช่นกัน
(วงจรชีวิตของกิจกรรมทำให้มันซับซ้อน - คุณต้องให้Activity
การจัดการวงจรชีวิตของกล่องโต้ตอบ - และไม่มีทางที่จะผ่านพารามิเตอร์ที่กำหนดเองเช่นข้อความที่กำหนดเองไปActivity.showDialog
หากใช้ระดับ API ต่ำกว่า 8)
สิ่งที่ดีคือคุณมักจะสามารถสร้างสิ่งที่เป็นนามธรรมของคุณเองDialogFragment
ได้อย่างง่ายดาย
String
พารามิเตอร์ เมื่อผู้ใช้คลิก "ใช่" ตัวอย่างเช่นการโต้ตอบเรียกวิธีการของกิจกรรมที่มีพารามิเตอร์ "ตกลง" พารามิเตอร์เหล่านี้ถูกระบุเมื่อแสดงข้อความโต้ตอบเช่น AskDialog.ask ("คุณเห็นด้วยกับข้อตกลงเหล่านี้หรือไม่", "เห็นด้วย", "ไม่เห็นด้วย");
FragmentManager
findFragmentByTag
แต่ใช่มันต้องใช้รหัสที่ยุติธรรม
Fragment
this
และมีคุณของคุณActivity
extends
Interface
แม้ว่าการทำเธรดอย่างระมัดระวังคุณสามารถหยุดการโทรอินเทอร์เฟซเมื่อคุณไม่ต้องการให้พวกเขาหากไม่เห็นพ้องด้วย ไม่แน่ใจว่าสิ่งนี้จะมีอะไรกับหน่วยความจำและปาเก็ตตี้พึ่งพาแบบวงกลมแม้ว่าใครอยากจะพูดสอดใน? ตัวเลือกอื่นคือMessage
/ Handler
แต่คุณยังอาจมีปัญหาการทำงานพร้อมกัน
ในโครงการของฉันฉันใช้AlertDialog.Builder
ไปมากแล้วก่อนที่ฉันจะพบว่ามันมีปัญหา อย่างไรก็ตามฉันไม่ต้องการเปลี่ยนรหัสจำนวนมากในแอพของฉัน นอกจากนี้ที่จริงผมเป็นแฟนของผมผ่านOnClickListeners
ชั้นเรียนที่ไม่ระบุชื่อที่พวกเขามีความจำเป็น (นั่นคือเมื่อใช้setPositiveButton()
,setNegativeButton()
ฯลฯ ) แทนที่จะต้องใช้หลายพันวิธีโทรกลับในการติดต่อสื่อสารระหว่างส่วนโต้ตอบและส่วนของผู้ถือซึ่งสามารถใน ความคิดของฉันนำไปสู่รหัสสับสนและซับซ้อนมาก โดยเฉพาะอย่างยิ่งหากคุณมีกล่องโต้ตอบที่แตกต่างกันหลายรายการในส่วนเดียวและต้องแยกความแตกต่างในการประยุกต์ใช้การติดต่อกลับระหว่างที่แสดงกล่องโต้ตอบ
ดังนั้นฉันจึงรวมวิธีการต่าง ๆ เพื่อสร้างAlertDialogFragment
คลาสตัวช่วยทั่วไปซึ่งสามารถใช้เหมือนกับ AlertDialog
:
สารละลาย
( โปรดทราบว่าฉันกำลังใช้นิพจน์แลมบ์ดาของ Java 8 ในรหัสของฉันดังนั้นคุณอาจต้องเปลี่ยนบางส่วนของรหัสหากคุณยังไม่ได้ใช้นิพจน์แลมบ์ดา )
/**
* Helper class for dialog fragments to show a {@link AlertDialog}. It can be used almost exactly
* like a {@link AlertDialog.Builder}
* <p />
* Creation Date: 22.03.16
*
* @author felix, http://flx-apps.com/
*/
public class AlertDialogFragment extends DialogFragment {
protected FragmentActivity activity;
protected Bundle args;
protected String tag = AlertDialogFragment.class.getSimpleName();
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
activity = getActivity();
args = getArguments();
}
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
Dialog dialog = setDialogDefaults(new AlertDialog.Builder(getActivity())).create();
if (args.containsKey("gravity")) {
dialog.getWindow().getAttributes().gravity = args.getInt("gravity");
}
dialog.setOnShowListener(d -> {
if (dialog != null && dialog.findViewById((android.R.id.message)) != null) {
((TextView) dialog.findViewById(android.R.id.message)).setMovementMethod(LinkMovementMethod.getInstance());
}
});
return dialog;
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return super.onCreateView(inflater, container, savedInstanceState);
}
@Override
public void onDismiss(DialogInterface dialog) {
super.onDismiss(dialog);
if (args.containsKey("onDismissListener")) {
Parcelable onDismissListener = args.getParcelable("onDismissListener");
if (onDismissListener != null && onDismissListener instanceof ParcelableOnDismissListener) {
((ParcelableOnDismissListener) onDismissListener).onDismiss(this);
}
}
}
/**
* Sets default dialog properties by arguments which were set using {@link #builder(FragmentActivity)}
*/
protected AlertDialog.Builder setDialogDefaults(AlertDialog.Builder builder) {
args = getArguments();
activity = getActivity();
if (args.containsKey("title")) {
builder.setTitle(args.getCharSequence("title"));
}
if (args.containsKey("message")) {
CharSequence message = args.getCharSequence("message");
builder.setMessage(message);
}
if (args.containsKey("viewId")) {
builder.setView(getActivity().getLayoutInflater().inflate(args.getInt("viewId"), null));
}
if (args.containsKey("positiveButtonText")) {
builder.setPositiveButton(args.getCharSequence("positiveButtonText"), (dialog, which) -> {
onButtonClicked("positiveButtonListener", which);
});
}
if (args.containsKey("negativeButtonText")) {
builder.setNegativeButton(args.getCharSequence("negativeButtonText"), (dialog, which) -> {
onButtonClicked("negativeButtonListener", which);
});
}
if (args.containsKey("neutralButtonText")) {
builder.setNeutralButton(args.getCharSequence("neutralButtonText"), (dialog, which) -> {
onButtonClicked("neutralButtonListener", which);
});
}
if (args.containsKey("items")) {
builder.setItems(args.getStringArray("items"), (dialog, which) -> {
onButtonClicked("itemClickListener", which);
});
}
// @formatter:off
// FIXME this a pretty hacky workaround: we don't want to show the dialog if onClickListener of one of the dialog's button click listener were lost
// the problem is, that there is no (known) solution for parceling a OnClickListener in the long term (only for state changes like orientation change,
// but not if the Activity was completely lost)
if (
(args.getParcelable("positiveButtonListener") != null && !(args.getParcelable("positiveButtonListener") instanceof ParcelableOnClickListener)) ||
(args.getParcelable("negativeButtonListener") != null && !(args.getParcelable("negativeButtonListener") instanceof ParcelableOnClickListener)) ||
(args.getParcelable("neutralButtonListener") != null && !(args.getParcelable("neutralButtonListener") instanceof ParcelableOnClickListener)) ||
(args.getParcelable("itemClickListener") != null && !(args.getParcelable("itemClickListener") instanceof ParcelableOnClickListener))
) {
new DebugMessage("Forgot onClickListener. Needs to be dismissed.")
.logLevel(DebugMessage.LogLevel.VERBOSE)
.show();
try {
dismissAllowingStateLoss();
} catch (NullPointerException | IllegalStateException ignored) {}
}
// @formatter:on
return builder;
}
public interface OnDismissListener {
void onDismiss(AlertDialogFragment dialogFragment);
}
public interface OnClickListener {
void onClick(AlertDialogFragment dialogFragment, int which);
}
protected void onButtonClicked(String buttonKey, int which) {
ParcelableOnClickListener parcelableOnClickListener = getArguments().getParcelable(buttonKey);
if (parcelableOnClickListener != null) {
parcelableOnClickListener.onClick(this, which);
}
}
// region Convenience Builder Pattern class almost similar to AlertDialog.Builder
// =============================================================================================
public AlertDialogFragment builder(FragmentActivity activity) {
this.activity = activity;
this.args = new Bundle();
return this;
}
public AlertDialogFragment addArguments(Bundle bundle) {
args.putAll(bundle);
return this;
}
public AlertDialogFragment setTitle(int titleStringId) {
return setTitle(activity.getString(titleStringId));
}
public AlertDialogFragment setTitle(CharSequence title) {
args.putCharSequence("title", title);
return this;
}
public AlertDialogFragment setMessage(int messageStringId) {
return setMessage(activity.getString(messageStringId));
}
public AlertDialogFragment setMessage(CharSequence message) {
args.putCharSequence("message", message);
return this;
}
public AlertDialogFragment setPositiveButton(int textStringId, OnClickListener onClickListener) {
return setPositiveButton(activity.getString(textStringId), onClickListener);
}
public AlertDialogFragment setPositiveButton(CharSequence text, AlertDialogFragment.OnClickListener onClickListener) {
args.putCharSequence("positiveButtonText", text);
args.putParcelable("positiveButtonListener", createParcelableOnClickListener(onClickListener));
return this;
}
public AlertDialogFragment setNegativeButton(int textStringId, AlertDialogFragment.OnClickListener onClickListener) {
return setNegativeButton(activity.getString(textStringId), onClickListener);
}
public AlertDialogFragment setNegativeButton(CharSequence text, AlertDialogFragment.OnClickListener onClickListener) {
args.putCharSequence("negativeButtonText", text);
args.putParcelable("negativeButtonListener", createParcelableOnClickListener(onClickListener));
return this;
}
public AlertDialogFragment setNeutralButton(int textStringId, AlertDialogFragment.OnClickListener onClickListener) {
return setNeutralButton(activity.getString(textStringId), onClickListener);
}
public AlertDialogFragment setNeutralButton(CharSequence text, AlertDialogFragment.OnClickListener onClickListener) {
args.putCharSequence("neutralButtonText", text);
args.putParcelable("neutralButtonListener", createParcelableOnClickListener(onClickListener));
return this;
}
public AlertDialogFragment setOnDismissListener(OnDismissListener onDismissListener) {
if (onDismissListener == null) {
return this;
}
Parcelable p = new ParcelableOnDismissListener() {
@Override
public void onDismiss(AlertDialogFragment dialogFragment) {
onDismissListener.onDismiss(dialogFragment);
}
};
args.putParcelable("onDismissListener", p);
return this;
}
public AlertDialogFragment setItems(String[] items, AlertDialogFragment.OnClickListener onClickListener) {
args.putStringArray("items", items);
args.putParcelable("itemClickListener", createParcelableOnClickListener(onClickListener));
return this;
}
public AlertDialogFragment setView(int viewId) {
args.putInt("viewId", viewId);
return this;
}
public AlertDialogFragment setGravity(int gravity) {
args.putInt("gravity", gravity);
return this;
}
public AlertDialogFragment setTag(String tag) {
this.tag = tag;
return this;
}
public AlertDialogFragment create() {
setArguments(args);
return AlertDialogFragment.this;
}
public AlertDialogFragment show() {
create();
try {
super.show(activity.getSupportFragmentManager(), tag);
}
catch (IllegalStateException e1) {
/**
* this whole part is used in order to attempt to show the dialog if an
* {@link IllegalStateException} was thrown (it's kinda comparable to
* {@link FragmentTransaction#commitAllowingStateLoss()}
* So you can remove all those dirty hacks if you are sure that you are always
* properly showing dialogs in the right moments
*/
new DebugMessage("got IllegalStateException attempting to show dialog. trying to hack around.")
.logLevel(DebugMessage.LogLevel.WARN)
.exception(e1)
.show();
try {
Field mShownByMe = DialogFragment.class.getDeclaredField("mShownByMe");
mShownByMe.setAccessible(true);
mShownByMe.set(this, true);
Field mDismissed = DialogFragment.class.getDeclaredField("mDismissed");
mDismissed.setAccessible(true);
mDismissed.set(this, false);
}
catch (Exception e2) {
new DebugMessage("error while showing dialog")
.exception(e2)
.logLevel(DebugMessage.LogLevel.ERROR)
.show();
}
FragmentTransaction transaction = activity.getSupportFragmentManager().beginTransaction();
transaction.add(this, tag);
transaction.commitAllowingStateLoss(); // FIXME hacky and unpredictable workaround
}
return AlertDialogFragment.this;
}
@Override
public int show(FragmentTransaction transaction, String tag) {
throw new NoSuchMethodError("Please use AlertDialogFragment.show()!");
}
@Override
public void show(FragmentManager manager, String tag) {
throw new NoSuchMethodError("Please use AlertDialogFragment.show()!");
}
protected ParcelableOnClickListener createParcelableOnClickListener(AlertDialogFragment.OnClickListener onClickListener) {
if (onClickListener == null) {
return null;
}
return new ParcelableOnClickListener() {
@Override
public void onClick(AlertDialogFragment dialogFragment, int which) {
onClickListener.onClick(dialogFragment, which);
}
};
}
/**
* Parcelable OnClickListener (can be remembered on screen rotation)
*/
public abstract static class ParcelableOnClickListener extends ResultReceiver implements AlertDialogFragment.OnClickListener {
public static final Creator<ResultReceiver> CREATOR = ResultReceiver.CREATOR;
ParcelableOnClickListener() {
super(null);
}
@Override
public abstract void onClick(AlertDialogFragment dialogFragment, int which);
}
/**
* Parcelable OnDismissListener (can be remembered on screen rotation)
*/
public abstract static class ParcelableOnDismissListener extends ResultReceiver implements AlertDialogFragment.OnDismissListener {
public static final Creator<ResultReceiver> CREATOR = ResultReceiver.CREATOR;
ParcelableOnDismissListener() {
super(null);
}
@Override
public abstract void onDismiss(AlertDialogFragment dialogFragment);
}
// =============================================================================================
// endregion
}
การใช้งาน
// showing a normal alert dialog with state loss on configuration changes (like device rotation)
new AlertDialog.Builder(getActivity())
.setTitle("Are you sure? (1)")
.setMessage("Do you really want to do this?")
.setPositiveButton("Yes", (dialog, which) -> Toast.makeText(getContext(), "Yes clicked", Toast.LENGTH_SHORT).show())
.setNegativeButton("Cancel", null)
.show();
// showing a dialog fragment using the helper class with no state loss on configuration changes
new AlertDialogFragment.builder(getActivity())
.setTitle("Are you sure? (2)")
.setMessage("Do you really want to do this?")
.setPositiveButton("Yes", (dialog, which) -> Toast.makeText(getContext(), "Yes clicked", Toast.LENGTH_SHORT).show())
.setNegativeButton("Cancel", null)
.show();
ฉันโพสต์สิ่งนี้ที่นี่ไม่เพียง แต่จะแบ่งปันวิธีการแก้ปัญหาของฉัน แต่ยังเพราะฉันต้องการที่จะขอให้คุณคนสำหรับความคิดเห็นของคุณ: วิธีการนี้ถูกกฎหมายหรือเป็นปัญหาบ้างหรือไม่?
ฉันขอแนะนำคำตอบของ @ ashishduh ให้เข้าใจง่ายขึ้นไหม:
public class AlertDialogFragment extends DialogFragment {
public static final String ARG_TITLE = "AlertDialog.Title";
public static final String ARG_MESSAGE = "AlertDialog.Message";
public static void showAlert(String title, String message, Fragment targetFragment) {
DialogFragment dialog = new AlertDialogFragment();
Bundle args = new Bundle();
args.putString(ARG_TITLE, title);
args.putString(ARG_MESSAGE, message);
dialog.setArguments(args);
dialog.setTargetFragment(targetFragment, 0);
dialog.show(targetFragment.getFragmentManager(), "tag");
}
public AlertDialogFragment() {}
@NonNull
@Override
public AlertDialog onCreateDialog(Bundle savedInstanceState)
{
Bundle args = getArguments();
String title = args.getString(ARG_TITLE, "");
String message = args.getString(ARG_MESSAGE, "");
return new AlertDialog.Builder(getActivity())
.setTitle(title)
.setMessage(message)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener()
{
@Override
public void onClick(DialogInterface dialog, int which)
{
getTargetFragment().onActivityResult(getTargetRequestCode(), Activity.RESULT_OK, null);
}
})
.create();
}
มันไม่จำเป็นสำหรับผู้ใช้ (ของคลาส) เพื่อทำความคุ้นเคยกับ internals ของส่วนประกอบและทำให้การใช้งานง่ายมาก:
AlertDialogFragment.showAlert(title, message, this);
ป.ล. ในกรณีของฉันฉันต้องการกล่องโต้ตอบการแจ้งเตือนที่เรียบง่ายนั่นคือสิ่งที่ฉันสร้างขึ้น คุณสามารถใช้แนวทางนี้กับ Yes / No หรือประเภทอื่น ๆ ที่คุณต้องการ
ใช้ไดอะล็อกสำหรับกล่องโต้ตอบใช่หรือไม่ใช่ง่าย
เมื่อคุณต้องการมุมมองที่ซับซ้อนมากขึ้นซึ่งคุณต้องได้รับวงจรชีวิตเช่น oncreate, ร้องขอสิทธิ์, แทนที่วงจรชีวิตใด ๆ ที่ฉันจะใช้ส่วนโต้ตอบ ดังนั้นคุณจึงแยกสิทธิ์และรหัสอื่น ๆ ที่ไดอะล็อกต้องการใช้งานโดยไม่ต้องสื่อสารกับกิจกรรมการโทร
Dialog
หรือAlertDialog.Builder::create()::show()
จะสร้างโต้ตอบที่หายไปเมื่อคุณหมุนหน้าจอ