Android 中防止屏幕旋转时对话框消失

发布于 2024-12-06 16:55:36 字数 286 浏览 7 评论 0原文

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

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

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

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

感谢您的帮助。

I am trying to prevent dialogs built with Alert builder from being dismissed when the Activity is restarted.

If I overload the onConfigurationChanged method I can successfully do this and reset the layout to correct orientation but I lose sticky text feature of edittext. So in solving the dialog problem I have created this edittext problem.

If I save the strings from the edittext and reassign them in the onCofiguration change they still seem to default to initial value not what was entered before rotation. Even if I force an invalidate does seem to update them.

I really need to solve either the dialog problem or the edittext problem.

Thanks for the help.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(14

慕烟庭风 2024-12-13 16:55:36

如今避免此问题的最佳方法是使用 DialogFragment

创建一个扩展 DialogFragment 的新类。覆盖 onCreateDialog 并返回旧的 DialogAlertDialog

然后你可以用 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+

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

The best way to avoid this problem nowadays is by using a DialogFragment.

Create a new class which extends DialogFragment. Override onCreateDialog and return your old Dialog or an AlertDialog.

Then you can show it with DialogFragment.show(fragmentManager, tag).

Here's an example with a 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();
    }
}

And in the Activity you call:

new MyDialogFragment().show(getSupportFragmentManager(), "tag"); // or getFragmentManager() in API 11+

This answer helps explain these other three questions (and their answers):

指尖上得阳光 2024-12-13 16:55:36
// 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);
    }
// 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);
    }
孤独难免 2024-12-13 16:55:36

如果您要更改方向更改时的布局,我不会将 android:configChanges="orientation" 放入您的清单中,因为无论如何您都会重新创建视图。

使用这些方法保存 Activity 的当前状态(例如输入的文本、显示的对话框、显示的数据等):

@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
}

@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
    super.onRestoreInstanceState(savedInstanceState);
}

这样,Activity 会再次执行 onCreate,然后调用 onRestoreInstanceState 方法,您可以在其中再次设置 EditText 值。

如果你想存储更复杂的对象,你可以使用

@Override
public Object onRetainNonConfigurationInstance() {
}

这里你可以存储任何对象,在onCreate中你只需调用getLastNonConfigurationInstance();来获取对象。

If you're changing the layout on orientation change I wouldn't put android:configChanges="orientation" in your manifest because you're recreating the views anyway.

Save the current state of your activity (like text entered, shown dialog, data displayed etc.) using these methods:

@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
}

@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
    super.onRestoreInstanceState(savedInstanceState);
}

That way the activity goes through onCreate again and afterwards calls the onRestoreInstanceState method where you can set your EditText value again.

If you want to store more complex Objects you can use

@Override
public Object onRetainNonConfigurationInstance() {
}

Here you can store any object and in onCreate you just have to call getLastNonConfigurationInstance(); to get the Object.

海未深 2024-12-13 16:55:36

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

示例:

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

Just add android:configChanges="orientation" with your activity
element in AndroidManifest.xml

Example:

<activity
            android:name=".YourActivity"
            android:configChanges="orientation"
            android:label="@string/app_name"></activity>
素染倾城色 2024-12-13 16:55:36

一种非常简单的方法是通过 onCreateDialog() 方法创建对话框(请参见下面的注释)。您可以通过 showDialog() 显示它们。这样,Android 就会为您处理旋转,您不必在 onPause() 中调用 dismiss() 来避免 WindowLeak,然后您也不必恢复对话框。来自文档:

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

请参阅 Android 文档 showDialog() 了解更多信息。希望它对某人有帮助!

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

A very easy approach is to create the dialogs from the method onCreateDialog() (see note below). You show them through showDialog(). This way, Android handles the rotation for you and you do not have to call dismiss() in onPause() to avoid a WindowLeak and then you neither have to restore the dialog. From the docs:

Show a dialog managed by this activity. A call to onCreateDialog(int, Bundle) will be made with the same id the first time this is called for a given id. From thereafter, the dialog will be automatically saved and restored.

See Android docs showDialog() for more info. Hope it helps somebody!

Note: If using AlertDialog.Builder, do not call show() from onCreateDialog(), call create() instead. If using ProgressDialog, just create the object, set the parameters you need and return it. In conclusion, show() inside onCreateDialog() causes problems, just create de Dialog instance and return it. This should work! (I have experienced issues using showDialog() from onCreate() -actually not showing the dialog-, but if you use it in onResume() or in a listener callback it works well).

