Bagaimana cara mengimplementasikan onBackPressed () di Fragments?


460

Apakah ada cara di mana kita dapat menerapkan onBackPressed()di Android Fragment mirip dengan cara di mana kita menerapkan dalam Aktivitas Android?

Karena siklus hidup Fragmen tidak memiliki onBackPressed(). Apakah ada metode alternatif lain untuk berkendara onBackPressed()di fragmen Android 3.0?


13
IMHO, sebuah fragmen seharusnya tidak tahu atau tidak peduli tentang tombol BACK. Suatu aktivitas dapat memedulikan tombol BACK, meskipun dengan fragmen yang biasanya ditangani oleh FragmentManager. Tetapi karena fragmen tidak tahu tentang lingkungannya yang lebih besar (misalnya, apakah itu salah satu dari beberapa aktivitas), maka fragmen tidak benar-benar aman untuk mencoba menentukan apa fungsi BACK yang tepat.
CommonsWare

61
Bagaimana ketika semua fragmennya menampilkan WebView, dan Anda ingin WebView "kembali" ke halaman sebelumnya ketika tombol kembali ditekan?
mharper

Jawaban Michael Herbig sangat cocok untuk saya. Tetapi bagi mereka yang tidak bisa menggunakan getSupportFragmentManager (). Gunakan getFragmentManager () sebagai gantinya.
Dantalian

Jawaban Dmitry Zaitsev pada [Pertanyaan Serupa] [1] bekerja dengan baik. [1]: stackoverflow.com/a/13450668/1372866
ashakirov

6
untuk menjawab mharper mungkin Anda dapat melakukan sesuatu seperti webview.setOnKeyListener (OnKeyListener baru () {@Override boolean publik onKey (Lihat v, int keyCode, acara KeyEvent) {if (keyCode == KeyEvent.KEYCODE_BACK && webview.canGoBack ()) {webview.goBack (); return true;} return false;}});
Omid Aminiva

Jawaban:


307

Saya memecahkan dengan cara ini menimpa onBackPresseddalam Kegiatan. Semua FragmentTransactionadalah addToBackStacksebelum komit:

@Override
public void onBackPressed() {

    int count = getSupportFragmentManager().getBackStackEntryCount();

    if (count == 0) {
        super.onBackPressed();
        //additional code
    } else {
        getSupportFragmentManager().popBackStack();
    }

}


4
count == 1 akan memungkinkan untuk menutup fragmen pertama pada tombol kembali tunggal tekan. Tetapi sebaliknya solusi terbaik
Kunalxigxag

2
Jika Anda menggunakan pustaka dukungan v7 dan Aktivitas Anda diperluas dari FragmentActivity (atau subkelas, seperti AppCompatActivity) ini akan terjadi secara default. Lihat FragmentActivity # onBackPressed
Xiao

3
tetapi Anda tidak dapat menggunakan variabel, kelas & fungsi dari kelas fragmen dalam kode ini
user25

2
@Prabs jika Anda menggunakan fragmen dukungan v4 maka pastikan Anda menggunakan getSupportFragmentManager (). GetBackStackEntryCount ();
Veer3383

152

Menurut saya solusi terbaik adalah:

SOLUSI JAWA

Buat antarmuka sederhana:

public interface IOnBackPressed {
    /**
     * If you return true the back press will not be taken into account, otherwise the activity will act naturally
     * @return true if your processing has priority if not false
     */
    boolean onBackPressed();
}

Dan dalam Aktivitas Anda

public class MyActivity extends Activity {
    @Override public void onBackPressed() {
    Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.main_container);
       if (!(fragment instanceof IOnBackPressed) || !((IOnBackPressed) fragment).onBackPressed()) {
          super.onBackPressed();
       }
    } ...
}

Akhirnya di Fragmen Anda:

public class MyFragment extends Fragment implements IOnBackPressed{
   @Override
   public boolean onBackPressed() {
       if (myCondition) {
            //action not popBackStack
            return true; 
        } else {
            return false;
        }
    }
}

SOLUSI KOTLIN

1 - Buat Antarmuka

interface IOnBackPressed {
    fun onBackPressed(): Boolean
}

2 - Siapkan Aktivitas Anda

class MyActivity : AppCompatActivity() {
    override fun onBackPressed() {
        val fragment =
            this.supportFragmentManager.findFragmentById(R.id.main_container)
        (fragment as? IOnBackPressed)?.onBackPressed()?.not()?.let {
            super.onBackPressed()
        }
    }
}

3 - Terapkan dalam Fragmen target Anda

class MyFragment : Fragment(), IOnBackPressed {
    override fun onBackPressed(): Boolean {
        return if (myCondition) {
            //action not popBackStack
            true
        } else {
            false
        }
    }
}

1
Apa R.id.main_container? Apakah itu ID untuk FragmentPager?
Nathan F.

1
@ MaximeJallu dalam solusi Kotlin, menggabungkan panggilan aman ( .?) dan menegakkan non-nulls ( !!) dapat menyebabkan NPE - para pemain tidak selalu aman. Saya lebih suka menulis if ((fragment as? IOnBackPressed)?.onBackPressed()?.not() == true) { ... }atau lebih kotliney(fragment as? IOnBackPressed)?.onBackPressed()?.not()?.let { ... }
Antek

1
@ Antek Halo, Terima kasih atas kembalinya Anda, Anda bisa mengedit posting, untuk melakukan koreksi. Jangan menilai solusi secara menyeluruh.
Maxime Jallu

4
Bukankah seharusnya begitu .onBackPressed()?.takeIf { !it }?.let{...}? .not()hanya mengembalikan kebalikannya.
David Miguel

