Fragmen onResume dari back-stack

96

Saya menggunakan paket kompatibilitas untuk menggunakan Fragmen dengan Android 2.2. Saat menggunakan fragmen, dan menambahkan transisi di antara mereka ke backstack, saya ingin mencapai perilaku onResume yang sama dari suatu aktivitas, yaitu setiap kali sebuah fragmen dibawa ke "latar depan" (terlihat oleh pengguna) setelah keluar dari backstack, saya ingin semacam callback diaktifkan di dalam fragmen (misalnya untuk melakukan perubahan tertentu pada resource UI bersama).

Saya melihat bahwa tidak ada callback bawaan dalam kerangka fragmen. apakah ada praktik yang baik untuk mencapai ini?

oriharel
sumber
13
Pertanyaan bagus. Saya mencoba mengubah judul bilah tindakan tergantung pada fragmen apa yang terlihat sebagai kasus penggunaan untuk skenario ini dan bagi saya sepertinya ada callback yang hilang di API.
Manfred Moser
3
Aktivitas Anda bisa menyetel judul asalkan mengetahui fragmen mana yang sedang ditampilkan pada satu waktu. Juga saya kira Anda bisa melakukan sesuatu onCreateViewyang akan dipanggil untuk fragmen setelah pop.
PJL
1
@PJL +1 Menurut referensi begitulah seharusnya dilakukan. Namun saya lebih suka cara pendengar
momo

Jawaban:

115

Karena kurangnya solusi yang lebih baik, saya membuat ini berfungsi untuk saya: Asumsikan saya memiliki 1 aktivitas (MyActivity) dan beberapa fragmen yang saling menggantikan (hanya satu yang terlihat pada satu waktu).

Di MyActivity, tambahkan listener ini:

getSupportFragmentManager().addOnBackStackChangedListener(getListener());

(Seperti yang Anda lihat, saya menggunakan paket kompatibilitas).

Implementasi getListener:

private OnBackStackChangedListener getListener()
    {
        OnBackStackChangedListener result = new OnBackStackChangedListener()
        {
            public void onBackStackChanged() 
            {                   
                FragmentManager manager = getSupportFragmentManager();

                if (manager != null)
                {
                    MyFragment currFrag = (MyFragment) manager.findFragmentById(R.id.fragmentItem);

                    currFrag.onFragmentResume();
                }                   
            }
        };

        return result;
    }

MyFragment.onFragmentResume()akan dipanggil setelah tombol "Kembali" ditekan. beberapa peringatan:

  1. Ini mengasumsikan Anda menambahkan semua transaksi ke backstack (menggunakan FragmentTransaction.addToBackStack())
  2. Ini akan diaktifkan pada setiap perubahan tumpukan (Anda dapat menyimpan hal-hal lain di back-stack seperti animasi) sehingga Anda mungkin mendapatkan beberapa panggilan untuk contoh fragmen yang sama.
oriharel
sumber
3
Anda harus menerima jawaban Anda sendiri sebagai benar jika berhasil untuk Anda.
powerj1984
7
Bagaimana cara kerjanya dalam hal id fragmen yang Anda kueri? Apa perbedaannya untuk setiap fragmen dan yang benar ditemukan di back-stack?
Manfred Moser
2
@Warpzit metode itu tidak dipanggil, dan dokumen android diam-diam mengakui bahwa itu tidak akan pernah dipanggil (ini hanya dipanggil ketika aktivitas dilanjutkan - yang tidak pernah, jika aktivitas itu sudah aktif)
Adam
2
Konyol bahwa kita harus melakukan ini! Tapi senang ada orang lain yang bisa menemukan "fitur" ini
stevebot
3
onResume () TIDAK dipanggil
Marty Miller
33

Saya telah sedikit mengubah solusi yang disarankan. Bekerja lebih baik untuk saya seperti itu:

private OnBackStackChangedListener getListener() {
    OnBackStackChangedListener result = new OnBackStackChangedListener() {
        public void onBackStackChanged() {
            FragmentManager manager = getSupportFragmentManager();
            if (manager != null) {
                int backStackEntryCount = manager.getBackStackEntryCount();
                if (backStackEntryCount == 0) {
                    finish();
                }
                Fragment fragment = manager.getFragments()
                                           .get(backStackEntryCount - 1);
                fragment.onResume();
            }
        }
    };
    return result;
}
Brotoo 25
sumber
1
tolong jelaskan perbedaannya, dan mengapa menggunakan ini.
Math chiller
2
Perbedaan antara solusi saya dan solusi sebelumnya adalah bahwa pada setiap perubahan fragmen seperti menambahkan, mengganti, atau mundur dalam tumpukan fragmen, metode "onResume" dari fragmen teratas akan dipanggil. Tidak ada id fragmen yang di-hardcode dalam metode ini. Pada dasarnya ini memanggil onResume () dalam fragmen yang Anda lihat pada setiap perubahan, tidak peduli apakah itu sudah dimuat di memori atau belum.
Brotoo25
9
Tidak ada catatan untuk metode yang disebut getFragments()di SupportFragmentManager ... -1: - /
Protostome
1
OnResume () Disebut. Tapi Saya Mendapatkan Layar Kosong..Ada cara lain untuk mengatasi masalah ini?
kavie
Saya pikir ini mengarah ke Pengecualian ArrayOutOfBounds jika backStackEntryCount adalah 0. Apakah saya salah? Mencoba mengedit postingan tetapi ditolak.
Mike T
6

Setelah popStackBack()Anda dapat menggunakan callback berikut: di onHiddenChanged(boolean hidden)dalam fragmen Anda

pengguna2230304
sumber
2
Tampaknya ini tidak berfungsi dengan fragmen aplikasi compat.
Lo-Tan
Itulah yang saya gunakan. Terima kasih!
Albert Vila Calvo
Solusi paling sederhana! Cukup tambahkan tanda centang jika fragmen saat ini terlihat dan voila, Anda dapat mengatur judul appbar.
EricH206
4

Bagian berikut di Android Developers menjelaskan mekanisme komunikasi Membuat callback peristiwa ke aktivitas . Mengutip satu baris darinya:

Cara yang baik untuk melakukannya adalah dengan mendefinisikan antarmuka callback di dalam fragmen dan mengharuskan aktivitas host mengimplementasikannya. Saat menerima callback melalui antarmuka, aktivitas bisa membagikan informasi dengan fragmen lain dalam tata letak sesuai kebutuhan.

Edit: Fragmen memiliki onStart(...)yang dipanggil saat fragmen terlihat oleh pengguna. Demikian pula onResume(...)saat terlihat dan aktif berjalan. Ini terkait dengan rekan aktivitas mereka. Singkatnya: gunakanonResume()

PJL
sumber
1
Saya hanya mencari metode di kelas Fragmen yang diaktifkan setiap kali ditampilkan kepada pengguna. baik karena FragmentTransaction.add () / replace (), atau FragmentManager.popBackStack ().
oriharel
@oriharel Lihat jawaban yang diperbarui. Saat aktivitas dimuat, Anda akan mendapatkan berbagai panggilan, onAttach, onCreate, onActivityCreated, onStart, onResume. Jika Anda telah memanggil `` setRetainInstance (true) 'maka Anda tidak akan mendapatkan onCreate saat fragmen ditampilkan ulang saat mengklik kembali.
PJL
15
onStart () tidak pernah dipanggil saat mengklik "Kembali" di antara fragmen dalam aktivitas yang sama. Saya mencoba sebagian besar callback dalam dokumentasi dan tidak ada yang dipanggil dalam skenario seperti itu. lihat jawaban saya untuk solusinya.
oriharel
hmm dengan compat lib v4 Saya melihat onResume untuk fragmen saya. Saya suka jawaban @ oriharel meskipun karena itu membedakan antara menjadi terlihat melalui popBackStack daripada hanya dibawa ke latar depan.
PJL
3

Jika sebuah fragmen diletakkan di backstack, Android akan menghancurkan tampilannya. Instance fragmen itu sendiri tidak dimatikan. Cara sederhana untuk memulai adalah dengan mendengarkan acara onViewCreated, dan menempatkan Anda logika "onResume ()" di sana.

