Ngăn loại bỏ hộp thoại khi xoay màn hình trong Android


94

Tôi đang cố gắng ngăn các hộp thoại được tạo bằng Trình tạo cảnh báo bị loại bỏ khi Hoạt động được khởi động lại.

Nếu tôi quá tải phương thức onConfigurationChanged, tôi có thể thực hiện thành công việc này và đặt lại bố cục để đúng hướng nhưng tôi mất tính năng văn bản cố định của edittext. Vì vậy, trong việc giải quyết vấn đề hộp thoại, tôi đã tạo ra vấn đề edittext này.

Nếu tôi lưu các chuỗi từ edittext và gán lại chúng trong thay đổi onCofiguration, chúng dường như vẫn được đặt mặc định thành giá trị ban đầu chứ không phải những gì đã được nhập trước khi xoay. Ngay cả khi tôi buộc vô hiệu hóa dường như vẫn cập nhật chúng.

Tôi thực sự cần giải quyết vấn đề hộp thoại hoặc vấn đề văn bản chỉnh sửa.

Cảm ơn đã giúp đỡ.


1
Làm cách nào để lưu / khôi phục nội dung của EditText đã chỉnh sửa? Bạn có thể hiển thị một số mã?
Peter Knego

Tôi đã tìm ra vấn đề với điều đó, tôi đã quên lấy lại chế độ xem bởi Id sau khi đặt lại bố cục.
draksia

Câu trả lời:


134

Cách tốt nhất để tránh vấn đề này hiện nay là sử dụng a DialogFragment.

Tạo một lớp mới mở rộng DialogFragment. Ghi đè onCreateDialogvà trả lại cũ của bạn Dialoghoặc một AlertDialog.

Sau đó, bạn có thể hiển thị nó với DialogFragment.show(fragmentManager, tag).

Đây là một ví dụ với 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();
    }
}

Và trong Hoạt động bạn gọi:

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

Câu trả lời này giúp giải thích ba câu hỏi khác (và câu trả lời của chúng):


Có một nút trong ứng dụng của tôi để gọi .show (), tôi phải nhớ trạng thái của hộp thoại cảnh báo, hiển thị / loại bỏ. Có cách nào để giữ hộp thoại mà không gọi .show () không?
Alpha Huang,

1
Nó nói rằng onAttachnó không được dùng nữa. Thay vào đó nên làm gì?
farahm

3
@faraz_ahmed_kamran, bạn nên sử dụng onAttach(Context context)android.support.v4.app.DialogFragment. Các onAttachphương pháp có contextthay vì activitynhư một tham số bây giờ.
Stan Mots

Có lẽ không cần thiết YesNoListener. Hãy xem câu trả lời này .
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
Ai thấy mã của tôi không hữu ích, hãy thử nó trước khi nhấp :-)
Chung IW

6
Hoạt động, không biết làm thế nào nhưng nó hoạt động! Làm sạch giải pháp đơn giản và trừu tượng, cảm ơn.
nmvictor

3
Tôi nghĩ rằng mã này là xấu. doLogout () có một tham chiếu đến ngữ cảnh là / chứa hoạt động. Không thể phá hủy hoạt động có thể gây rò rỉ bộ nhớ. Tôi đang tìm kiếm khả năng sử dụng AlertDialog từ ngữ cảnh tĩnh nhưng bây giờ tôi chắc chắn rằng điều đó là không thể. Tôi nghĩ kết quả chỉ có thể là rác.
Điều đáng kinh ngạc ngày

2
Nó chỉ trông giống như nó hoạt động. Hộp thoại vẫn mở, nhưng nó không có kết nối với hoạt động hoặc phân đoạn mới được tạo (nó được tạo mới sau mỗi lần thay đổi hướng). Vì vậy, bạn không thể làm bất cứ điều gì yêu cầu Contextbên trong các nút hộp thoại OnClickListener.
Udo Klimaschewski

2
Mã này hoạt động nhưng không được khuyến nghị. Nó làm rò rỉ tham chiếu hoạt động, đó là lý do tại sao hộp thoại có thể liên tục. Đây là một thực hành rất xấu sẽ dẫn đến rò rỉ bộ nhớ.
Hexise