请爱~陌生人 2024-12-13 16:55:36

这个问题很久以前就有答案了。

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

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

用法是:

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

This question was answered a long time ago.

Yet this is non-hacky and simple solution I use for myself.

I did this helper class for myself, so you can use it in your application too.

Usage is:

PersistentDialogFragment.newInstance(
        getBaseContext(),
        RC_REQUEST_CODE,
        R.string.message_text,
        R.string.positive_btn_text,
        R.string.negative_btn_text)
        .show(getSupportFragmentManager(), PersistentDialogFragment.TAG);

Or

 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;
                }          
        }
}
ζ澈沫 2024-12-13 16:55:36

当然,最好的方法是使用 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 时,您可以在 onCreateDialog() 中调用 getContext(),将其转换为 DialogProvider 接口,并通过 onCreateDialog() 来请求特定对话框代码>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 被解雇? 并使用源代码

Definitely, the best approach is by using DialogFragment.

Here is mine solution of wrapper class that helps to prevent different dialogs from being dismissed within one Fragment (or Activity with small refactoring). Also, it helps to avoid massive code refactoring if for some reasons there are a lot of AlertDialogs scattered among the code with slight differences between them in terms of actions, appearance or something else.

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

When it comes to Activity you can invoke getContext() inside onCreateDialog(), cast it to the DialogProvider interface and request a specific dialog by mDialogId. All logic to dealing with a target fragment should be deleted.

Usage from fragment:

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

You can read the complete article on my blog How to prevent Dialog being dismissed? and play with the source code.

始终不够 2024-12-13 16:55:36

看来这仍然是一个问题,即使“一切都正确”并使用 DialogFragment 等。

Google 问题跟踪器 声称这是由于消息队列中遗留有旧的关闭消息所致。提供的解决方法非常简单:

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

        super.onDestroyView();
    }

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

It seems that this is still an issue, even when "doing everything right" and using DialogFragment etc.

There is a thread on Google Issue Tracker which claims that it is due to an old dismiss message being left in the message queue. The provided workaround is quite simple:

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

        super.onDestroyView();
    }

Incredible that this is still needed 7 years after that issue was first reported.

只是在用心讲痛 2024-12-13 16:55:36

您可以将Dialog 的 onSave/onRestore 方法与Activity 的 onSave/onRestore 方法结合起来,以保留 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());
}

You can combine the Dialog's onSave/onRestore methods with the Activity's onSave/onRestore methods to keep the state of the Dialog.

Note: This method works for those "simple" Dialogs, such as displaying an alert message. It won't reproduce the contents of a WebView embedded in a Dialog. If you really want to prevent a complex dialog from dismissal during rotation, try Chung IW's method.

@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());
}
两人的回忆 2024-12-13 16:55:36

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

I had a similar problem: when the screen orientation changed, the dialog's onDismiss listener was called even though the user didn't dismiss the dialog. I was able to work around this by instead using the onCancel listener, which triggered both when the user pressed the back button and when the user touched outside of the dialog.

伤感在游骋 2024-12-13 16:55:36

如果没有任何帮助,并且您需要一个有效的解决方案,您可以安全起见,每次打开对话框时都将其基本信息保存到活动 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)
   }
}

并且在 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,该 ViewModel 在活动生命周期中也不会被销毁。因此,当您打开对话框时,您将获得 ViewModel 并一如既往地使用对话框的此 ViewModel 初始化 UI。

In case nothing helps, and you need a solution that works, you can go on the safe side, and each time you open a dialog save its basic info to the activity ViewModel (and remove it from this list when you dismiss dialog). This basic info could be dialog type and some id (the information you need in order to open this dialog). This ViewModel is not destroyed during changes of Activity lifecycle. Let's say user opens a dialog to leave a reference to a restaurant. So dialog type would be LeaveReferenceDialog and the id would be the restaurant id. When opening this dialog, you save this information in an Object that you can call DialogInfo, and add this object to the ViewModel of the Activity. This information will allow you to reopen the dialog when the activity onResume() is being called:

// On resume in Activity
    override fun onResume() {
            super.onResume()
    
            // Restore dialogs that were open before activity went to background
            restoreDialogs()
        }

