Android 4.2: perilaku back-stack dengan fragmen bersarang


108

Dengan Android 4.2, pustaka dukungan mendapat dukungan untuk fragmen bersarang, lihat di sini . Saya telah bermain-main dengannya dan menemukan perilaku / bug yang menarik terkait back stack dan getChildFragmentManager () . Saat menggunakan getChildFragmentManager () dan addToBackStack (Nama string), dengan menekan tombol kembali, sistem tidak menjalankan back-stack ke fragmen sebelumnya. Sebaliknya, saat menggunakan getFragmentManager () dan addToBackStack (Nama string), dengan menekan tombol kembali, sistem akan kembali ke fragmen sebelumnya.

Bagi saya, perilaku ini tidak terduga. Dengan menekan tombol kembali pada perangkat saya, saya berharap bahwa fragmen terakhir yang ditambahkan ke tumpukan belakang akan muncul, bahkan jika fragmen ditambahkan ke tumpukan belakang di pengelola fragmen anak-anak.

Apakah perilaku ini benar? Apakah perilaku ini bug? Apakah ada solusi untuk masalah ini?

contoh kode dengan getChildFragmentManager ():

public class FragmentceptionActivity extends FragmentActivity {

@Override
protected void onCreate(Bundle arg0) {
    super.onCreate(arg0);

    final FrameLayout wrapper1 = new FrameLayout(this);
    wrapper1.setLayoutParams(new FrameLayout.LayoutParams(
            FrameLayout.LayoutParams.MATCH_PARENT,
            FrameLayout.LayoutParams.MATCH_PARENT));
    wrapper1.setId(1);

    final FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
            FrameLayout.LayoutParams.MATCH_PARENT,
            FrameLayout.LayoutParams.WRAP_CONTENT);
    params.topMargin = 0;

    final TextView text = new TextView(this);
    text.setLayoutParams(params);
    text.setText("fragment 1");
    wrapper1.addView(text);

    setContentView(wrapper1);

    getSupportFragmentManager().beginTransaction().addToBackStack(null)
            .add(1, new Fragment1()).commit();
}

public class Fragment1 extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        final FrameLayout wrapper2 = new FrameLayout(getActivity());
        wrapper2.setLayoutParams(new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.MATCH_PARENT));
        wrapper2.setId(2);

        final FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.WRAP_CONTENT);
        params.topMargin = 100;

        final TextView text = new TextView(getActivity());
        text.setLayoutParams(params);
        text.setText("fragment 2");
        wrapper2.addView(text);

        return wrapper2;
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        getFragmentManager().beginTransaction().addToBackStack(null)
                .add(2, new Fragment2()).commit();
    }
}

public class Fragment2 extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        final FrameLayout wrapper3 = new FrameLayout(getActivity());
        wrapper3.setLayoutParams(new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.MATCH_PARENT));
        wrapper3.setId(3);

        final FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.WRAP_CONTENT);
        params.topMargin = 200;

        final TextView text = new TextView(getActivity());
        text.setLayoutParams(params);
        text.setText("fragment 3");
        wrapper3.addView(text);

        return wrapper3;
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        getChildFragmentManager().beginTransaction().addToBackStack(null)
                .add(3, new Fragment3()).commit();
    }
}

public class Fragment3 extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        final FrameLayout wrapper4 = new FrameLayout(getActivity());
        wrapper4.setLayoutParams(new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.MATCH_PARENT));
        wrapper4.setId(4);

        final FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.WRAP_CONTENT);
        params.topMargin = 300;

        final TextView text = new TextView(getActivity());
        text.setLayoutParams(params);
        text.setText("fragment 4");
        wrapper4.addView(text);

        return wrapper4;
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        getChildFragmentManager().beginTransaction().addToBackStack(null)
                .add(4, new Fragment4()).commit();
    }
}

public class Fragment4 extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        final FrameLayout wrapper5 = new FrameLayout(getActivity());
        wrapper5.setLayoutParams(new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.MATCH_PARENT));
        wrapper5.setId(5);

        final FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.WRAP_CONTENT);
        params.topMargin = 400;

        final TextView text = new TextView(getActivity());
        text.setLayoutParams(params);
        text.setText("fragment 5");
        wrapper5.addView(text);

        return wrapper5;
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
    }
}

}

kode contoh dengan getFragmentManager ():