1
val fragment = this.supportFragmentManager.findFragmentById(R.id.flContainer) as? NavHostFragment val currentFragment = fragment?.childFragmentManager?.fragments?.get(0) as? IOnBackPressed currentFragment?.onBackPressed()?.takeIf { !it }?.let{ super.onBackPressed() }Ini untuk orang yang menggunakan Kotlin dan NavigationController
Pavle Pavlov

97

Menurut jawaban @HaMMeRed di sini adalah pseudocode bagaimana cara kerjanya. Katakanlah bahwa aktivitas utama Anda disebut BaseActivityyang memiliki fragmen anak (seperti dalam contoh SlidingMenu lib). Berikut langkah-langkahnya:

Pertama kita perlu membuat antarmuka dan kelas yang mengimplementasikan antarmuka untuk memiliki metode generik

  1. Buat antarmuka kelas OnBackPressedListener

    public interface OnBackPressedListener {
        public void doBack();
    }
    
  2. Buat kelas yang mengimplementasikan keterampilan OnBackPressedListener

    public class BaseBackPressedListener implements OnBackPressedListener {
        private final FragmentActivity activity;
    
        public BaseBackPressedListener(FragmentActivity activity) {
            this.activity = activity;
        }
    
        @Override
        public void doBack() {
            activity.getSupportFragmentManager().popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
        }
    }
    
  3. Sejak sekarang, kami akan mengerjakan kode kami BaseActivitydan bagian-bagiannya

  4. Buat pendengar pribadi di atas kelas Anda BaseActivity

    protected OnBackPressedListener onBackPressedListener;
  5. buat metode untuk mengatur pendengar BaseActivity

    public void setOnBackPressedListener(OnBackPressedListener onBackPressedListener) {
        this.onBackPressedListener = onBackPressedListener;
    }
    
  6. di override onBackPressedmengimplementasikan sesuatu seperti itu

    @Override
    public void onBackPressed() {
        if (onBackPressedListener != null)
            onBackPressedListener.doBack();
        else
            super.onBackPressed();
    
  7. dalam fragmen onCreateViewAnda, Anda harus menambahkan pendengar kami

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        activity = getActivity();
    
        ((BaseActivity)activity).setOnBackPressedListener(new BaseBackPressedListener(activity));
    
        View view = ... ;
    //stuff with view
    
        return view;
    }
    

Voila, sekarang ketika Anda mengklik kembali dalam fragmen Anda harus menangkap kebiasaan Anda pada metode kembali.


Jika lebih dari satu fragmen adalah pendengar, bagaimana Anda menentukan, di onBackPressed(), fragmen mana yang ditampilkan ketika tombol kembali ditekan?
Al Lelopath