Which calls:

    fun restoreDialogs() {
    mainActivityViewModel.setIsRestoringDialogs(true) // lock list in view model

    for (dialogInfo in mainActivityViewModel.openDialogs)
        openDialog(dialogInfo)

    mainActivityViewModel.setIsRestoringDialogs(false) // open lock
}

When IsRestoringDialogs in ViewModel is set to true, dialog info will not be added to the list in view model, and it's important because we're now restoring dialogs which are already in that list. Otherwise, changing the list while using it would cause an exception. So:

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

Then remove dialog info when dismissing it:

// Dismiss dialog
override fun dismissLeaveReferenceDialog(Dialog dialog, id: String) {
   if (dialog?.isAdded()){
      dialog.dismiss()
      mainActivityViewModel.removeOpenDialog(LEAVE_REFERENCE_DIALOG, id)
   }
}

And in the ViewModel of the Activity:

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

You actually reopen all the dialogs that were open before, in the same order. But how do they retain their information? Each dialog has a ViewModel of its own, which is also not destroyed during the activity lifecycle. So when you open the dialog, you get the ViewModel and init the UI using this ViewModel of the dialog as always.

神回复 2024-12-13 16:55:36

好的,经过 1 周的努力,我找到了解决方案:

当然,上述问题的正确解决方案是 DialogFragement 我仍然面临一些问题,因为我想让它可重复使用,有时在任何系统更改(如旋转)后它不会消失,将暗模式更改为亮模式(能够继续出现在屏幕中,但禁用被禁用)等。

此问题将解决以下问题:

  1. 屏幕旋转/系统更改时对话框消失。
  2. 数据将保留在对话框中(对话框标题和其他内容)。
  3. 系统更改/轮换后将能够解散。

注意:我已经在 Kotlin 中完成了此操作。但概念是相同的,请阅读评论,它非常重要


ConfirmDialogWithYesAndNo.kt

class ConfirmDialogWithYesAndNo(private val confirmDialogTitleText:String="Default Confirm Dialog Title",private val confirmDialogMessageText:String="Default Confirm Dialog Message",private val confirmDialogPositiveButtonText:String="Yes",private val confirmDialogNegativeButtonText:String="No",private val confirmDialogPositiveButtonClickListener:()->Unit= {},private val confirmDialogNegativeButtonClickListener:()->Unit= {}): DialogFragment(){
    //Must keep this global
    private lateinit var alertDialog: AlertDialog

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        //Must use this otherwise after system change it will not dismiss and won't have same text which you passed.
        retainInstance = true
    }

    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
        super.onCreateDialog(savedInstanceState)

        //Normal Dialog creation and return it
        val builder = AlertDialog.Builder(requireActivity())
        builder.setTitle(confirmDialogTitleText)
        builder.setMessage(confirmDialogMessageText)
        builder.setIcon(android.R.drawable.ic_dialog_alert)
        builder.setPositiveButton("Yes") { _, _ ->
            //In case you use custom layout
            alertDialog.dismiss()

            confirmDialogPositiveButtonClickListener()
        }
        builder.setNegativeButton("No") { _, _ ->
            //In case you use custom layout button
            confirmDialogNegativeButtonClickListener()
        }
        val alertDialog: AlertDialog = builder.create()
        alertDialog.setCancelable(false)
        alertDialog.show()

        return alertDialog
    }
}

用法

MainActivity.kt:

class MainActivity : AppCompatActivity() {
    ....
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
.....
        butoon.setOnClickListener {
            val confirmDialogWithYesAndNo= ConfirmDialogWithYesAndNo("Hiii T","Hi Message","Yess B","No B",{
                Toast.makeText(this,"Yesss",Toast.LENGTH_LONG).show()
            },{
                Toast.makeText(this,"Noo",Toast.LENGTH_LONG).show()
            })
            confirmDialogWithYesAndNo.showNow(this.supportFragmentManager,"anyrandomstring")
          ...
        }
       ....

    }
}


要联系或检查此代码示例:vaibhavmojidra.github.io/site/

Ok After working on this for 1 Week I found the solution:

Of course the correct solution to above issue is DialogFragement Still there was some issues I was facing as I wanted to make it resusable and sometimes it was not dismissing after any system change like rotating, changing of dark to light mode (Was able to continue appearing in screen but dismissing was disabled) etc..