public class FragmentceptionActivity extends FragmentActivity {

@Override
protected void onCreate(Bundle arg0) {
    super.onCreate(arg0);

    final FrameLayout wrapper1 = new FrameLayout(this);
    wrapper1.setLayoutParams(new FrameLayout.LayoutParams(
            FrameLayout.LayoutParams.MATCH_PARENT,
            FrameLayout.LayoutParams.MATCH_PARENT));
    wrapper1.setId(1);

    final FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
            FrameLayout.LayoutParams.MATCH_PARENT,
            FrameLayout.LayoutParams.WRAP_CONTENT);
    params.topMargin = 0;

    final TextView text = new TextView(this);
    text.setLayoutParams(params);
    text.setText("fragment 1");
    wrapper1.addView(text);

    setContentView(wrapper1);

    getSupportFragmentManager().beginTransaction().addToBackStack(null)
            .add(1, new Fragment1()).commit();
}

public class Fragment1 extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        final FrameLayout wrapper2 = new FrameLayout(getActivity());
        wrapper2.setLayoutParams(new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.MATCH_PARENT));
        wrapper2.setId(2);

        final FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.WRAP_CONTENT);
        params.topMargin = 100;

        final TextView text = new TextView(getActivity());
        text.setLayoutParams(params);
        text.setText("fragment 2");
        wrapper2.addView(text);

        return wrapper2;
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        getFragmentManager().beginTransaction().addToBackStack(null)
                .add(2, new Fragment2()).commit();
    }
}

public class Fragment2 extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        final FrameLayout wrapper3 = new FrameLayout(getActivity());
        wrapper3.setLayoutParams(new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.MATCH_PARENT));
        wrapper3.setId(3);

        final FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.WRAP_CONTENT);
        params.topMargin = 200;

        final TextView text = new TextView(getActivity());
        text.setLayoutParams(params);
        text.setText("fragment 3");
        wrapper3.addView(text);

        return wrapper3;
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        getFragmentManager().beginTransaction().addToBackStack(null)
                .add(3, new Fragment3()).commit();
    }
}

public class Fragment3 extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        final FrameLayout wrapper4 = new FrameLayout(getActivity());
        wrapper4.setLayoutParams(new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.MATCH_PARENT));
        wrapper4.setId(4);

        final FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.WRAP_CONTENT);
        params.topMargin = 300;

        final TextView text = new TextView(getActivity());
        text.setLayoutParams(params);
        text.setText("fragment 4");
        wrapper4.addView(text);

        return wrapper4;
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        getFragmentManager().beginTransaction().addToBackStack(null)
                .add(4, new Fragment4()).commit();
    }
}

public class Fragment4 extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        final FrameLayout wrapper5 = new FrameLayout(getActivity());
        wrapper5.setLayoutParams(new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.MATCH_PARENT));
        wrapper5.setId(5);

        final FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.WRAP_CONTENT);
        params.topMargin = 400;

        final TextView text = new TextView(getActivity());
        text.setLayoutParams(params);
        text.setText("fragment 5");
        wrapper5.addView(text);

        return wrapper5;
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
    }
}

}

hmmm itu sangat menarik. Saya juga tidak mengharapkan perilaku itu. Jika ini masalahnya, maka tampaknya kita mungkin telah mengganti OnBackPressed dan dalam metode itu mendapatkan fragmen dengan fragmen turunan kemudian mendapatkan ChildFragmentManager untuk mengeksekusi transaksi pop.
Marco RS

@Marco, Tepat, tapi bagaimanapun itu adalah solusi. Mengapa API tidak berfungsi dengan baik?
Egor

Jawaban:


67

Solusi ini mungkin versi yang lebih baik dari jawaban @Sean:

@Override
public void onBackPressed() {
    // if there is a fragment and the back stack of this fragment is not empty,
    // then emulate 'onBackPressed' behaviour, because in default, it is not working
    FragmentManager fm = getSupportFragmentManager();
    for (Fragment frag : fm.getFragments()) {
        if (frag.isVisible()) {
            FragmentManager childFm = frag.getChildFragmentManager();
            if (childFm.getBackStackEntryCount() > 0) {
                childFm.popBackStack();
                return;
            }
        }
    }
    super.onBackPressed();
}

Sekali lagi, saya menyiapkan solusi ini berdasarkan jawaban @Sean di atas.

Seperti yang dikatakan @ AZ13, solusi ini hanya dapat dilakukan dalam situasi fragmen turunan satu level. Dalam kasus beberapa tingkat fragmen, pekerjaan menjadi sedikit rumit, jadi saya sarankan mencoba solusi ini hanya kasus yang layak saya katakan. =)