2
Anda dapat menyesuaikan pendengar klik, bukan baseBackPressedListener baru dan memanggil di sana sebagai anonim untuk mengatur perilaku sendiri, sesuatu seperti ini:((BaseActivity)activity).setOnBackPressedListener(new OnBackpressedListener(){ public void doBack() { //...your stuff here }});
deadfish

Terima kasih, saya telah mengubah posisi saya dalam hal ini, saya akan merekomendasikan decoupling dengan pesan siaran lokal sekarang. Saya pikir itu akan menghasilkan komponen berkualitas tinggi yang sangat dipisahkan.
HaMMeReD


Ini berfungsi dengan baik untuk saya, saya memulai kembali Kegiatan
raphaelbgr

86

Ini berfungsi untuk saya: https://stackoverflow.com/a/27145007/3934111

@Override
public void onResume() {
    super.onResume();

    if(getView() == null){
        return;
    }

    getView().setFocusableInTouchMode(true);
    getView().requestFocus();
    getView().setOnKeyListener(new View.OnKeyListener() {
        @Override
        public boolean onKey(View v, int keyCode, KeyEvent event) {

            if (event.getAction() == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK){
                // handle back button's click listener
                return true;
            }
            return false;
        }
    });
}

2
Bekerja dengan sempurna! Tetapi kemudian Anda juga harus menangani tindakan default, dengan menulis satu lagi kondisi JIKA mungkin. Sejak itu, saya menurunkan slideUpPanel saya jika diperluas. Jadi, saya pertama kali harus menandai cek jika (panel menyala) lalu .....
sud007

18
Masalah dengan solusi ini adalah bahwa getView () mengembalikan hanya tampilan utama (dan bukan subview, jika mereka mendapatkan fokus). Jadi jika Anda memiliki EditText dan ketik beberapa teks lalu tekan "kembali" sekali untuk menyembunyikan keyboard, tekan kedua pada "kembali" dimulai dari "EditText" (tampilan itu sendiri) dan metode ini tampaknya tidak menangkap " kembali tombol tekan lalu (karena hanya menangkap untuk tampilan utama). Seperti yang saya pahami, Anda dapat menangkap peristiwa penekanan tombol pada "EditText" Anda dan juga pandangan lain, tetapi jika Anda memiliki formulir "kompleks" maka ini tidak sebersih dan terawat seperti seharusnya.
Pelpotronic

Ini adalah solusi sederhana yang bagus untuk mimpi buruk yaitu fragmen. Jika Anda memiliki bentuk edittext 'kompleks' maka Anda mungkin harus menghapus proyek Anda dan memulai lagi. Kembalikan salah ketika Anda ingin menggunakan perilaku standar. Kembalikan benar ketika Anda telah mencegat pers belakang dan telah melakukan sesuatu dengannya
behelit

65

Jika Anda menginginkan fungsionalitas semacam itu, Anda perlu menimpanya dalam aktivitas Anda, dan kemudian menambahkan YourBackPressedantarmuka ke semua fragmen Anda, yang Anda panggil pada fragmen yang relevan setiap kali tombol kembali ditekan.

Sunting: Saya ingin menambahkan jawaban saya sebelumnya.

Jika saya melakukan ini hari ini, saya akan menggunakan siaran, atau mungkin siaran yang dipesan jika saya mengharapkan panel lain untuk memperbarui secara bersamaan ke panel konten utama / master.

LocalBroadcastManagerdi Perpustakaan Dukungan dapat membantu dengan ini, dan Anda hanya mengirim siaran onBackPresseddan berlangganan fragmen Anda yang peduli. Saya pikir Messaging adalah implementasi yang lebih terpisah dan akan skala yang lebih baik, jadi itu akan menjadi rekomendasi implementasi resmi saya sekarang. Cukup gunakan Intenttindakan sebagai filter untuk pesan Anda. kirim yang baru Anda buat ACTION_BACK_PRESSED, kirimkan dari aktivitas Anda dan dengarkan dalam fragmen yang relevan.


4
Ide yang cukup bagus! beberapa pemikiran tambahan: + penting untuk merealisasikan logika ini dengan siaran pergi dari aktivitas> fragmen (bukan sebaliknya). OnBackPressed hanya tersedia di tingkat aktivitas, jadi menjebak acara di sana dan menyiarkannya ke semua fragmen "mendengarkan" adalah kunci di sini. + Menggunakan EventBus (seperti Square's Otto) membuat implementasi untuk kasus seperti sepele ini!
Kaushik Gopal

2
Saya belum menggunakan Square's EventBus, tapi saya akan berada di produk masa depan. Saya pikir ini solusi java murni yang lebih baik, dan jika Anda dapat merobohkan bagian Android dari arsitektur Anda, semuanya lebih baik.
HaMMeReD

Bagaimana Anda membiarkan hanya fragmen teratas yang menangani acara? misalnya, Anda ingin menghancurkan fragmen yang sedang ditampilkan
eugene

Penerima siaran harus terikat pada siklus hidup jika Anda menggunakannya, jadi ketika itu tidak terlihat, penerima seharusnya tidak menerima kejadian itu.
HaMMeReD

Perhatikan juga bahwa LocalBroadcastManagertidak dapat melakukan siaran yang dipesan
Xiao

46

Tidak ada yang mudah diimplementasikan dan tidak akan berfungsi secara optimal.

Fragmen memiliki pemanggilan metode onDetach yang akan melakukan pekerjaan.

@Override
    public void onDetach() {
        super.onDetach();
        PUT YOUR CODE HERE
    }

INI AKAN MELAKUKAN PEKERJAAN.


20
tetapi masalahnya di sini adalah Anda tidak tahu apakah fragmen itu terlepas karena tekanan balik atau karena beberapa tindakan lain, yang menyebabkan pengguna melakukan beberapa aktivitas atau fragmen lain.
Sunny

7
onDetach () dipanggil ketika fragmen tidak lagi melekat pada aktivitasnya. Ini disebut setelah onDestroy (). Meskipun Anda akan mendapatkan kode ini ketika Anda menekan tombol kembali Anda juga akan mendapatkan kode ini ketika Anda mengubah dari fragmen ke fragmen. Anda dapat melakukan sesuatu yang bagus untuk membuat pekerjaan ini ...
apmartin1991

Berfungsi dengan baik untuk DialogFragment.
CoolMind

2
Jangan lupa untuk menambahkan isRemoving()seperti yang dijelaskan dalam stackoverflow.com/a/27103891/2914140 .
CoolMind

1
ini salah. Ini dapat disebut karena beberapa alasan
Bali

40

Jika Anda menggunakan androidx.appcompat:appcompat:1.1.0atau di atas maka Anda dapat menambahkan OnBackPressedCallbackfragmen Anda sebagai berikut

requireActivity()
    .onBackPressedDispatcher
    .addCallback(this, object : OnBackPressedCallback(true) {
        override fun handleOnBackPressed() {
            Log.d(TAG, "Fragment back pressed invoked")
            // Do custom work here    

            // if you want onBackPressed() to be called as normal afterwards
            if (isEnabled) {
                isEnabled = false
                requireActivity().onBackPressed()
            }
        }
    }
)

Lihat https://developer.android.com/guide/navigation/navigation-custom-back


Jika Anda menggunakan androidx-core-ktx, Anda dapat menggunakanrequireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner) { /* code to be executed when back is pressed */ }
Maks

Penting untuk dicatat bahwa LifecycleOwnerparam harus ditambahkan seperti pada contoh ini. Tanpa itu, setiap fragmen yang dimulai setelahnya akan memanggil handleBackPressed()jika tombol kembali ditekan.
Eric B.

Apakah metode ini harus dengan perpustakaan navigasi?
Diam

25

Cukup tambahkan addToBackStacksaat Anda beralih antar fragmen Anda seperti di bawah ini:

fragmentManager.beginTransaction().replace(R.id.content_frame,fragment).addToBackStack("tag").commit();

jika Anda menulis addToBackStack(null), ia akan menanganinya sendiri tetapi jika Anda memberi tag, Anda harus menanganinya secara manual.


Apakah ada hal lain yang perlu dilakukan atau tidak menambahkan metode addToBackStack sudah cukup?
Sreecharan Desabattula

1
jika Anda hanya menambahkan bahwa itu harus berfungsi, jika masih tidak berfungsi harus ada sesuatu dalam kode Anda. Tinggalkan kode di suatu tempat untuk melihat tolong @SreecharanDesabattula
Rudi


20

karena pertanyaan ini dan beberapa jawabannya sudah lebih dari lima tahun, izinkan saya berbagi solusi. Ini adalah tindak lanjut dan modernisasi jawaban dari @oyenigun

UPDATE: Di bagian bawah artikel ini, saya menambahkan implementasi alternatif menggunakan ekstensi Fragmen abstrak yang tidak akan melibatkan Kegiatan sama sekali, yang akan berguna bagi siapa pun dengan hierarki fragmen yang lebih kompleks yang melibatkan fragmen bersarang yang membutuhkan perilaku punggung yang berbeda.

Saya perlu menerapkan ini karena beberapa fragmen yang saya gunakan memiliki tampilan lebih kecil yang ingin saya abaikan dengan tombol kembali, seperti tampilan informasi kecil yang muncul, dll, tetapi ini bagus untuk siapa saja yang perlu menimpa perilaku tombol kembali di dalam fragmen.

Pertama, tentukan Antarmuka

public interface Backable {
    boolean onBackPressed();
}

Antarmuka ini, yang saya sebut Backable(saya stickler untuk konvensi penamaan), memiliki metode tunggal onBackPressed()yang harus mengembalikan booleannilai. Kita perlu menegakkan nilai boolean karena kita perlu tahu apakah tekan tombol kembali telah "menyerap" acara kembali. Kembali trueberarti sudah, dan tidak ada tindakan lebih lanjut diperlukan, jika tidak, falsemengatakan bahwa tindakan kembali standar masih harus dilakukan. Antarmuka ini harus berupa file sendiri (lebih disukai dalam paket terpisah bernama interfaces). Ingat, memisahkan kelas Anda ke dalam paket adalah praktik yang baik.

Kedua, temukan fragmen atas

Saya membuat metode yang mengembalikan Fragmentobjek terakhir di tumpukan belakang. Saya menggunakan tag ... jika Anda menggunakan ID, buat perubahan yang diperlukan. Saya memiliki metode statis ini di kelas utilitas yang berhubungan dengan status navigasi, dll ... tapi tentu saja, letakkan di tempat yang paling cocok untuk Anda. Untuk perbaikan, saya telah memasukkan milik saya di kelas yang disebut NavUtils.

public static Fragment getCurrentFragment(Activity activity) {
    FragmentManager fragmentManager = activity.getFragmentManager();
    if (fragmentManager.getBackStackEntryCount() > 0) {
        String lastFragmentName = fragmentManager.getBackStackEntryAt(
                fragmentManager.getBackStackEntryCount() - 1).getName();
        return fragmentManager.findFragmentByTag(lastFragmentName);
    }
    return null;
}

Pastikan jumlah tumpukan kembali lebih besar dari 0, jika tidak, ArrayOutOfBoundsExceptiondapat dilempar saat runtime. Jika tidak lebih besar dari 0, kembalikan nol. Kami akan memeriksa nilai nol nanti ...

Ketiga, Implementasikan dalam Fragmen

Menerapkan Backableantarmuka di bagian mana pun di mana Anda perlu mengganti perilaku tombol kembali. Tambahkan metode implementasi.

public class SomeFragment extends Fragment implements 
        FragmentManager.OnBackStackChangedListener, Backable {

...

    @Override
    public boolean onBackPressed() {

        // Logic here...
        if (backButtonShouldNotGoBack) {
            whateverMethodYouNeed();
            return true;
        }
        return false;
    }

}

Dalam onBackPressed()mengganti, masukkan logika apa pun yang Anda butuhkan. Jika Anda ingin tombol kembali tidak memunculkan tumpukan belakang (perilaku default), kembali benar , bahwa acara kembali Anda telah diserap. Jika tidak, kembalikan salah.

Terakhir, di Aktivitas Anda ...

Ganti onBackPressed()metode dan tambahkan logika ini ke dalamnya:

@Override
public void onBackPressed() {

    // Get the current fragment using the method from the second step above...
    Fragment currentFragment = NavUtils.getCurrentFragment(this);

    // Determine whether or not this fragment implements Backable
    // Do a null check just to be safe
    if (currentFragment != null && currentFragment instanceof Backable) {

        if (((Backable) currentFragment).onBackPressed()) {
            // If the onBackPressed override in your fragment 
            // did absorb the back event (returned true), return
            return;
        } else {
            // Otherwise, call the super method for the default behavior
            super.onBackPressed();
        }
    }

    // Any other logic needed...
    // call super method to be sure the back button does its thing...
    super.onBackPressed();
}

Kami mendapatkan fragmen saat ini di tumpukan belakang, kemudian kami melakukan pemeriksaan nol dan menentukan apakah itu mengimplementasikan Backableantarmuka kami . Jika ya, tentukan apakah acara itu diserap. Jika demikian, kita sudah selesai onBackPressed()dan dapat kembali. Kalau tidak, perlakukan itu sebagai tekan kembali normal dan panggil metode super.

Opsi kedua untuk tidak melibatkan Aktivitas

Kadang-kadang, Anda sama sekali tidak ingin Kegiatan menangani ini, dan Anda perlu menanganinya secara langsung di dalam fragmen. Tetapi siapa yang bilang Anda tidak bisa memiliki Fragmen dengan back press API? Cukup rentangkan fragmen Anda ke kelas baru.

Buat kelas abstrak yang memperluas Fragmen dan mengimplementasikan View.OnKeyListnerantarmuka ...

import android.app.Fragment;
import android.os.Bundle;
import android.view.KeyEvent;
import android.view.View;

public abstract class BackableFragment extends Fragment implements View.OnKeyListener {

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        view.setFocusableInTouchMode(true);
        view.requestFocus();
        view.setOnKeyListener(this);
    }

    @Override
    public boolean onKey(View v, int keyCode, KeyEvent event) {
        if (event.getAction() == KeyEvent.ACTION_UP) {
            if (keyCode == KeyEvent.KEYCODE_BACK) {
                onBackButtonPressed();
                return true;
            }
        }

        return false;
    }

    public abstract void onBackButtonPressed();
}

Seperti yang Anda lihat, setiap fragmen yang meluas BackableFragmentakan secara otomatis menangkap kembali klik menggunakan View.OnKeyListenerantarmuka. Panggil saja onBackButtonPressed()metode abstrak dari dalam onKey()metode yang diimplementasikan menggunakan logika standar untuk membedakan tekan tombol kembali. Jika Anda perlu mendaftarkan klik kunci selain tombol kembali, pastikan untuk memanggil supermetode ketika menimpa onKey()fragmen Anda, jika tidak Anda akan menimpa perilaku di abstraksi.

Mudah digunakan, cukup rentangkan dan terapkan:

public class FragmentChannels extends BackableFragment {

    ...

    @Override
    public void onBackButtonPressed() {
        if (doTheThingRequiringBackButtonOverride) {
            // do the thing
        } else {
            getActivity().onBackPressed();
        }
    }

    ...
}

Karena onBackButtonPressed()metode di kelas super abstrak, setelah Anda memperluas Anda harus menerapkan onBackButtonPressed(). Ini kembali voidkarena hanya perlu melakukan tindakan dalam kelas fragmen, dan tidak perlu menyampaikan penyerapan pers kembali ke Aktivitas. Pastikan Anda memanggil onBackPressed()metode Aktivitas jika apa pun yang Anda lakukan dengan tombol kembali tidak memerlukan penanganan, jika tidak, tombol kembali akan dinonaktifkan ... dan Anda tidak menginginkan itu!

Peringatan Seperti yang Anda lihat, ini mengatur pendengar kunci ke tampilan root dari fragmen, dan kita harus memfokuskannya. Jika ada teks edit yang terlibat (atau tampilan mencuri fokus lainnya) dalam fragmen Anda yang memperluas kelas ini, (atau fragmen dalam lainnya atau tampilan yang memiliki yang sama), Anda harus mengatasinya secara terpisah. Ada artikel bagus tentang cara memperpanjang EditText untuk kehilangan fokus pada tekanan balik.

Saya harap seseorang menemukan ini berguna. Selamat coding.


Terima kasih, ini solusi yang bagus. Bisakah Anda mengatakan, mengapa Anda menelepon super.onBackPressed();dua kali?
CoolMind

Ini hanya dipanggil satu kali per skenario tentang keadaan nol dari currentFragment. Jika fragmen tidak nol dan fragmen mengimplementasikan Backableantarmuka, tidak ada tindakan yang diambil. Jika fragmen tidak nol dan TIDAK diterapkan Backable, kami memanggil metode super. Jika fragmennya nol, ia melompati panggilan super terakhir.
mwieczorek

Maaf, saya tidak mengerti. Dalam kasus ketika a currentFragmentbukan nol dan merupakan turunan dari Backable dan terlepas (kami menekan backtombol dan menutup fragmen) kemunculan pertama super.onBackPressed();disebut, kemudian yang kedua.
CoolMind

Solusi Luar Biasa
David Toledo

Mungkin "Closeable" terdengar sedikit lebih baik daripada "Backable"
pumnao

15

Solusinya sederhana:

1) Jika Anda memiliki kelas fragmen dasar yang diperluas oleh semua fragmen, kemudian tambahkan kode ini ke kelasnya, jika tidak, buat kelas fragmen basis seperti itu

 /* 
 * called when on back pressed to the current fragment that is returned
 */
 public void onBackPressed()
 {
    // add code in super class when override
 }

2) Di kelas Aktivitas Anda, ganti onBackPressed sebagai berikut:

private BaseFragment _currentFragment;

@Override
public void onBackPressed()
{
      super.onBackPressed();
     _currentFragment.onBackPressed();
}

3) Di kelas Fragmen Anda, tambahkan kode yang Anda inginkan:

 @Override
 public void onBackPressed()
 {
    setUpTitle();
 }


9

onBackPressed() menyebabkan Fragmen terlepas dari Aktivitas.

Menurut @Sterling Diaz menjawab, saya pikir dia benar. TAPI beberapa situasi akan salah. (mis. Layar Putar)

Jadi, saya pikir kita bisa mendeteksi apakah isRemoving()akan mencapai tujuan.

Anda dapat menulisnya di onDetach()atau onDestroyView(). Itu bekerja.

@Override
public void onDetach() {
    super.onDetach();
    if(isRemoving()){
        // onBackPressed()
    }
}

@Override
public void onDestroyView() {
    super.onDestroyView();
    if(isRemoving()){
        // onBackPressed()
    }
}

8

Yah saya melakukannya seperti ini, dan itu bekerja untuk saya

Antarmuka yang sederhana

FragmentOnBackClickInterface.java

public interface FragmentOnBackClickInterface {
    void onClick();
}

Contoh implementasi

MyFragment.java

public class MyFragment extends Fragment implements FragmentOnBackClickInterface {

// other stuff

public void onClick() {
       // what you want to call onBackPressed?
}

lalu timpa saja diBackPressed dalam aktivitas