This problem will resolve issues :

  1. Disappearing of Dialog when screen rotates/System changes.
  2. Data will persistent on dialog (Dialog Title and other stuff).
  3. Will be able to dismiss after system changes/rotate.

Note: I have done this in Kotlin. But concept is same please read comments its very important


ConfirmDialogWithYesAndNo.kt

class ConfirmDialogWithYesAndNo(private val confirmDialogTitleText:String="Default Confirm Dialog Title",private val confirmDialogMessageText:String="Default Confirm Dialog Message",private val confirmDialogPositiveButtonText:String="Yes",private val confirmDialogNegativeButtonText:String="No",private val confirmDialogPositiveButtonClickListener:()->Unit= {},private val confirmDialogNegativeButtonClickListener:()->Unit= {}): DialogFragment(){
    //Must keep this global
    private lateinit var alertDialog: AlertDialog

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        //Must use this otherwise after system change it will not dismiss and won't have same text which you passed.
        retainInstance = true
    }

    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
        super.onCreateDialog(savedInstanceState)

        //Normal Dialog creation and return it
        val builder = AlertDialog.Builder(requireActivity())
        builder.setTitle(confirmDialogTitleText)
        builder.setMessage(confirmDialogMessageText)
        builder.setIcon(android.R.drawable.ic_dialog_alert)
        builder.setPositiveButton("Yes") { _, _ ->
            //In case you use custom layout
            alertDialog.dismiss()

            confirmDialogPositiveButtonClickListener()
        }
        builder.setNegativeButton("No") { _, _ ->
            //In case you use custom layout button
            confirmDialogNegativeButtonClickListener()
        }
        val alertDialog: AlertDialog = builder.create()
        alertDialog.setCancelable(false)
        alertDialog.show()

        return alertDialog
    }
}

Usage

MainActivity.kt:

class MainActivity : AppCompatActivity() {
    ....
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
.....
        butoon.setOnClickListener {
            val confirmDialogWithYesAndNo= ConfirmDialogWithYesAndNo("Hiii T","Hi Message","Yess B","No B",{
                Toast.makeText(this,"Yesss",Toast.LENGTH_LONG).show()
            },{
                Toast.makeText(this,"Noo",Toast.LENGTH_LONG).show()
            })
            confirmDialogWithYesAndNo.showNow(this.supportFragmentManager,"anyrandomstring")
          ...
        }
       ....

    }
}


To contact or to check this code example : vaibhavmojidra.github.io/site/

美羊羊 2024-12-13 16:55:36

是的,我同意@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();
    }

   }

在您调用的 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
                      }
                
                 }

始终确保进行 null 检查在调用其任何方法之前先检查侦听器接口,以防止 NullPointerException(屏幕旋转将导致侦听器接口为空)。

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

Yes, I agree with the solution of using DialogFragment given by @Brais Gabin, just want to suggest some changes to the solution given by him.

While defining our custom class that extends DialogFragment, we require some interfaces to manage the actions ultimately by the activity or the fragment that has invoked the dialog. But setting these listener interfaces in the onAttach(Context context) method may sometimes cause ClassCastException that may crash the app.

So to avoid this exception, we can create a method to set the listener interfaces and call just it after creating the object of the dialog fragment.
Here is a sample code that could help you understand more-

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

   }

And in the Activity or in the Fragment you call-

                   AlertRetryDialog alertRetryDialog = new AlertRetryDialog();
                    alertRetryDialog.setListener(HomeFragment.this);
                    alertRetryDialog.show(getFragmentManager(), "tag");

And implement the methods of your listener interface in your Activity or the Fragment-

              public class YourActivity or YourFragment implements AlertRetryDialog.Listener{ 
                
                  //here's my listener interface's method
                    @Override
                    public void onRetry()
                    {
                     //your code for action
                      }
                
                 }

Always make sure that you do a null check of the listener interfaces before calling any of its methods to prevent NullPointerException (Screen rotation will cause the listener interfaces to be null).

Please do let me know if you find this answer helpful. Thank You.

他是夢罘是命 2024-12-13 16:55:36

只需使用

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

,应用程序就会知道如何处理旋转和屏幕尺寸。

Just use

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

and app will know how to handle rotation and screen size.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文