Catatan: Karena getFragmentsmetode sekarang menjadi metode pribadi, solusi ini tidak akan berfungsi. Anda dapat memeriksa komentar untuk menemukan tautan yang menyarankan solusi tentang situasi ini.


2
林奕 忠 jawaban harus digunakan sebagai pengganti yang ini - ini dengan benar menangani beberapa fragmen bersarang
Hameno

Apakah ini benar-benar kode yang berfungsi? Bahkan getFragments bukan metode publik? stackoverflow.com/a/42572412/4729203
wonsuc

Ini berfungsi sekali tetapi mungkin tidak berfungsi sekarang jika getFragments tidak lagi untuk publik.
ismailarilik

1
getFragmentssekarang menjadi publik.
grrigore

ini tidak berfungsi dengan benar jika Anda memiliki FragA -> Frag B -> Frag C di dalam Pager ...
Zhar

57

Sepertinya bug. Lihat di: http://code.google.com/p/android/issues/detail?id=40323

Untuk solusi yang berhasil saya gunakan (seperti yang disarankan dalam komentar):

    @Override
public void onBackPressed() {

    // If the fragment exists and has some back-stack entry
    if (mActivityDirectFragment != null && mActivityDirectFragment.getChildFragmentManager().getBackStackEntryCount() > 0){
        // Get the fragment fragment manager - and pop the backstack
        mActivityDirectFragment.getChildFragmentManager().popBackStack();
    }
    // Else, nothing in the direct fragment back stack
    else{
        // Let super handle the back press
        super.onBackPressed();          
    }
}

12
Sayangnya ini bukan solusi yang tepat dan hanya solusi untuk kasus yang paling sederhana. Anggap saja Anda memiliki fragmen, yang berisi fragmen lain, yang berisi fragmen lain, dan seterusnya. Kemudian Anda harus menyebarkan panggilan ke fragmen terakhir yang berisi satu atau lebih fragmen lain: /
AZ13

@MuhammadBabar Tidak, Anda tidak akan. Jika nol, bagian kedua dari pernyataan tidak akan dievaluasi karena sudah ditentukan sebagai salah. operannya adalah & dan bukan | .
Sean

25

Jawaban sebenarnya untuk pertanyaan ini ada di fungsi Fragment Transaction yang disebut setPrimaryNavigationFragment.

/**
 * Set a currently active fragment in this FragmentManager as the primary navigation fragment.
 *
 * <p>The primary navigation fragment's
 * {@link Fragment#getChildFragmentManager() child FragmentManager} will be called first
 * to process delegated navigation actions such as {@link FragmentManager#popBackStack()}
 * if no ID or transaction name is provided to pop to. Navigation operations outside of the
 * fragment system may choose to delegate those actions to the primary navigation fragment
 * as returned by {@link FragmentManager#getPrimaryNavigationFragment()}.</p>
 *
 * <p>The fragment provided must currently be added to the FragmentManager to be set as
 * a primary navigation fragment, or previously added as part of this transaction.</p>
 *
 * @param fragment the fragment to set as the primary navigation fragment
 * @return the same FragmentTransaction instance
 */
public abstract FragmentTransaction setPrimaryNavigationFragment(Fragment fragment);

Anda harus menyetel fungsi ini pada fragmen induk awal saat aktivitas menambahkannya. Saya memiliki fungsi replaceFragment di dalam aktivitas saya yang terlihat seperti ini:

public void replaceFragment(int containerId, BaseFragment fragment, boolean addToBackstack) {
    FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
    fragmentTransaction.setPrimaryNavigationFragment(fragment);
    if (addToBackstack) {
        fragmentTransaction.addToBackStack(fragment.TAG);
    }

    fragmentTransaction.replace(containerId, fragment).commit();
}

Ini memberi Anda perilaku yang sama seperti jika Anda mengklik kembali dari Fragmen B biasa kembali ke Fragmen A, kecuali sekarang ini juga pada fragmen turunan!


3
Great ditemukan! Ini menyelesaikan masalah yang disebutkan oleh OP dengan cara yang jauh lebih bersih dan lebih sedikit hackie.
Jona

Itu macet kecuali bagian dari perpustakaan navigasi sebagai HavHostFragment
Didi

22

Solusi ini mungkin versi yang lebih baik dari jawaban @ismailarilik:

Versi Fragmen Bersarang

