防止在Android屏幕旋转时关闭对话框


94

我试图防止在重新启动“活动”时关闭由“警报”构建器构建的对话框。

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

如果我保存了来自edittext的字符串并在onCofiguration更改中将它们重新分配,它们似乎仍默认为初始值,而不是旋转之前输入的初始值。即使我强制失效,似乎也不会更新它们。

我真的需要解决对话框问题或edittext问题。

谢谢您的帮助。


1
如何保存/恢复已编辑EditText的内容?你能显示一些代码吗?
彼得·克尼戈

我发现了问题所在,我忘记了在重置布局后通过ID重新获得视图。
draksia 2011年

Answers:


134

如今,避免此问题的最佳方法是使用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+

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


我的应用程序中有一个按钮可以调用.show(),我必须记住警报对话框的状态,显示/关闭。有没有一种方法可以在不调用.show()的情况下保留对话框?
Alpha Huang

1
它说onAttach现在已弃用。应该怎么做呢?
farahm

3
@faraz_ahmed_kamran,您应该使用onAttach(Context context)android.support.v4.app.DialogFragment。该onAttach方法现在采用context而不是activity作为参数。
Stan Mots,2016年

也许没有必要YesNoListener。看到这个答案
Mygod

46
// 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);
    }

1
对于谁发现我的代码没有用,请在单击之前进行尝试:-)
Chung IW 2014年

6
有效,不知道如何工作,但是有效!干净简单的抽象解决方案,谢谢。
nmvictor

3
我认为这段代码是不好的。doLogout()引用了包含/包含活动的上下文。该活动不能被破坏,这可能导致内存泄漏。我一直在寻找从静态上下文使用AlertDialog的可能性,但现在我确定这是不可能的。我认为结果只能是垃圾。
令人难以置信的

2
看起来好像可行。该对话框保持打开状态,但与新创建的活动或片段没有关系(在每次方向更改时都会重新创建)。因此,您无法做任何需要Context在对话框按钮内部进行的操作OnClickListener
Udo Klimaschewski

2
该代码有效,但根本不建议使用。它泄漏活动参考,这就是对话框可以持久的原因。这是一个非常糟糕的做法,将导致内存泄漏。
Hexise

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();即可获取该对象。


4
OnRetainNonConfigurationInstance()现在已经废弃的医生说:developer.android.com/reference/android/app/... setRetainInstance(boolean retain)应该使用来代替:developer.android.com/reference/android/app/...
ForceMagic

@ForceMagic setRetainInstance完全不同:它是用于Fragments,它不能保证您将保留该实例。
Miha_x64 '17

3

只需在AndroidManifest.xml中将android:configChanges =“ orientation”与您的活动元素一起添加

例:

<activity
            android:name=".YourActivity"
            android:configChanges="orientation"
            android:label="@string/app_name"></activity>

在某些情况下,这可能会导致对话框显示不正确。
山姆

1
android:configChanges =“ orientation | screenSize”注意:如果您的应用程序面向Android 3.2(API级别13)或更高版本,则还应该声明“ screenSize”配置,因为当设备在纵向和横向之间切换时,它也会更改。
tsig

1

一种非常简单的方法是从该方法创建对话框onCreateDialog()(请参见下面的注释)。您通过显示showDialog()。这样,机器人会为你转动,你不必调用dismiss()onPause()避免WindowLeak,然后你既没有恢复对话。从文档:

显示由该活动管理的对话框。首次调用给定ID时,将使用相同的ID调用onCreateDialog(int,Bundle)。从此以后,对话框将自动保存和恢复。

有关更多信息,请参见Android文档showDialog()。希望对您有所帮助!

注意:如果使用AlertDialog.Builder,请不要show()从中onCreateDialog()调用,create()而是改为调用。如果使用ProgressDialog,只需创建对象,设置所需的参数并返回即可。总之,show()内部onCreateDialog()会引起问题,只需创建de Dialog实例并返回它即可。这应该工作!(我在onCreate()中使用showDialog()遇到了问题-实际上没有显示对话框,但是如果在onResume()或侦听器回调中使用它,效果很好)。


在哪种情况下,您需要一些代码?该onCreateDialog()或与生成器一起显示并调用show()吗?
Caumons

我已经做到了..但问题是,现在不赞成onCreateDialog():-\
neteinstein 2012年

好!请记住,大多数Android设备仍可使用2.X版本,因此您仍然可以使用它!看看Android平台版本使用
Caumons

但是,如果没有onCreateDialog,还有什么其他选择呢?
neteinstein '02

1
您可以使用构建器类,例如AlertDialog.Builder。如果在内部使用它onCreateDialog(),则show()返回的结果,而不是使用create()。否则,调用show()返回的AlertDialog并将其存储到Activity的属性中onPause() dismiss(),如果显示,则将其存储在Activity中,以避免WindowLeak。希望能帮助到你!
Caumons

1

这个问题早已被回答。

但这是我自己使用的非hacky简单的解决方案。

我自己做了这个帮助器类,因此您也可以在应用程序中使用它。

用法是:

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;
                }          
        }
}

1

您可以将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());
}

1

当然,最好的方法是使用DialogFragment。

这是包装器类的我的解决方案,它有助于防止在一个Fragment(或具有少量重构的Activity)中消除不同的对话框。而且,如果出于某些原因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);
        }
    }
}

您可以在我的博客上阅读完整的文章。如何防止Dialog被解雇?并播放源代码


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年,仍然需要这样做。



0

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


-3

只需使用

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

然后应用会知道如何处理旋转和屏幕尺寸。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.