Mencegah penutupan dialog pada rotasi layar di Android

94

Saya mencoba untuk mencegah dialog yang dibuat dengan Pembuat peringatan ditutup saat Aktivitas dimulai ulang.

Jika saya membebani metode onConfigurationChanged, saya berhasil melakukan ini dan mengatur ulang tata letak ke orientasi yang benar tetapi saya kehilangan fitur teks tempel dari teks editan. Jadi dalam memecahkan masalah dialog saya telah membuat masalah teks edit ini.

Jika saya menyimpan string dari teks editan dan menetapkannya kembali dalam perubahan onCofiguration, mereka tampaknya masih default ke nilai awal bukan apa yang dimasukkan sebelum rotasi. Bahkan jika saya memaksa pembatalan tampaknya memperbaruinya.

Saya benar-benar perlu menyelesaikan masalah dialog atau masalah teks edit.

Terima kasih untuk bantuannya.

draksia
sumber
1
Bagaimana Anda menyimpan / mengembalikan konten EditText yang diedit? Bisakah Anda menunjukkan beberapa kode?
Peter Knego
Saya menemukan masalah dengan itu, saya lupa untuk mendapatkan tampilan lagi oleh Id setelah mengatur ulang tata letak.
draksia

Jawaban:

134

Cara terbaik untuk menghindari masalah ini saat ini adalah dengan menggunakan a DialogFragment.

Buat kelas baru yang diperluas DialogFragment. Ganti onCreateDialogdan kembalikan file lama Dialogatau file AlertDialog.

Kemudian Anda bisa menunjukkannya dengan DialogFragment.show(fragmentManager, tag).

Berikut adalah contoh dengan 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();
    }
}

Dan dalam Aktivitas yang Anda panggil:

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

Jawaban ini membantu menjelaskan tiga pertanyaan lainnya (dan jawabannya):

Brais Gabin
sumber
Ada tombol di aplikasi saya untuk memanggil .show (), saya harus mengingat status dialog peringatan, menampilkan / ditutup. Apakah ada cara untuk mempertahankan dialog tanpa memanggil .show ()?
Alpha Huang
1
Dikatakan bahwa onAttachsekarang sudah usang. Apa yang harus dilakukan?
farahm
3
@faraz_ahmed_kamran, Anda harus menggunakan onAttach(Context context)dan android.support.v4.app.DialogFragment. The onAttachMetode mengambil contextalih-alih activitysebagai parameter sekarang.
Stan Mots
Mungkin tidak perlu YesNoListener. Lihat jawaban ini .
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);
    }
Chung IW
sumber
1
Bagi mereka yang merasa kode saya tidak berguna, cobalah sebelum klik :-)
Chung IW
6
Bekerja, tidak tahu bagaimana tetapi berhasil! Solusi sederhana dan abstrak bersih, terima kasih.
nmvictor
3
Saya pikir kode ini buruk. doLogout () memiliki referensi ke konteks yang merupakan / berisi aktivitas. Aktivitas tidak dapat dimusnahkan yang dapat menyebabkan kebocoran memori. Saya sedang mencari kemungkinan untuk menggunakan AlertDialog dari konteks statis tetapi sekarang saya yakin itu tidak mungkin. Hasilnya hanya bisa jadi sampah menurut saya.
Jan
2
Sepertinya itu berhasil. Dialog tetap terbuka, tetapi tidak ada koneksi ke aktivitas atau fragmen yang baru dibuat (ini baru dibuat pada setiap perubahan orientasi). Jadi Anda tidak dapat melakukan apa pun yang memerlukan Contextdi dalam tombol dialog OnClickListener.
Udo Klimaschewski
2
Kode ini berfungsi tetapi tidak merekomendasikan sama sekali. Ini membocorkan referensi aktivitas, itulah sebabnya dialog bisa tetap ada. Ini adalah praktik yang sangat buruk yang akan menyebabkan kebocoran memori.
Hexise
4