private boolean onBackPressed(FragmentManager fm) {
    if (fm != null) {
        if (fm.getBackStackEntryCount() > 0) {
            fm.popBackStack();
            return true;
        }

        List<Fragment> fragList = fm.getFragments();
        if (fragList != null && fragList.size() > 0) {
            for (Fragment frag : fragList) {
                if (frag == null) {
                    continue;
                }
                if (frag.isVisible()) {
                    if (onBackPressed(frag.getChildFragmentManager())) {
                        return true;
                    }
                }
            }
        }
    }
    return false;
}

@Override
public void onBackPressed() {
    FragmentManager fm = getSupportFragmentManager();
    if (onBackPressed(fm)) {
        return;
    }
    super.onBackPressed();
}

tampaknya tidak berfungsi ketika ada beberapa tingkat fragmen bersarang
splinter123

Cloud Anda memberi saya arsitektur Anda dari fragmen bersarang? Di aplikasi saya, ini berfungsi. Mungkin aku melewatkan sesuatu.
林奕 忠

9
Urutan untuk meletuskan backstack tidak seperti yang saya harapkan dalam contoh ini. Saya yakin Anda pertama-tama harus menemukan fragmen yang terlihat paling dalam bersarang di pohon dan mencoba untuk meletuskan tumpukan di atasnya, kemudian secara bertahap naik ke pohon untuk mencari fragmen lain yang terlihat di mana Anda dapat meletuskan tumpukan belakang sampai Anda mendapatkannya. Perbaikan cepat yang menangani kasus paling sederhana adalah dengan mengubah fungsi onBackPressed (FragmentManager fm) dan mengganti blok yang muncul di backstack dengan yang menyebutkan fragmen. Dengan begitu, memunculkan backstack dalam fragmen anak bersarang yang lebih dalam akan terjadi pertama kali
Theo

1
Selain itu, jika Anda memiliki banyak fragmen bersarang, masing-masing dengan fragmen turunannya yang mungkin menutupi sebagian layar, gagasan untuk memiliki backstack terpisah untuk setiap pengelola fragmen turunan tidak lagi masuk akal. Backstack harus disatukan di semua fragmen dalam aktivitas, dan melacak perubahan fragmen sesuai urutan terjadinya, terlepas dari level bertingkat dari fragmen tersebut.
Theo

Apakah mungkin untuk menggunakan solusi ini tanpa SupportFragmentManagerdan sebagai gantinya hanya Standar FragmentManager?
Peter Chappy

13

Dengan jawaban ini, ia akan menangani pemeriksaan ulang rekursif dan memberikan setiap fragmen kesempatan untuk mengganti perilaku default. Ini berarti Anda dapat memiliki fragmen yang menghosting ViewPager melakukan sesuatu yang istimewa seperti menggulir ke halaman itu sebagai back-stack, atau menggulir ke halaman beranda dan kemudian kembali tekan keluar berikutnya.

Tambahkan ini ke Aktivitas Anda yang memperluas AppCompatActivity.

@Override
public void onBackPressed()
{
    if(!BaseFragment.handleBackPressed(getSupportFragmentManager())){
        super.onBackPressed();
    }
}

Tambahkan ini ke BaseFragment Anda atau kelas tempat semua fragmen Anda mewarisi.

public static boolean handleBackPressed(FragmentManager fm)
{
    if(fm.getFragments() != null){
        for(Fragment frag : fm.getFragments()){
            if(frag != null && frag.isVisible() && frag instanceof BaseFragment){
                if(((BaseFragment)frag).onBackPressed()){
                    return true;
                }
            }
        }
    }
    return false;
}

protected boolean onBackPressed()
{
    FragmentManager fm = getChildFragmentManager();
    if(handleBackPressed(fm)){
        return true;
    }
    else if(getUserVisibleHint() && fm.getBackStackEntryCount() > 0){
        fm.popBackStack();
        return true;
    }
    return false;
}

ini adalah jawaban yang bagus, bekerja pada API level 23/24 dan dengan dukungan lib 24.1.1
Rinav

Telah menggunakan ini untuk beberapa waktu sekarang, ini adalah solusi yang bagus - tetapi baru-baru ini memiliki peringatan kompilasi tentang akses langsung ke getFragments () dari grup perpustakaan luar. Apakah Anda sudah memperbarui dengan work-around?
Simon Huckett

apakah ada sesuatu yang diperbaiki atau pada tahun 2020 kami masih memiliki masalah dan harus menerapkan solusi Anda ?? !!
Zhar

3