boolean fragmentAlreadyLoaded = false;
    @Override
    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
        // TODO Auto-generated method stub
        super.onViewCreated(view, savedInstanceState);

        if (savedInstanceState == null && !fragmentAlreadyLoaded) {
            fragmentAlreadyLoaded = true;

            // Code placed here will be executed once
        }

        //Code placed here will be executed even when the fragment comes from backstack
    }
Franjo
sumber
3

Dalam aktivitas saya onCreate ()

getSupportFragmentManager().addOnBackStackChangedListener(getListener());

Gunakan metode ini untuk menangkap Fragmen tertentu dan memanggil onResume ()

private FragmentManager.OnBackStackChangedListener getListener()
    {
        FragmentManager.OnBackStackChangedListener result = new FragmentManager.OnBackStackChangedListener()
        {
            public void onBackStackChanged()
            {
                Fragment currentFragment = getSupportFragmentManager().findFragmentById(R.id.fragment_container);
                if (currentFragment instanceof YOURFRAGMENT) {
                    currentFragment.onResume();
                }
            }
        };

        return result;
    }
Quan Nguyen
sumber
2

Sedikit ditingkatkan dan dibungkus menjadi solusi manajer.

Hal-hal yang perlu diingat. FragmentManager bukan singleton, ia hanya mengelola Fragmen dalam Aktivitas, jadi dalam setiap aktivitas akan menjadi baru. Selain itu, solusi ini sejauh ini tidak memperhitungkan ViewPager yang memanggil metode setUserVisibleHint () yang membantu mengontrol visibilitas Fragmen.

Jangan ragu untuk menggunakan kelas-kelas berikut saat menangani masalah ini (menggunakan injeksi Dagger2). Panggilan dalam Aktivitas:

//inject FragmentBackstackStateManager instance to myFragmentBackstackStateManager
FragmentManager fragmentManager = getSupportFragmentManager(); 
myFragmentBackstackStateManager.apply(fragmentManager);

FragmentBackstackStateManager.java:

@Singleton
public class FragmentBackstackStateManager {

    private FragmentManager fragmentManager;

    @Inject
    public FragmentBackstackStateManager() {
    }

    private BackstackCallback backstackCallbackImpl = new BackstackCallback() {
        @Override
        public void onFragmentPushed(Fragment parentFragment) {
            parentFragment.onPause();
        }

        @Override
        public void onFragmentPopped(Fragment parentFragment) {
            parentFragment.onResume();
        }
    };

    public FragmentBackstackChangeListenerImpl getListener() {
        return new FragmentBackstackChangeListenerImpl(fragmentManager, backstackCallbackImpl);
    }

    public void apply(FragmentManager fragmentManager) {
        this.fragmentManager = fragmentManager;
        fragmentManager.addOnBackStackChangedListener(getListener());
    }
}

FragmentBackstackChangeListenerImpl.java:

public class FragmentBackstackChangeListenerImpl implements FragmentManager.OnBackStackChangedListener {

    private int lastBackStackEntryCount = 0;
    private final FragmentManager fragmentManager;
    private final BackstackCallback backstackChangeListener;

    public FragmentBackstackChangeListenerImpl(FragmentManager fragmentManager, BackstackCallback backstackChangeListener) {
        this.fragmentManager = fragmentManager;
        this.backstackChangeListener = backstackChangeListener;
        lastBackStackEntryCount = fragmentManager.getBackStackEntryCount();
    }

    private boolean wasPushed(int backStackEntryCount) {
        return lastBackStackEntryCount < backStackEntryCount;
    }

    private boolean wasPopped(int backStackEntryCount) {
        return lastBackStackEntryCount > backStackEntryCount;
    }

    private boolean haveFragments() {
        List<Fragment> fragmentList = fragmentManager.getFragments();
        return fragmentList != null && !fragmentList.isEmpty();
    }


    /**
     * If we push a fragment to backstack then parent would be the one before => size - 2
     * If we pop a fragment from backstack logically it should be the last fragment in the list, but in Android popping a fragment just makes list entry null keeping list size intact, thus it's also size - 2
     *
     * @return fragment that is parent to the one that is pushed to or popped from back stack
     */
    private Fragment getParentFragment() {
        List<Fragment> fragmentList = fragmentManager.getFragments();
        return fragmentList.get(Math.max(0, fragmentList.size() - 2));
    }