    @Override
public void onBackPressed() {
    int count = getSupportFragmentManager().getBackStackEntryCount();
    List<Fragment> frags = getSupportFragmentManager().getFragments();
    Fragment lastFrag = getLastNotNull(frags);
    //nothing else in back stack || nothing in back stack is instance of our interface
    if (count == 0 || !(lastFrag instanceof FragmentOnBackClickInterface)) {
        super.onBackPressed();
    } else {
        ((FragmentOnBackClickInterface) lastFrag).onClick();
    }
}

private Fragment getLastNotNull(List<Fragment> list){
    for (int i= list.size()-1;i>=0;i--){
        Fragment frag = list.get(i);
        if (frag != null){
            return frag;
        }
    }
    return null;
}

Tidak bekerja! super.onbackpressed () dipanggil selalu :(
Animesh Mangla

Cara melewati acara Fragmen Batin ini. Saya memiliki ViewPager dalam Aktivitas dan ViewPager memiliki Fragmen, Sekarang semua Fragmen tersebut memiliki fragmen anak. Cara meneruskan acara tombol ke fragmen anak itu.
Kishan Vaghela

@KishanVaghela, saya tidak menyentuh Android sejak itu tetapi saya punya satu ide. Beri saya tanggal 24 untuk memperbarui jawaban saya.
Błażej

Ok tidak masalah, Salah satu pilihannya adalah saya harus meneruskan acara pendengar ke setiap fragmen anak dari fragmen orangtua. tapi ini bukan cara yang ideal karena saya perlu melakukan ini untuk setiap fragmen.
Kishan Vaghela

@KishanVaghela saya sedang memikirkan manajer dalam aktivitas. Anda bisa menambah / menghapus fragmen dengan antarmuka itu. Tetapi dengan pendekatan ini Anda harus menerapkan manajer di setiap fragmen yang memiliki subfragmen. Jadi, mungkin solusi yang lebih baik adalah menciptakan manajer sebagai singleton. Maka Anda harus menambahkan / menghapus fragmen / objek dan di 'onBackPressed' you call method 'onBackPressed' dari manajer ini. Metode itu akan memanggil metode 'onClick' di setiap objek yang ditambahkan ke manajer. Apakah itu lebih atau kurang jelas bagi Anda?
Błażej

8

Anda harus menambahkan antarmuka ke proyek Anda seperti di bawah ini;

public interface OnBackPressed {

     void onBackPressed();
}

Dan kemudian, Anda harus mengimplementasikan antarmuka ini pada fragmen Anda;

public class SampleFragment extends Fragment implements OnBackPressed {

    @Override
    public void onBackPressed() {
        //on Back Pressed
    }

}

Dan Anda dapat memicu acara onBackPressed ini di bawah aktivitas Anda di acaraBackPressed seperti di bawah ini;

public class MainActivity extends AppCompatActivity {
       @Override
        public void onBackPressed() {
                Fragment currentFragment = getSupportFragmentManager().getFragments().get(getSupportFragmentManager().getBackStackEntryCount() - 1);
                if (currentFragment instanceof OnBackPressed) {  
                    ((OnBackPressed) currentFragment).onBackPressed();
                }
                super.onBackPressed();
        }
}

Ini metode yang baik, tapi hati-hati, jangan panggil getActivity().onBackPressed();karena akan memanggil pengecualian: "java.lang.StackOverflowError: ukuran stack 8MB".
CoolMind

8

Ini hanya kode kecil yang akan melakukan trik:

 getActivity().onBackPressed();

Semoga ini bisa membantu seseorang :)


4
Dia meminta untuk mengesampingkan perilakunya, bukan hanya memohon metode aktivitas.
mwieczorek

2
Saya memang membacanya dengan cermat, terima kasih. Solusi Anda hanya me-relay kembali metode onBackPressed ke Activity, ketika ia bertanya bagaimana menimpanya.
mwieczorek

7

Jika Anda menggunakan EventBus, itu mungkin solusi yang jauh lebih sederhana:

Dalam Fragmen Anda:

@Override
public void onAttach(Activity activity) {
    super.onAttach(activity);
    EventBus.getDefault().register(this);
}

@Override
public void onDetach() {
    super.onDetach();
    EventBus.getDefault().unregister(this);
}


// This method will be called when a MessageEvent is posted
public void onEvent(BackPressedMessage type){
    getSupportFragmentManager().popBackStack();
}

dan di kelas Aktivitas Anda, Anda dapat mendefinisikan:

@Override
public void onStart() {
    super.onStart();
    EventBus.getDefault().register(this);
}

@Override
public void onStop() {
    EventBus.getDefault().unregister(this);
    super.onStop();
}

// This method will be called when a MessageEvent is posted
public void onEvent(BackPressedMessage type){
    super.onBackPressed();
}

@Override
public void onBackPressed() {
    EventBus.getDefault().post(new BackPressedMessage(true));
}

BackPressedMessage.java hanyalah objek POJO

Ini sangat bersih dan tidak ada kerumitan antarmuka / implementasi.


7

ini solusi saya:

di MyActivity.java:

public interface OnBackClickListener {
        boolean onBackClick();
    }