Jika Anda mengubah tata letak pada perubahan orientasi, saya tidak akan memasukkan android:configChanges="orientation"manifes Anda karena Anda tetap membuat ulang tampilan.

Simpan status aktivitas Anda saat ini (seperti teks yang dimasukkan, dialog yang ditampilkan, data yang ditampilkan, dll.) Menggunakan metode ini:

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

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

Dengan begitu, aktivitas melewati onCreate lagi dan kemudian memanggil metode onRestoreInstanceState tempat Anda bisa menyetel nilai EditText lagi.

Jika Anda ingin menyimpan Objek yang lebih kompleks, Anda dapat menggunakan

@Override
public Object onRetainNonConfigurationInstance() {
}

Di sini Anda dapat menyimpan objek apa pun dan di onCreate Anda hanya perlu memanggil getLastNonConfigurationInstance();untuk mendapatkan Objek tersebut.

Maria Neumayer
sumber
4
OnRetainNonConfigurationInstance() sekarang tidak lagi digunakan seperti yang dikatakan oleh Dokumen: developer.android.com/reference/android/app/… setRetainInstance(boolean retain) sebagai gantinya harus digunakan: developer.android.com/reference/android/app/…
ForceMagic
@ForceMagic setRetainInstancesama sekali berbeda: ini untuk Fragmen dan tidak menjamin Anda bahwa instance akan dipertahankan.
Miha_x64
3

Cukup tambahkan android: configChanges = "orientasi" dengan elemen aktivitas Anda di AndroidManifest.xml

Contoh:

<activity
            android:name=".YourActivity"
            android:configChanges="orientation"
            android:label="@string/app_name"></activity>
SAAM
sumber
Ini dapat menyebabkan dialog ditampilkan dengan tidak benar dalam beberapa keadaan.
Sam
1
android: configChanges = "orientasi | screenSize" Catatan: Jika aplikasi Anda menargetkan Android 3.2 (API level 13) atau lebih tinggi, Anda juga harus mendeklarasikan konfigurasi "screenSize", karena konfigurasi juga berubah saat perangkat beralih antara orientasi potret dan lanskap.
tsig
1

Pendekatan yang sangat mudah adalah membuat dialog dari metode tersebut onCreateDialog() (lihat catatan di bawah). Anda menunjukkannya showDialog(). Dengan cara ini, Android menangani rotasi untuk Anda dan Anda tidak perlu memanggil dismiss()dalam onPause()untuk menghindari WindowLeak dan kemudian Anda tidak harus mengembalikan dialog. Dari dokumen:

Tampilkan dialog yang dikelola oleh aktivitas ini. Panggilan ke onCreateDialog (int, Bundle) akan dilakukan dengan id yang sama saat pertama kali dipanggil untuk id tertentu. Setelah itu, dialog akan disimpan dan dikembalikan secara otomatis.

Lihat dokumen Android showDialog () untuk info selengkapnya. Semoga membantu seseorang!

Catatan: Jika menggunakan AlertDialog.Builder, jangan menelepon show()dari onCreateDialog(), panggil create()saja. Jika menggunakan ProgressDialog, cukup buat objek, setel parameter yang Anda butuhkan dan kembalikan. Kesimpulannya, show()di dalam onCreateDialog()menyebabkan masalah, buat saja instance de Dialog dan kembalikan. Ini seharusnya berhasil! (Saya mengalami masalah saat menggunakan showDialog () dari onCreate () -sebenarnya tidak menampilkan dialog-, tetapi jika Anda menggunakannya di onResume () atau dalam callback listener, dialog berfungsi dengan baik).