    @Override
    public void onBackStackChanged() {
        int currentBackStackEntryCount = fragmentManager.getBackStackEntryCount();
        if (haveFragments()) {
            Fragment parentFragment = getParentFragment();

            //will be null if was just popped and was last in the stack
            if (parentFragment != null) {
                if (wasPushed(currentBackStackEntryCount)) {
                    backstackChangeListener.onFragmentPushed(parentFragment);
                } else if (wasPopped(currentBackStackEntryCount)) {
                    backstackChangeListener.onFragmentPopped(parentFragment);
                }
            }
        }

        lastBackStackEntryCount = currentBackStackEntryCount;
    }
}

BackstackCallback.java:

public interface BackstackCallback {
    void onFragmentPushed(Fragment parentFragment);

    void onFragmentPopped(Fragment parentFragment);
}
AAverin
sumber
0

Ini adalah jawaban yang benar, Anda bisa memanggil onResume () asalkan fragmen dilampirkan ke aktivitas. Atau, Anda dapat menggunakan onAttach dan onDetach

iamlukeyb
sumber
1
Bisakah Anda Memposting Contoh?
kavie
0

onResume () untuk fragmen berfungsi dengan baik ...

public class listBook extends Fragment {

    private String listbook_last_subtitle;
...

    @Override
       public void onCreate(Bundle savedInstanceState) {

        String thisFragSubtitle = (String) getActivity().getActionBar().getSubtitle();
        listbook_last_subtitle = thisFragSubtitle;
       }
...

    @Override
        public void onResume(){
            super.onResume();
            getActivity().getActionBar().setSubtitle(listbook_last_subtitle);
        }
...
Roshan Poudyal
sumber
0
public abstract class RootFragment extends Fragment implements OnBackPressListener {

 @Override
 public boolean onBackPressed() {
  return new BackPressImpl(this).onBackPressed();
 }

 public abstract void OnRefreshUI();

}


public class BackPressImpl implements OnBackPressListener {

 private Fragment parentFragment;

 public BackPressImpl(Fragment parentFragment) {
  this.parentFragment = parentFragment;
 }

 @Override
 public boolean onBackPressed() {
  ((RootFragment) parentFragment).OnRefreshUI();
 }
}

dan tingkat terakhir Frament Anda dari RootFragment untuk melihat efeknya

Luu Quoc Thang
sumber
0

Solusi saya adalah mendapatkan judul bilah tindakan saat ini di Fragmen sebelum menyetelnya ke judul baru. Dengan cara ini, setelah Fragmen muncul, saya dapat mengubah kembali ke judul tersebut.

@Override
public void onResume() {
    super.onResume();
    // Get/Backup current title
    mTitle = ((ActionBarActivity) getActivity()).getSupportActionBar()
            .getTitle();
    // Set new title
    ((ActionBarActivity) getActivity()).getSupportActionBar()
        .setTitle(R.string.this_fragment_title);
}

@Override
public void onDestroy() {
    // Set title back
    ((ActionBarActivity) getActivity()).getSupportActionBar()
        .setTitle(mTitle);

    super.onDestroy();
}
Permen Mahendran
sumber
0

Saya telah menggunakan enum FragmentTagsuntuk mendefinisikan semua kelas fragmen saya.

TAG_FOR_FRAGMENT_A(A.class),
TAG_FOR_FRAGMENT_B(B.class),
TAG_FOR_FRAGMENT_C(C.class)

lulus FragmentTags.TAG_FOR_FRAGMENT_A.name()sebagai tag fragmen.

dan sekarang

@Override
public void onBackPressed(){
   FragmentManager fragmentManager = getFragmentManager();
   Fragment current
   = fragmentManager.findFragmentById(R.id.fragment_container);
    FragmentTags fragmentTag = FragmentTags.valueOf(current.getTag());

  switch(fragmentTag){
    case TAG_FOR_FRAGMENT_A:
        finish();
        break;
   case TAG_FOR_FRAGMENT_B:
        fragmentManager.popBackStack();
        break;
   case default: 
   break;
}
Ali
sumber