4

Nếu bạn đang thay đổi bố cục khi thay đổi hướng, tôi sẽ không đưa android:configChanges="orientation"vào tệp kê khai của bạn vì bạn vẫn đang tạo lại các chế độ xem.

Lưu trạng thái hiện tại của hoạt động của bạn (như văn bản đã nhập, hộp thoại được hiển thị, dữ liệu được hiển thị, v.v.) bằng các phương pháp sau:

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

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

Bằng cách đó, hoạt động sẽ diễn ra lại trên onCreate và sau đó gọi phương thức onRestoreInstanceState nơi bạn có thể đặt lại giá trị EditText của mình.

Nếu bạn muốn lưu trữ các Đối tượng phức tạp hơn, bạn có thể sử dụng

@Override
public Object onRetainNonConfigurationInstance() {
}

Tại đây bạn có thể lưu trữ bất kỳ đối tượng nào và trong onCreate, bạn chỉ cần gọi getLastNonConfigurationInstance();để lấy Đối tượng.


4
OnRetainNonConfigurationInstance()hiện không được dùng nữa vì Doc cho biết: developer.android.com/reference/android/app/… setRetainInstance(boolean retain) nên được sử dụng thay thế: developer.android.com/reference/android/app/…
ForceMagic

@ForceMagic setRetainInstancethì hoàn toàn khác: nó dành cho Fragment và nó không đảm bảo cho bạn rằng phiên bản đó sẽ được giữ lại.
Miha_x64

3

Chỉ cần thêm android: configChanges = "direction" với phần tử hoạt động của bạn trong AndroidManifest.xml

Thí dụ:

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

Điều này có thể khiến hộp thoại hiển thị không chính xác trong một số trường hợp.
Sam

1
android: configChanges = "direction | screenSize" Lưu ý: Nếu ứng dụng của bạn nhắm mục tiêu Android 3.2 (API cấp 13) trở lên, thì bạn cũng nên khai báo cấu hình "screenSize", vì nó cũng thay đổi khi thiết bị chuyển đổi giữa hướng dọc và ngang.
tsig

1

Một cách tiếp cận rất dễ dàng là tạo các hộp thoại từ phương pháp onCreateDialog()(xem ghi chú bên dưới). Bạn cho họ thấy thông qua showDialog(). Bằng cách này, Android xử lý luân chuyển cho bạn và bạn không cần phải gọi dismiss()trong onPause()để tránh một WindowLeak và sau đó bạn không phải khôi phục lại hộp thoại. Từ các tài liệu:

Hiển thị hộp thoại được quản lý bởi hoạt động này. Một lệnh gọi đến onCreateDialog (int, Bundle) sẽ được thực hiện với cùng một id lần đầu tiên điều này được gọi cho một id nhất định. Từ đó, hộp thoại sẽ tự động được lưu và khôi phục.

Xem tài liệu Android showDialog () để biết thêm thông tin. Hy vọng nó sẽ giúp ai đó!

Lưu ý: Nếu sử dụng AlertDialog.Builder, không gọi show()từ onCreateDialog(), create()thay vào đó hãy gọi . Nếu sử dụng ProgressDialog, chỉ cần tạo đối tượng, thiết lập các tham số bạn cần và trả về nó. Tóm lại, show()bên trong onCreateDialog()gây ra vấn đề, chỉ cần tạo cá thể de Dialog và trả lại nó. Điều này sẽ hoạt động! (Tôi đã gặp sự cố khi sử dụng showDialog () từ onCreate () - thực tế là không hiển thị hộp thoại-, nhưng nếu bạn sử dụng nó trong onResume () hoặc trong một trình nghe gọi lại thì nó hoạt động tốt).


Đối với trường hợp nào bạn sẽ cần một số mã? OnCreateDialog () hoặc hiển thị nó với trình tạo và gọi show () cho nó?
Caumons

Tôi đã làm được điều đó .. nhưng vấn đề là onCreateDialog () hiện không được dùng nữa: - \
neteinstein