Caumons
sumber
Untuk kasus mana Anda memerlukan beberapa kode? OnCreateDialog () atau menampilkannya dengan pembuat dan memanggil show () untuk itu?
Caumons
Saya telah berhasil melakukannya .. tetapi masalahnya, onCreateDialog () sekarang tidak digunakan lagi: - \
neteinstein
BAIK! Ingatlah bahwa sebagian besar perangkat Android masih berfungsi dengan versi 2.X, jadi Anda tetap dapat menggunakannya! Lihat penggunaan versi platform Android
Caumons
Namun, apa pilihan lain jika bukan onCreateDialog?
neteinstein
1
Anda dapat menggunakan kelas pembangun misalnya AlertDialog.Builder. Jika Anda menggunakannya di dalam onCreateDialog(), alih-alih menggunakan show()mengembalikan hasil create(). Jika tidak, panggil show()dan simpan AlertDialog yang dikembalikan ke dalam atribut Aktivitas dan di onPause() dismiss()dalamnya jika ditampilkan untuk menghindari WindowLeak. Semoga membantu!
Caumons
1

Pertanyaan ini sudah lama dijawab.

Namun ini adalah solusi non-hacky dan sederhana yang saya gunakan untuk diri saya sendiri.

Saya melakukan kelas pembantu ini untuk diri saya sendiri, jadi Anda juga dapat menggunakannya di aplikasi Anda.

Penggunaannya adalah:

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

Atau

 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;
                }          
        }
}
Ioane Sharvadze
sumber
1

Anda bisa menggabungkan metode onSave / onRestore Dialog dengan metode onSave / onRestore Aktivitas untuk mempertahankan status Dialog.

Catatan: Metode ini bekerja untuk Dialog "sederhana", seperti menampilkan pesan peringatan. Ini tidak akan mereproduksi konten WebView yang disematkan dalam Dialog. Jika Anda benar-benar ingin mencegah dialog kompleks dari pemecatan selama rotasi, coba metode 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());
}
hackjutsu
sumber
1

Jelas, pendekatan terbaik adalah dengan menggunakan DialogFragment.

Berikut adalah solusi saya dari kelas pembungkus yang membantu mencegah dialog berbeda ditutup dalam satu Fragmen (atau Aktivitas dengan pemfaktoran ulang kecil). Selain itu, akan membantu untuk menghindari refactoring kode besar-besaran jika karena alasan tertentu ada banyak AlertDialogskode yang tersebar di antara kode dengan sedikit perbedaan di antara mereka dalam hal tindakan, tampilan, atau hal lain.

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

Ketika datang ke Aktivitas, Anda bisa memanggil getContext()di dalam onCreateDialog(), mentransmisikannya ke DialogProviderantarmuka dan meminta dialog tertentu mDialogId. Semua logika untuk menangani fragmen target harus dihapus.

Penggunaan dari fragmen:

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

Anda dapat membaca artikel lengkap di blog saya Bagaimana cara mencegah Dialog ditutup? dan bermain-main dengan kode sumber .

Dmitry Korobeinikov
sumber
1

Tampaknya ini masih menjadi masalah, bahkan saat "melakukan semuanya dengan benar" dan menggunakan DialogFragmentdll.

Ada utas di Google Issue Tracker yang mengklaim bahwa hal itu disebabkan oleh pesan penutupan lama yang tertinggal dalam antrean pesan. Solusi yang disediakan cukup sederhana:

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

        super.onDestroyView();
    }

Luar biasa, ini masih dibutuhkan 7 tahun setelah isu itu pertama kali dilaporkan.

Magnus W.
sumber
0

Saya mengalami masalah serupa: saat orientasi layar berubah, onDismisspendengar dialog akan dipanggil meskipun pengguna tidak menutup dialog. Saya dapat mengatasinya dengan menggunakan onCancelpendengar, yang dipicu saat pengguna menekan tombol kembali dan saat pengguna menyentuh di luar dialog.

Sam
sumber
-3

Gunakan saja

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

dan aplikasi akan tahu cara menangani rotasi dan ukuran layar.

pengguna3384694
sumber