    private OnBackClickListener onBackClickListener;

public void setOnBackClickListener(OnBackClickListener onBackClickListener) {
        this.onBackClickListener = onBackClickListener;
    }

@Override
    public void onBackPressed() {
        if (onBackClickListener != null && onBackClickListener.onBackClick()) {
            return;
        }
        super.onBackPressed();
    }

dan dalam Fragmen:

((MyActivity) getActivity()).setOnBackClickListener(new MyActivity.OnBackClickListener() {
    @Override
    public boolean onBackClick() {
        if (condition) {
            return false;
        }

        // some codes

        return true;
    }
});

6

Menggunakan komponen Navigasi, Anda dapat melakukannya seperti ini :

Jawa

public class MyFragment extends Fragment {

@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // This callback will only be called when MyFragment is at least Started.
    OnBackPressedCallback callback = new OnBackPressedCallback(true /* enabled by default */) {
        @Override
        public void handleOnBackPressed() {
            // Handle the back button event
        }
    });
    requireActivity().getOnBackPressedDispatcher().addCallback(this, callback);

    // The callback can be enabled or disabled here or in handleOnBackPressed()
}
...
}

Kotlin

class MyFragment : Fragment() {

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    // This callback will only be called when MyFragment is at least Started.
    val callback = requireActivity().onBackPressedDispatcher.addCallback(this) {
        // Handle the back button event
    }

    // The callback can be enabled or disabled here or in the lambda
}
...
}