Kode ini akan menavigasi pohon pengelola fragmen dan mengembalikan yang terakhir ditambahkan yang memiliki fragmen apa pun yang dapat dikeluarkan dari tumpukan:

private FragmentManager getLastFragmentManagerWithBack(FragmentManager fm)
{
  FragmentManager fmLast = fm;

  List<Fragment> fragments = fm.getFragments();

  for (Fragment f : fragments)
  {
    if ((f.getChildFragmentManager() != null) && (f.getChildFragmentManager().getBackStackEntryCount() > 0))
    {
      fmLast = f.getFragmentManager();
      FragmentManager fmChild = getLastFragmentManagerWithBack(f.getChildFragmentManager());

      if (fmChild != fmLast)
        fmLast = fmChild;
    }
  }

  return fmLast;
}

Panggil metode:

@Override
public void onBackPressed()
{
  FragmentManager fm = getLastFragmentManagerWithBack(getSupportFragmentManager());

  if (fm.getBackStackEntryCount() > 0)
  {
    fm.popBackStack();
    return;
  }

  super.onBackPressed();
}

Ini berfungsi untuk semua fragmen bersarang jika terdaftar dengan benar !! +100
Pratik Saluja

2

Alasannya adalah Aktivitas Anda berasal dari FragmentActivity, yang menangani penekanan tombol KEMBALI (lihat baris 173 dari FragmentActivity .

Dalam aplikasi kita, saya menggunakan ViewPager (dengan fragmen) dan setiap fragmen dapat memiliki fragmen bersarang. Cara saya menangani ini adalah dengan:

  • mendefinisikan antarmuka OnBackKeyPressedListener dengan satu metode void onBackKeyPressed()
  • mengimplementasikan antarmuka ini dalam fragmen "teratas" yang ditampilkan ViewPager
  • mengganti onKeyDown dan mendeteksi pers BACK, dan memanggil onBackKeyPressed dalam fragmen yang sedang aktif di halaman tampilan.

Perhatikan juga, yang saya gunakan getChildFragmentManager()dalam fragmen untuk menyarangkan fragmen dengan benar. Pembahasan dan penjelasannya bisa kalian lihat di postingan android-developers ini .


2

jika Anda menggunakan fragmen dalam sebuah fragmen, gunakan getChildFragmentManager()

getChildFragmentManager().beginTransaction().replace(R.id.fragment_name, fragment).addToBackStack(null).commit();

jika Anda menggunakan fragmen anak dan menggantinya gunakan getParentFragmentManager()

getParentFragmentManager().beginTransaction().replace(R.id.fragment_name, fragment).addToBackStack(null).commit();

jika keduanya tidak berfungsi untuk Anda, coba ini getActivity().getSupportFragmentManager()

getActivity().getSupportFragmentManager().beginTransaction().replace(R.id.fragment_name, fragment).addToBackStack(null).commit();

1

saya bisa menangani tumpukan belakang fragmen dengan menambahkan ke fragmen induk metode ini pada metode onCreate View () dan meneruskan tampilan root.

private void catchBackEvent(View v){
    v.setFocusableInTouchMode(true);
    v.requestFocus();
    v.setOnKeyListener( new OnKeyListener()
    {
        @Override
        public boolean onKey( View v, int keyCode, KeyEvent event )
        {
            if( keyCode == KeyEvent.KEYCODE_BACK )
            {
                if(isEnableFragmentBackStack()){
                    getChildFragmentManager().popBackStack();
                                    setEnableFragmentBackStack(false);
                    return true;
                }
                else
                    return false;   
            }
            return false;
        }
    } );
}

Metode isEnableFragmentBackStack () adalah tanda boolean untuk mengetahui saat saya berada di fragmen utama atau yang berikutnya.

Pastikan bahwa ketika Anda mengkomit fragmen yang perlu memiliki tumpukan, Anda harus menambahkan Metode addToBackstack.


1

Solusi ini mungkin lebih baik, karena memeriksa semua level fragmen bersarang:

 /**
 * This method will go check all the back stacks of the added fragments and their nested fragments
 * to the the {@code FragmentManager} passed as parameter.
 * If there is a fragment and the back stack of this fragment is not empty,
 * then emulate 'onBackPressed' behaviour, because in default, it is not working.
 *
 * @param fm the fragment manager to which we will try to dispatch the back pressed event.
 * @return {@code true} if the onBackPressed event was consumed by a child fragment, otherwise {@code false}.
 */
public static boolean dispatchOnBackPressedToFragments(FragmentManager fm) {

    List<Fragment> fragments = fm.getFragments();
    boolean result;
    if (fragments != null && !fragments.isEmpty()) {
        for (Fragment frag : fragments) {
            if (frag != null && frag.isAdded() && frag.getChildFragmentManager() != null) {
                // go to the next level of child fragments.
                result = dispatchOnBackPressedToFragments(frag.getChildFragmentManager());
                if (result) return true;
            }
        }
    }

    // if the back stack is not empty then we pop the last transaction.
    if (fm.getBackStackEntryCount() > 0) {
        fm.popBackStack();
        fm.executePendingTransactions();
        return true;
    }

    return false;
}

dalam aktivitas onBackPressedAnda, Anda dapat menyebutnya seperti ini:

FragmentManager fm = getSupportFragmentManager();
                // if there is a fragment and the back stack of this fragment is not empty,
                // then emulate 'onBackPressed' behaviour, because in default, it is not working
                if (!dispatchOnBackPressedToFragments(fm)) {
                    // if no child fragment consumed the onBackPressed event,
                    // we execute the default behaviour.
                    super.onBackPressed();
                }

1

Terima kasih kepada semua orang atas bantuan mereka, ini (versi tweak) berfungsi untuk saya:

@Override
public void onBackPressed() {
    if (!recursivePopBackStack(getSupportFragmentManager())) {
        super.onBackPressed();
    }
}

/**
 * Recursively look through nested fragments for a backstack entry to pop
 * @return: true if a pop was performed
 */
public static boolean recursivePopBackStack(FragmentManager fragmentManager) {
    if (fragmentManager.getFragments() != null) {
        for (Fragment fragment : fragmentManager.getFragments()) {
            if (fragment != null && fragment.isVisible()) {
                boolean popped = recursivePopBackStack(fragment.getChildFragmentManager());
                if (popped) {
                    return true;
                }
            }
        }
    }

    if (fragmentManager.getBackStackEntryCount() > 0) {
        fragmentManager.popBackStack();
        return true;
    }

    return false;
}

CATATAN: Anda mungkin juga ingin menyetel warna latar belakang dari fragmen bersarang ini ke warna latar belakang jendela tema aplikasi, karena secara default transparan. Agak di luar cakupan pertanyaan ini, tetapi diselesaikan dengan menyelesaikan atribut android.R.attr.windowBackground, dan menyetel latar belakang tampilan Fragmen ke ID sumber daya tersebut.


1

Lebih dari 5 tahun dan masalah ini masih relevan. Jika Anda tidak ingin menggunakan fragmentManager.getFragments () karena batasannya. Perluas dan gunakan kelas di bawah ini:

NestedFragmentActivity.java

abstract public class NestedFragmentActivity extends AppCompatActivity {

    private final Stack<Integer> mActiveFragmentIdStack = new Stack<>();
    private final Stack<String> mActiveFragmentTagStack = new Stack<>();

    @Override
    public void onBackPressed() {
        if (mActiveFragmentIdStack.size() > 0 && mActiveFragmentTagStack.size() > 0) {

            // Find by id
            int lastFragmentId = mActiveFragmentIdStack.lastElement();
            NestedFragment nestedFragment = (NestedFragment) getSupportFragmentManager().findFragmentById(lastFragmentId);

            // If cannot find by id, find by tag
            if (nestedFragment == null) {
                String lastFragmentTag = mActiveFragmentTagStack.lastElement();
                nestedFragment = (NestedFragment) getSupportFragmentManager().findFragmentByTag(lastFragmentTag);
            }

            if (nestedFragment != null) {
                nestedFragment.onBackPressed();
            }

            // If cannot find by tag, then simply pop
            mActiveFragmentTagStack.pop();
            mActiveFragmentIdStack.pop();

        } else {
            super.onBackPressed();
        }
    }

    public void addToBackStack(int fragmentId, String fragmentTag) {
        mActiveFragmentIdStack.add(fragmentId);
        mActiveFragmentTagStack.add(fragmentTag);
    }
}

NestedFragment.java

abstract public class NestedFragment extends Fragment {

    private final Stack<Integer> mActiveFragmentIdStack = new Stack<>();
    private final Stack<String> mActiveFragmentTagStack = new Stack<>();

    private NestedFragmentActivity mParentActivity;
    private NestedFragment mParentFragment;

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);

        if (getParentFragment() == null) {
            try {
                mParentActivity = (NestedFragmentActivity) context;
            } catch (ClassCastException e) {
                throw new ClassCastException(context.toString()
                        + " must implement " + NestedFragmentActivity.class.getName());
            }
        } else {
            try {
                mParentFragment = (NestedFragment) getParentFragment();
            } catch (ClassCastException e) {
                throw new ClassCastException(getParentFragment().getClass().toString()
                        + " must implement " + NestedFragment.class.getName());
            }
        }
    }

    public void onBackPressed() {

        if (mActiveFragmentIdStack.size() > 0 && mActiveFragmentTagStack.size() > 0) {

            // Find by id
            int lastFragmentId = mActiveFragmentIdStack.lastElement();
            NestedFragment nestedFragment = (NestedFragment) getChildFragmentManager().findFragmentById(lastFragmentId);

            // If cannot find by id, find by tag
            if (nestedFragment == null) {
                String lastFragmentTag = mActiveFragmentTagStack.lastElement();
                nestedFragment = (NestedFragment) getChildFragmentManager().findFragmentByTag(lastFragmentTag);
            }

            if (nestedFragment != null) {
                nestedFragment.onBackPressed();
            }

            // If cannot find by tag, then simply pop
            mActiveFragmentIdStack.pop();
            mActiveFragmentTagStack.pop();

        } else {
            getChildFragmentManager().popBackStack();
        }
    }

    private void addToBackStack(int fragmentId, String fragmentTag) {
        mActiveFragmentIdStack.add(fragmentId);
        mActiveFragmentTagStack.add(fragmentTag);
    }

    public void addToParentBackStack() {
        if (mParentFragment != null) {
            mParentFragment.addToBackStack(getId(), getTag());
        } else if (mParentActivity != null) {
            mParentActivity.addToBackStack(getId(), getTag());
        }
    }
}

