99

我试图防止在重新启动活动时关闭使用警报生成器构建的对话框。

如果我重载 onConfigurationChanged 方法,我可以成功地做到这一点并将布局重置为正确的方向,但我失去了 edittext 的粘性文本功能。因此,在解决对话框问题时,我创建了这个 edittext 问题。

如果我从编辑文本中保存字符串并在 onCofiguration 更改中重新分配它们,它们似乎仍然默认为初始值,而不是在旋转之前输入的值。即使我强制无效似乎也会更新它们。

我真的需要解决对话问题或编辑文本问题。

谢谢您的帮助。

4

13 回答 13

140

现在避免这个问题的最好方法是使用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+

该答案有助于解释其他三个问题(及其答案):

于 2013-03-31T13:19:28.327 回答
48
// 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);
    }
于 2014-12-05T07:51:07.360 回答
5

如果您要更改方向更改的布局,我不会放入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();即可获取该对象。

于 2011-09-26T16:45:05.040 回答
4

只需在 AndroidManifest.xml 中使用您的活动元素添加 android:configChanges="orientation"

例子:

<activity
            android:name=".YourActivity"
            android:configChanges="orientation"
            android:label="@string/app_name"></activity>
于 2016-06-27T05:43:11.067 回答
1

一种非常简单的方法是从该方法创建对话框onCreateDialog()(请参阅下面的注释)。你通过showDialog(). 这样,Android 会为您处理旋转,您不必调用dismiss()onPause()避免 WindowLeak,然后您也不必恢复对话框。从文档:

显示由该活动管理的对话框。第一次为给定 id 调用 onCreateDialog(int, Bundle) 时,将使用相同的 id 进行调用。此后,对话框将自动保存和恢复。

有关更多信息,请参阅Android 文档 showDialog()。希望它可以帮助某人!

注意:如果使用 AlertDialog.Builder,不要调用show()from onCreateDialog()create()而是调用。如果使用 ProgressDialog,只需创建对象,设置您需要的参数并返回它。总之,show()insideonCreateDialog()导致问题,只需创建de Dialog实例并返回它。这应该工作!(我在 onCreate() 中使用 showDialog() 时遇到了问题——实际上没有显示对话框——但如果你在 onResume() 或侦听器回调中使用它,效果很好)。

于 2012-02-05T22:29:02.607 回答
1

这个问题很久以前就回答过了。

然而,这是我为自己使用的简单简单的解决方案。

我为自己做了这个帮助类,所以你也可以在你的应用程序中使用它。

用法是:

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;
                }          
        }
}
于 2016-06-23T13:00:10.693 回答
1

当然,最好的方法是使用 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);
        }
    }
}

您可以阅读我的博客上的完整文章如何防止对话框被解雇?并玩弄源代码

于 2018-01-13T16:49:31.737 回答
1

似乎这仍然是一个问题,即使“做正确的事”和使用DialogFragment等。

Google 问题跟踪器上有一个线程声称这是由于消息队列中留下了旧的关闭消息。提供的解决方法非常简单:

    @Override
    public void onDestroyView() {
        /* Bugfix: https://issuetracker.google.com/issues/36929400 */
        if (getDialog() != null && getRetainInstance())
            getDialog().setDismissMessage(null);

        super.onDestroyView();
    }

令人难以置信的是,在该问题首次报告 7 年后仍然需要这样做。

于 2018-09-17T19:35:57.927 回答
0

您可以将Dialog 的 onSave/onRestore方法与Activity 的 onSave/onRestore方法结合起来,以保持 Dialog 的状态。

注意:此方法适用于那些“简单”的对话框,例如显示警报消息。它不会重现嵌入在 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());
}
于 2015-02-03T23:57:48.663 回答
0

我有一个类似的问题:当屏幕方向改变时,onDismiss即使用户没有关闭对话框,也会调用对话框的侦听器。我能够通过使用onCancel侦听器来解决此问题,当用户按下后退按钮和用户在对话框之外触摸时都会触发该侦听器。

于 2018-01-30T08:28:02.137 回答
0

如果没有任何帮助,并且您需要一个可行的解决方案,您可以安全地进行,每次打开对话框时都将其基本信息保存到活动 ViewModel(并在您关闭对话框时将其从该列表中删除)。此基本信息可能是对话框类型和一些 id(打开此对话框所需的信息)。此 ViewModel 在 Activity 生命周期更改期间不会被销毁。假设用户打开一个对话框以留下对餐厅的引用。所以对话类型是 LeaveReferenceDialog,id 是餐厅 id。打开此对话框时,将此信息保存在可以调用 DialogInfo 的 Object 中,并将此对象添加到 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)
   }
}

在 Activity 的 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,它在 Activity 生命周期中也不会被销毁。因此,当您打开对话框时,您将获得 ViewModel 并像往常一样使用对话框的此 ViewModel 初始化 UI。

于 2020-12-08T11:12:11.880 回答
-1

是的,我同意@Brais Gabin 给出的使用DialogFragment 的解决方案,只是想建议对他给出的解决方案进行一些更改。

在定义扩展 DialogFragment 的自定义类时,我们需要一些接口来最终通过调用对话框的活动或片段来管理操作。但是在 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();
    }

   }

在你调用的活动或片段中——

                   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(屏幕旋转将导致侦听器接口为空)。

如果您觉得这个答案有帮助,请告诉我。谢谢你。

于 2021-05-15T10:34:58.780 回答
-3

只需使用

ConfigurationChanges = Android.Content.PM.ConfigChanges.Orientation | Android.Content.PM.ConfigChanges.ScreenSize

并且应用程序将知道如何处理旋转和屏幕大小。

于 2016-12-02T17:26:47.023 回答