我正在尝试阻止在重新启动活动时关闭使用“警报”构建器构建的对话框。
如果我重载onConfigurationChanged方法,我可以成功执行此操作并重置布局以更正方向但我丢失了edittext的粘性文本功能。所以在解决对话问题时我创建了这个edittext问题。
如果我从edittext中保存字符串并在onCofiguration更改中重新分配它们,它们似乎仍然默认为初始值而不是旋转前输入的值。即使我强制使用无效似乎也会更新它们。
我真的需要解决对话框问题或编辑文本问题。
感谢您的帮助。
答案 0 :(得分:120)
现在避免此问题的最佳方法是使用DialogFragment
。
创建一个扩展DialogFragment
的新类。覆盖onCreateDialog
并返回旧的Dialog
或AlertDialog
。
然后您可以使用DialogFragment.show(fragmentManager, tag)
显示它。
以下是Listener
的示例:
public class MyDialogFragment extends DialogFragment {
public interface YesNoListener {
void onYes();
void onNo();
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
if (!(activity instanceof YesNoListener)) {
throw new ClassCastException(activity.toString() + " must implement YesNoListener");
}
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
return new AlertDialog.Builder(getActivity())
.setTitle(R.string.dialog_my_title)
.setMessage(R.string.dialog_my_message)
.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
((YesNoListener) getActivity()).onYes();
}
})
.setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
((YesNoListener) getActivity()).onNo();
}
})
.create();
}
}
在你打电话的活动中:
new MyDialogFragment().show(getSupportFragmentManager(), "tag"); // or getFragmentManager() in API 11+
这个答案有助于解释其他三个问题(及其答案):
答案 1 :(得分:44)
// Prevent dialog dismiss when orientation changes
private static void doKeepDialog(Dialog dialog){
WindowManager.LayoutParams lp = new WindowManager.LayoutParams();
lp.copyFrom(dialog.getWindow().getAttributes());
lp.width = WindowManager.LayoutParams.WRAP_CONTENT;
lp.height = WindowManager.LayoutParams.WRAP_CONTENT;
dialog.getWindow().setAttributes(lp);
}
public static void doLogout(final Context context){
final AlertDialog dialog = new AlertDialog.Builder(context)
.setIcon(android.R.drawable.ic_dialog_alert)
.setTitle(R.string.titlelogout)
.setMessage(R.string.logoutconfirm)
.setPositiveButton("Yes", new DialogInterface.OnClickListener()
{
@Override
public void onClick(DialogInterface dialog, int which) {
...
}
})
.setNegativeButton("No", null)
.show();
doKeepDialog(dialog);
}
答案 2 :(得分:4)
如果您要更改方向更改的布局,我不会在您的清单中放置android:configChanges="orientation"
,因为您无论如何都在重新创建视图。
使用以下方法保存活动的当前状态(如输入的文字,显示的对话框,显示的数据等):
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
}
这样,活动再次通过onCreate,然后调用onRestoreInstanceState方法,您可以在其中再次设置EditText值。
如果要存储更复杂的对象,可以使用
@Override
public Object onRetainNonConfigurationInstance() {
}
在这里你可以存储任何对象,在onCreate中你只需要调用getLastNonConfigurationInstance();
来获取对象。
答案 3 :(得分:3)
只需添加android:configChanges =" orientation"与您的活动 AndroidManifest.xml中的元素
示例:
<activity
android:name=".YourActivity"
android:configChanges="orientation"
android:label="@string/app_name"></activity>
答案 4 :(得分:1)
一种非常简单的方法是从方法onCreateDialog()
创建对话框(请参阅下面的注释)。您可以通过showDialog()
显示它们。这样,Android就会为您处理轮换,您无需在dismiss()
中调用onPause()
来避免WindowLeak,然后您也不必恢复对话框。来自文档:
显示此活动管理的对话框。对第一次调用给定id时,对onCreateDialog(int,Bundle)的调用将使用相同的id。此后,对话框将自动保存并恢复。
有关详细信息,请参阅Android docs showDialog()。希望它对某人有帮助!
注意:如果使用AlertDialog.Builder,请勿从show()
致电onCreateDialog()
,而是致电create()
。如果使用ProgressDialog,只需创建对象,设置所需的参数并将其返回。总之,show()
内的onCreateDialog()
会导致问题,只需创建de Dialog实例并返回它。这应该工作! (我在onCreate()中使用showDialog()时遇到过问题 - 实际上没有显示对话框 - 但是如果你在onResume()或者监听器回调中使用它,它运行良好。)
答案 5 :(得分:1)
您可以将对话框的onSave / onRestore 方法与 Activity onSave / onRestore 方法结合使用,以保持对话框的状态。
注意:此方法适用于那些&#34;简单&#34;对话框,例如显示警报消息。它不会重现嵌入在Dialog中的WebView的内容。如果你真的想在旋转过程中防止复杂的对话被解雇,请尝试使用Chung IW的方法。
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
myDialog.onRestoreInstanceState(savedInstanceState.getBundle("DIALOG"));
// Put your codes to retrieve the EditText contents and
// assign them to the EditText here.
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
// Put your codes to save the EditText contents and put them
// to the outState Bundle here.
outState.putBundle("DIALOG", myDialog.onSaveInstanceState());
}
答案 6 :(得分:1)
很久以前就回答了这个问题。
然而,这是我自己使用的非hacky 和简单解决方案。
我为自己做了this helper class,所以你也可以在你的应用程序中使用它。
用法是:
PersistentDialogFragment.newInstance(
getBaseContext(),
RC_REQUEST_CODE,
R.string.message_text,
R.string.positive_btn_text,
R.string.negative_btn_text)
.show(getSupportFragmentManager(), PersistentDialogFragment.TAG);
或者
PersistentDialogFragment.newInstance(
getBaseContext(),
RC_EXPLAIN_LOCATION,
"Dialog title",
"Dialog Message",
"Positive Button",
"Negative Button",
false)
.show(getSupportFragmentManager(), PersistentDialogFragment.TAG);
public class ExampleActivity extends Activity implements PersistentDialogListener{
@Override
void onDialogPositiveClicked(int requestCode) {
switch(requestCode) {
case RC_REQUEST_CODE:
break;
}
}
@Override
void onDialogNegativeClicked(int requestCode) {
switch(requestCode) {
case RC_REQUEST_CODE:
break;
}
}
}
答案 7 :(得分:1)
即使“正确地做所有事情”并使用DialogFragment
等,这似乎仍然是一个问题。
Google Issue Tracker上有一个线程,该线程声称这是由于旧的关闭消息保留在消息队列中。提供的解决方法非常简单:
@Override
public void onDestroyView() {
/* Bugfix: https://issuetracker.google.com/issues/36929400 */
if (getDialog() != null && getRetainInstance())
getDialog().setDismissMessage(null);
super.onDestroyView();
}
令人难以置信的是,在首次报告该问题7年后,仍然需要这样做。
答案 8 :(得分:0)
当然,最好的方法是使用DialogFragment。
这是包装类的我的解决方案,有助于防止不同的对话框在一个片段(或具有小重构的活动)中被解雇。此外,如果由于某些原因在代码中散布了大量AlertDialogs
,在操作,外观或其他方面存在细微差别,则有助于避免大量代码重构。
public class DialogWrapper extends DialogFragment {
private static final String ARG_DIALOG_ID = "ARG_DIALOG_ID";
private int mDialogId;
/**
* Display dialog fragment.
* @param invoker The fragment which will serve as {@link AlertDialog} alert dialog provider
* @param dialogId The ID of dialog that should be shown
*/
public static <T extends Fragment & DialogProvider> void show(T invoker, int dialogId) {
Bundle args = new Bundle();
args.putInt(ARG_DIALOG_ID, dialogId);
DialogWrapper dialogWrapper = new DialogWrapper();
dialogWrapper.setArguments(args);
dialogWrapper.setTargetFragment(invoker, 0);
dialogWrapper.show(invoker.getActivity().getSupportFragmentManager(), null);
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mDialogId = getArguments().getInt(ARG_DIALOG_ID);
}
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
return getDialogProvider().getDialog(mDialogId);
}
private DialogProvider getDialogProvider() {
return (DialogProvider) getTargetFragment();
}
public interface DialogProvider {
Dialog getDialog(int dialogId);
}
}
当涉及到Activity时,您可以在getContext()
内调用onCreateDialog()
,将其强制转换为DialogProvider
界面,并通过mDialogId
请求特定对话框。应删除处理目标片段的所有逻辑。
片段的用法:
public class MainFragment extends Fragment implements DialogWrapper.DialogProvider {
private static final int ID_CONFIRMATION_DIALOG = 0;
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
Button btnHello = (Button) view.findViewById(R.id.btnConfirm);
btnHello.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
DialogWrapper.show(MainFragment.this, ID_CONFIRMATION_DIALOG);
}
});
}
@Override
public Dialog getDialog(int dialogId) {
switch (dialogId) {
case ID_CONFIRMATION_DIALOG:
return createConfirmationDialog(); //Your AlertDialog
default:
throw new IllegalArgumentException("Unknown dialog id: " + dialogId);
}
}
}
您可以阅读我的博客How to prevent Dialog being dismissed?上的完整文章并使用source code进行播放。
答案 9 :(得分:0)
我遇到了类似的问题:当屏幕方向改变时,即使用户没有关闭对话框,也会调用对话框的onDismiss
监听器。我可以通过使用onCancel
监听器来解决这个问题,该监听器在用户按下后退按钮和用户在对话框外触摸时触发。
答案 10 :(得分:0)
如果没有任何帮助,并且您需要一个可行的解决方案,则可以放心一点,每次打开对话框时,请将其基本信息保存到活动ViewModel中(并在关闭对话框时将其从此列表中删除) )。此基本信息可以是对话框类型和某些ID(打开此对话框所需的信息)。在Activity生命周期更改期间,不会销毁此ViewModel。假设用户打开一个对话框以留下对餐厅的引用。因此,对话框类型为LeaveReferenceDialog,而id为餐厅ID。打开此对话框时,将此信息保存在一个可以调用DialogInfo的对象中,然后将此对象添加到Activity的ViewModel中。该信息将允许您在调用活动onResume()时重新打开对话框:
// On resume in Activity
override fun onResume() {
super.onResume()
// Restore dialogs that were open before activity went to background
restoreDialogs()
}
哪个电话:
fun restoreDialogs() {
mainActivityViewModel.setIsRestoringDialogs(true) // lock list in view model
for (dialogInfo in mainActivityViewModel.openDialogs)
openDialog(dialogInfo)
mainActivityViewModel.setIsRestoringDialogs(false) // open lock
}
当ViewModel中的IsRestoringDialogs设置为true时,对话框信息将不会添加到视图模型中的列表中,这一点很重要,因为我们现在正在还原该列表中已经存在的对话框。否则,在使用列表时更改列表将导致异常。所以:
// Create new dialog
override fun openLeaveReferenceDialog(restaurantId: String) {
var dialog = LeaveReferenceDialog()
// Add id to dialog in bundle
val bundle = Bundle()
bundle.putString(Constants.RESTAURANT_ID, restaurantId)
dialog.arguments = bundle
dialog.show(supportFragmentManager, "")
// Add dialog info to list of open dialogs
addOpenDialogInfo(DialogInfo(LEAVE_REFERENCE_DIALOG, restaurantId))
}
然后在关闭对话框时删除对话框信息:
// Dismiss dialog
override fun dismissLeaveReferenceDialog(Dialog dialog, id: String) {
if (dialog?.isAdded()){
dialog.dismiss()
mainActivityViewModel.removeOpenDialog(LEAVE_REFERENCE_DIALOG, id)
}
}
在活动的ViewModel中:
fun addOpenDialogInfo(dialogInfo: DialogInfo){
if (!isRestoringDialogs){
val dialogWasInList = removeOpenDialog(dialogInfo.type, dialogInfo.id)
openDialogs.add(dialogInfo)
}
}
fun removeOpenDialog(type: Int, id: String) {
if (!isRestoringDialogs)
for (dialogInfo in openDialogs)
if (dialogInfo.type == type && dialogInfo.id == id)
openDialogs.remove(dialogInfo)
}
实际上,您以相同的顺序重新打开了之前打开的所有对话框。但是他们如何保留自己的信息?每个对话框都有自己的ViewModel,在活动生命周期中也不会销毁它。因此,当您打开对话框时,将一如既往地获得ViewModel并使用该对话框的ViewModel来初始化UI。
答案 11 :(得分:-1)
只需使用
ConfigurationChanges = Android.Content.PM.ConfigChanges.Orientation | Android.Content.PM.ConfigChanges.ScreenSize
和app将知道如何处理旋转和屏幕尺寸。
答案 12 :(得分:-1)
是的,我同意@Brais Gabin 提供的使用 DialogFragment 的解决方案,只是想对他提供的解决方案提出一些更改建议。
在定义扩展 DialogFragment 的自定义类时,我们需要一些接口来最终通过 Activity 或调用对话框的片段来管理操作。但是在 onAttach(Context context) 方法中设置这些侦听器接口有时可能会导致 ClassCastException 导致应用程序崩溃。
所以为了避免这个异常,我们可以创建一个方法来设置监听器接口,并在创建对话框片段的对象后调用它。 这是一个示例代码,可以帮助您了解更多-
AlertRetryDialog.class
public class AlertRetryDialog extends DialogFragment {
public interface Listener{
void onRetry();
}
Listener listener;
public void setListener(Listener listener)
{
this.listener=listener;
}
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
AlertDialog.Builder builder=new AlertDialog.Builder(getActivity());
builder.setMessage("Please Check Your Network Connection").setPositiveButton("Retry", new
DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
//Screen rotation will cause the listener to be null
//Always do a null check of your interface listener before calling its method
if(listener!=null&&listener instanceof HomeFragment)
listener.onRetry();
}
}).setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
});
return builder.create();
}
}
在您调用的 Activity 或 Fragment 中-
AlertRetryDialog alertRetryDialog = new AlertRetryDialog();
alertRetryDialog.setListener(HomeFragment.this);
alertRetryDialog.show(getFragmentManager(), "tag");
并在您的 Activity 或 Fragment 中实现您的侦听器接口的方法-
public class YourActivity or YourFragment implements AlertRetryDialog.Listener{
//here's my listener interface's method
@Override
public void onRetry()
{
//your code for action
}
}
始终确保在调用侦听器接口的任何方法之前对其进行空检查以防止 NullPointerException(屏幕旋转将导致侦听器接口为空)。
如果您觉得这个答案有帮助,请告诉我。谢谢。