Penjelasan:

Setiap aktivitas dan fragmen yang diperluas dari kelas-kelas di atas mengelola back-stack mereka sendiri untuk masing-masing anak mereka, anak dari anak-anak mereka, dan seterusnya. Backstack hanyalah catatan tag / id "fragmen aktif". Jadi peringatannya adalah selalu memberikan tag dan / atau id untuk fragmen Anda.

Saat menambahkan ke backstack di childFragmentManager, Anda juga perlu memanggil "addToParentBackStack ()". Ini memastikan bahwa tag / id fragmen ditambahkan di fragmen / aktivitas induk untuk muncul nanti.

Contoh:

    getChildFragmentManager().beginTransaction().replace(
            R.id.fragment,
            fragment,
            fragment.getTag()
    ).addToBackStack(null).commit();
    addToParentBackStack();

1

Saya telah menerapkannya dengan benar jika ada yang tidak menemukan jawaban sampai sekarang

cukup tambahkan metode ini pada fragmen bersarang anak Anda

   @Override
public void onCreate(@Nullable Bundle savedInstanceState) {
    // This callback will only be called when MyFragment is at least Started.
    OnBackPressedCallback callback = new OnBackPressedCallback(true ) {
        @Override
        public void handleOnBackPressed() {
            // Handle the back button event
            FragmentManager fm= getFragmentManager();
            if (fm != null) {
                if (fm.getBackStackEntryCount() > 0) {
                    fm.popBackStack();
                    Log.e( "Frag","back" );

                }

                List<Fragment> fragList = fm.getFragments();
                if (fragList != null && fragList.size() > 0) {
                    for (Fragment frag : fragList) {
                        if (frag == null) {
                            continue;
                        }
                        if (frag.isVisible()) {
                          Log.e( "Frag","Visible" );
                        }
                    }
                }
            }


        }
    };
    requireActivity().getOnBackPressedDispatcher().addCallback(this, callback);
    super.onCreate( savedInstanceState );
}