4

Bagaimana dengan menggunakan onDestroyView ()?

@Override
public void onDestroyView() {
    super.onDestroyView();
}

5
bagaimana dengan pergi ke fragmen lain dari fragmen yang sebenarnya, apa yang akan terjadi dengan menggunakan metode Anda? :)
Choletski

Jawaban yang paling tidak berguna. Itu sama dengan menulis i = iatau if (1 < 0) {}.
CoolMind

4
@Override
public void onResume() {
    super.onResume();

    getView().setFocusableInTouchMode(true);
    getView().requestFocus();
    getView().setOnKeyListener(new View.OnKeyListener() {
        @Override
        public boolean onKey(View v, int keyCode, KeyEvent event) {
            if (event.getAction() == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) {
                // handle back button
                replaceFragmentToBackStack(getActivity(), WelcomeFragment.newInstance(bundle), tags);

                return true;
            }

            return false;
        }
    });
}

Hanya jika fragmen tidak memiliki tampilan yang fokus (seperti mengedit teks), biarkan fragmen menangani tombol kembali sendiri seperti jawaban ini. Jika tidak, biarkan aktivitas yang mengandung fragmen melakukannya oleh OnBackPress () saat jawaban pertama ditulis; karena tampilan yang fokus akan mencuri fokus fragmen. Namun, kami dapat mendapatkan kembali fokus untuk fragmen dengan memfokuskan semua tampilan dengan fokus yang jelas dengan metode clearFocus () dan memfokuskan kembali ke fragmen.
Nguyen Tan Dat

4
public class MyActivity extends Activity {

    protected OnBackPressedListener onBackPressedListener;

    public interface OnBackPressedListener {
        void doBack();
    }

    public void setOnBackPressedListener(OnBackPressedListener onBackPressedListener) {
        this.onBackPressedListener = onBackPressedListener;
    }

    @Override
    public void onBackPressed() {
        if (onBackPressedListener != null)
            onBackPressedListener.doBack();
        else
            super.onBackPressed();
    } 

    @Override
    protected void onDestroy() {
        onBackPressedListener = null;
        super.onDestroy();
    }
}

dalam fragmen Anda tambahkan berikut ini, jangan lupa untuk mengimplementasikan antarmuka mainaktivitas.

public class MyFragment extends Framgent implements MyActivity.OnBackPressedListener {
    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
         ((MyActivity) getActivity()).setOnBackPressedListener(this);
    }

@Override
public void doBack() {
    //BackPressed in activity will call this;
}

}

4

Dalam solusi saya (Kotlin);

Saya menggunakan fungsi onBackAlternative sebagai parameter pada BaseActivity .

BaseActivity

abstract class BaseActivity {

    var onBackPressAlternative: (() -> Unit)? = null

    override fun onBackPressed() {
        if (onBackPressAlternative != null) {
            onBackPressAlternative!!()
        } else {
            super.onBackPressed()
        }
    }
}

Saya memiliki fungsi untuk mengatur onBackPressAlternative di BaseFragment .

BaseFragment

abstract class BaseFragment {

     override fun onStart() {
        super.onStart()
        ...
        setOnBackPressed(null) // Add this
     }

      //Method must be declared as open, for overriding in child class
     open fun setOnBackPressed(onBackAlternative: (() -> Unit)?) {
         (activity as BaseActivity<*, *>).onBackPressAlternative = onBackAlternative
     }
}

Kemudian onBackPressAlternative saya siap digunakan pada fragmen.

Sub Fragmen

override fun setOnBackPressed(onBackAlternative: (() -> Unit)?) {
    (activity as BaseActivity<*, *>).onBackPressAlternative = {
        // TODO Your own custom onback function 
    }
}

3

Jangan menerapkan metode ft.addToBackStack () sehingga ketika Anda menekan tombol kembali aktivitas Anda akan selesai.

proAddAccount = new ProfileAddAccount();
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.fragment_container, proAddAccount);
//fragmentTransaction.addToBackStack(null);
fragmentTransaction.commit();


3

Ikuti saja langkah-langkah ini:

Selalu sambil menambahkan fragmen,

fragmentTransaction.add(R.id.fragment_container, detail_fragment, "Fragment_tag").addToBackStack(null).commit();

Kemudian di aktivitas utama, timpa onBackPressed()

if (getSupportFragmentManager().getBackStackEntryCount() > 0) {
    getSupportFragmentManager().popBackStack();
} else {
    finish();
}

Untuk menangani tombol kembali di aplikasi Anda,

Fragment f = getActivity().getSupportFragmentManager().findFragmentByTag("Fragment_tag");
if (f instanceof FragmentName) {
    if (f != null) 
        getActivity().getSupportFragmentManager().beginTransaction().remove(f).commit()               
}

Itu dia!


3

Dalam siklus hidup aktivitas, selalu tombol android kembali berurusan dengan transaksi FragmentManager ketika kami menggunakan FragmentActivity atau AppCompatActivity.

Untuk menangani backstack, kita tidak perlu menangani count backstack atau menandai apa pun, tetapi kita harus tetap fokus sambil menambahkan atau mengganti sebuah fragmen. Silakan temukan cuplikan berikut untuk menangani case tombol kembali,

    public void replaceFragment(Fragment fragment) {

        FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
        if (!(fragment instanceof HomeFragment)) {
            transaction.addToBackStack(null);
        }
        transaction.replace(R.id.activity_menu_fragment_container, fragment).commit();
    }

Di sini, saya tidak akan menambahkan tumpukan kembali untuk fragmen rumah saya karena itu adalah halaman rumah aplikasi saya. Jika menambahkan addToBackStack ke HomeFragment maka aplikasi akan menunggu untuk menghapus semua framen dalam kegiatan maka kita akan mendapatkan layar kosong jadi saya menjaga kondisi berikut,