ĐỒNG Ý! Hãy nhớ rằng hầu hết các thiết bị Android vẫn hoạt động với phiên bản 2.X, vì vậy bạn vẫn có thể sử dụng nó! Xem qua việc sử dụng các phiên bản nền tảng Android
Caumons

Tuy nhiên, tùy chọn khác là gì nếu không phải là onCreateDialog?
neteinstein

1
Bạn có thể sử dụng các lớp xây dựng, ví dụ AlertDialog.Builder. Nếu bạn sử dụng nó bên trong onCreateDialog(), thay vì sử dụng show()trả về kết quả của create(). Nếu không, hãy gọi show()và lưu trữ AlertDialog được trả về thành một thuộc tính của Activity và trong onPause() dismiss()đó nếu hiển thị để tránh WindowLeak. Hy vọng nó giúp!
Caumons

1

Câu hỏi này đã được trả lời cách đây rất lâu.

Tuy nhiên, đây là giải pháp không hackđơn giản mà tôi sử dụng cho chính mình.

Tôi đã tạo lớp trợ giúp này cho chính mình, vì vậy bạn cũng có thể sử dụng nó trong ứng dụng của mình.

Cách sử dụng là:

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

Hoặc là

 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

Bạn có thể kết hợp phương thức onSave / onRestore của Hộp thoại với phương thức onSave / onRestore của Activity để giữ trạng thái của Hộp thoại.

Lưu ý: Phương pháp này hoạt động đối với những Hộp thoại "đơn giản", chẳng hạn như hiển thị thông báo cảnh báo. Nó sẽ không tái tạo nội dung của WebView được nhúng trong Hộp thoại. Nếu bạn thực sự muốn ngăn một hộp thoại phức tạp bị loại bỏ trong khi xoay, hãy thử phương pháp của 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

Chắc chắn, cách tiếp cận tốt nhất là sử dụng DialogFragment.

Đây là giải pháp của tôi về lớp wrapper giúp ngăn các hộp thoại khác nhau bị loại bỏ trong một Fragment (hoặc Activity có cấu trúc lại nhỏ). Ngoài ra, nó sẽ giúp tránh tái cấu trúc mã lớn nếu vì một số lý do, có rất nhiều AlertDialogsmã nằm rải rác giữa các mã với sự khác biệt nhỏ giữa chúng về hành động, hình thức hoặc thứ gì đó khác.

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

Khi nói đến Hoạt động, bạn có thể gọi getContext()bên trong onCreateDialog(), truyền nó tới DialogProvidergiao diện và yêu cầu một hộp thoại cụ thể bằng mDialogId. Tất cả logic để xử lý một phân đoạn đích phải bị xóa.

Cách sử dụng từ phân mảnh:

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

Bạn có thể đọc toàn bộ bài viết trên blog của tôi Cách ngăn Hộp thoại bị loại bỏ? và chơi với mã nguồn .


1

Có vẻ như đây vẫn là một vấn đề, ngay cả khi "làm mọi thứ đúng" và sử dụng DialogFragmentv.v.

Có một chuỗi trên Google Issue Tracker tuyên bố rằng đó là do một thông báo loại bỏ cũ được để trong hàng đợi thông báo. Cách giải quyết được cung cấp khá đơn giản:

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

        super.onDestroyView();
    }

Đáng kinh ngạc là điều này vẫn cần thiết sau 7 năm kể từ khi vấn đề đó được báo cáo lần đầu tiên.



0

Tôi đã gặp sự cố tương tự: khi hướng màn hình thay đổi, trình onDismissnghe của hộp thoại được gọi mặc dù người dùng không loại bỏ hộp thoại. Thay vào đó, tôi có thể giải quyết vấn đề này bằng cách sử dụng trình onCancelnghe, được kích hoạt cả khi người dùng nhấn nút quay lại và khi người dùng chạm vào bên ngoài hộp thoại.


-3

Chỉ dùng

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

và ứng dụng sẽ biết cách xử lý xoay và kích thước màn hình.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.