0

Saya memecahkan masalah ini dengan menyimpan fragmen yang saat ini dibuka di properti aktivitas. Kemudian saya mengganti metode

 // INSIDE ACTIVITY
 override fun onBackPressed() {
    val fragment = currentFragment

    if(fragment != null && fragment.childFragmentManager.fragments.any()){
        fragment.childFragmentManager.popBackStack()
    }
    else {
        super.onBackPressed()
    }
}

Beginilah cara saya menambahkan fragmen anak ke dalam fragmen yang saat ini dibuka dari dalam dirinya sendiri.

 // INSIDE FRAGMENT
 menu_fragment_view.setBackgroundColor(-((Math.random() * 10000 ).toInt() % 30000)) // to see change
 add_fragment_button.setOnClickListener {
        val transaction = childFragmentManager.beginTransaction()

        transaction.add(R.id.menu_fragment_fragment_holder, MenuFragment())

        transaction.addToBackStack(null)

        transaction.commit()
    }

Ini adalah layout xml dari fragmen induk dan fragmen yang ditambahkan

 <?xml version="1.0" encoding="utf-8"?>
 <androidx.constraintlayout.widget.ConstraintLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:id="@+id/menu_fragment_view">
     <Button
         app:layout_constraintTop_toTopOf="parent"
         app:layout_constraintLeft_toLeftOf="parent"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:id="@+id/add_fragment_button"
         android:text="Just add fragment"/>
     <FrameLayout
         android:id="@+id/menu_fragment_fragment_holder"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         app:layout_constraintTop_toTopOf="parent"
         app:layout_constraintLeft_toLeftOf="parent"/> 