if (!(fragment instanceof HomeFragment)) {
            transaction.addToBackStack(null);
}

Sekarang, Anda dapat melihat fragmen yang ditambahkan sebelumnya pada aktivitas dan aplikasi akan keluar ketika mencapai HomeFragment. Anda juga dapat melihat cuplikan berikut.

@Override
public void onBackPressed() {

    if (mDrawerLayout.isDrawerOpen(Gravity.LEFT)) {
        closeDrawer();
    } else {
        super.onBackPressed();
    }
}

3

Fragmen: Buat metode BaseFragment menempatkan:

 public boolean onBackPressed();

Aktivitas:

@Override
public void onBackPressed() {
    List<Fragment> fragments = getSupportFragmentManager().getFragments();
    if (fragments != null) {
        for (Fragment fragment : fragments) {
            if (!fragment.isVisible()) continue;

            if (fragment instanceof BaseFragment && ((BaseFragment) fragment).onBackPressed()) {
                return;
            }
        }
    }

    super.onBackPressed();
}

Aktivitas Anda akan berjalan di atas fragmen yang terlampir dan terlihat dan memanggilBackPressed () pada masing-masing dari mereka dan membatalkan jika salah satu dari mereka mengembalikan 'benar' (artinya telah ditangani, jadi tidak ada tindakan lebih lanjut).


Pak, ini adalah cara yang paling elegan dan berfungsi sempurna!
asozcan

3

Menurut catatan rilis AndroidX , androidx.activity 1.0.0-alpha01dirilis dan memperkenalkan ComponentActivity, kelas dasar baru yang ada FragmentActivitydan AppCompatActivity. Dan rilis ini memberi kita fitur baru:

Anda sekarang dapat mendaftarkan OnBackPressedCallbackvia addOnBackPressedCallbackuntuk menerima onBackPressed()panggilan balik tanpa perlu mengganti metode dalam aktivitas Anda.


Bisakah Anda menunjukkan contohnya? Saya tidak mendapatkan metode untuk menyelesaikannya.
Bryan Bryce

1
@BryanBryce Saya belum menemukan contoh tetapi dokumen resmi: developer.android.com/reference/androidx/activity/…
TonnyL

Metode ini tidak akan menyelesaikan b / c AppCompatActivity sebenarnya memanjang dari androidx.core.app.ComponentActivity bukan androidx.activity.ComponentActivity.
wooldridgetm

3

Anda dapat mendaftarkan fragmen dalam aktivitas untuk menangani kembali pers:

interface BackPressRegistrar {
    fun registerHandler(handler: BackPressHandler)
    fun unregisterHandler(handler: BackPressHandler)
}

interface BackPressHandler {
    fun onBackPressed(): Boolean
}

pemakaian:

Dalam Fragmen:

private val backPressHandler = object : BackPressHandler {
    override fun onBackPressed(): Boolean {
        showClosingWarning()
        return false
    }
}

override fun onResume() {
    super.onResume()
    (activity as? BackPressRegistrar)?.registerHandler(backPressHandler)
}

override fun onStop() {
    (activity as? BackPressRegistrar)?.unregisterHandler(backPressHandler)
    super.onStop()
}

Dalam Aktivitas:

class MainActivity : AppCompatActivity(), BackPressRegistrar {


    private var registeredHandler: BackPressHandler? = null
    override fun registerHandler(handler: BackPressHandler) { registeredHandler = handler }
    override fun unregisterHandler(handler: BackPressHandler) { registeredHandler = null }

    override fun onBackPressed() {
        if (registeredHandler?.onBackPressed() != false) super.onBackPressed()
    }
}

3

Menyediakan navigasi belakang khusus dengan menangani diBackPressed sekarang lebih mudah dengan panggilan balik di dalam fragmen.

class MyFragment : Fragment() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val onBackPressedCallback = object : OnBackPressedCallback(true) {
            override fun handleOnBackPressed() {
                if (true == conditionForCustomAction) {
                    myCustomActionHere()
                } else  NavHostFragment.findNavController(this@MyFragment).navigateUp();    
        }
    }
    requireActivity().onBackPressedDispatcher.addCallback(
        this, onBackPressedCallback
    )
    ...
}

Jika Anda ingin tindakan kembali default berdasarkan beberapa kondisi, Anda dapat menggunakan:

 NavHostFragment.findNavController(this@MyFragment).navigateUp();

3

Di dalam metode onCreate fragmen tambahkan yang berikut:

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    OnBackPressedCallback callback = new OnBackPressedCallback(true) {
        @Override
        public void handleOnBackPressed() {
            //Handle the back pressed
        }
    };
    requireActivity().getOnBackPressedDispatcher().addCallback(this, callback);
}

setelah menambahkan backpress myFragment ini berfungsi tetapi sekarang backpress aktivitas tidak
Sunil Chaudhary

2

Solusi terbaik,

import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v7.app.AppCompatActivity;

public class BaseActivity extends AppCompatActivity {
    @Override
    public void onBackPressed() {

        FragmentManager fm = getSupportFragmentManager();
        for (Fragment frag : fm.getFragments()) {
            if (frag == null) {
                super.onBackPressed();
                finish();
                return;
            }
            if (frag.isVisible()) {
                FragmentManager childFm = frag.getChildFragmentManager();
                if (childFm.getFragments() == null) {
                    super.onBackPressed();
                    finish();
                    return;
                }
                if (childFm.getBackStackEntryCount() > 0) {
                    childFm.popBackStack();
                    return;
                }
                else {

                    fm.popBackStack();
                    if (fm.getFragments().size() <= 1) {
                        finish();
                    }
                    return;
                }

            }
        }
    }
}
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.