</androidx.constraintlayout.widget.ConstraintLayout>

0

Setelah mengamati beberapa solusi yang disajikan di sini, saya menemukan bahwa untuk memungkinkan fleksibilitas & kontrol untuk fragmen induk seperti saat memunculkan tumpukan atau kapan tindakan mundur harus diabaikan olehnya, saya lebih suka menggunakan implementasi seperti itu:

Mendefinisikan antarmuka "ParentFragment":

interface ParentFragment {
/**
 * Fragments that host child fragments and want to control their BackStack behaviour when the back button is pressed should implement this
 *
 * @return true if back press was handled, false otherwise
 */
fun onBackPressed(): Boolean

}

Mengganti "onBackPressed" di aktivitas induk (atau di BaseActivity):

override fun onBackPressed() {
    val fm: FragmentManager = supportFragmentManager
    for (frag in fm.fragments) {
        if (frag.isVisible && frag is ParentFragment && frag.onBackPressed()) {
            return
        }
    }
    super.onBackPressed()
}

Dan kemudian biarkan fragmen induk menangani sesuai keinginannya, misalnya:

override fun onBackPressed(): Boolean {
    val childFragment = childFragmentManager.findFragmentByTag(SomeChildFragment::class.java.simpleName)
    if (childFragment != null && childFragment.isVisible) {
        // Only for that case, pop the BackStack (perhaps when other child fragments are visible don't)
        childFragmentManager.popBackStack()
        return true
    }
    return false
}

Hal ini memungkinkan untuk menghindari pemikiran bahwa ada beberapa fragmen turunan yang sah untuk dihapus saat menggunakan halaman tampilan misalnya (dan jumlah entri back stack> 0).


-1

Jika Anda memiliki DialogFragmentyang pada gilirannya memiliki fragmen bersarang, 'solusinya' sedikit berbeda. Alih-alih menyetel onKeyListenerke rootView, Anda harus melakukannya dengan Dialog. Anda juga akan menyiapkan DialogInterface.OnKeyListenerdan bukan yang Viewsatu. Tentu ingat addToBackStack!

Btw, memiliki 1 fragmen di backstack untuk mendelegasikan panggilan kembali ke aktivitas adalah kasus penggunaan pribadi saya. Skenario umum mungkin untuk hitungan menjadi 0.

Inilah yang harus Anda lakukan dalam onCreateDialog

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        Dialog dialog =  super.onCreateDialog(savedInstanceState);
        dialog.setOnKeyListener(new DialogInterface.OnKeyListener() {
            @Override
            public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
                if(keyCode == KeyEvent.KEYCODE_BACK){
                    FragmentManager cfm = getChildFragmentManager();
                    if(cfm.getBackStackEntryCount()>1){
                        cfm.popBackStack();
                        return true;
                    }   
                }   
                return false;
            }
        });
        return dialog;
    }

Tidak terlalu bisa dimengerti atau tidak lengkap. DialogFragment atau Superclass Fragment tidak memiliki setOnKeyListener.
Ixx

@Ixx Anda harus mengaktifkan pendengar Dialog(bukan DialogFragment) dan pendengar yang Anda gunakan adalah DialogInterface.OnKeyListener- kode berfungsi dan lengkap (dan sedang digunakan). Mengapa Anda tidak memberikan situasi Anda dan mungkin saya dapat membantu.
Sachin Ahuja

-1

untuk ChildFragments, ini berfungsi ..

@Override
    public void onBackPressed() {

 if (getSupportFragmentManager().getBackStackEntryCount() > 0) {
            getSupportFragmentManager().popBackStack();
        } else {
            doExit(); //super.onBackPressed();
        }
}
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.