Android Fragment back stack ile ilgili sorunlar


121

Android parçasının arka istifinin çalışma şekliyle ilgili büyük bir sorunum var ve sunulan herhangi bir yardım için çok minnettar olurum.

3 Parçanız olduğunu hayal edin

[1] [2] [3]

Kullanıcının gidebilmesini istiyorum, [1] > [2] > [3]ancak geri dönerken (geri düğmesine basarak) [3] > [1].

Tahmin ettiğim gibi addToBackStack(..), bu, parçayı [2]XML'de tanımlanan parça tutucuya getiren işlemi oluştururken çağırmamakla başarılabilirdi .

Ben istemiyorum eğer sanki bu gerçeği görünüyor [2]kullanıcı presler üzerinde geri düğmesini tekrar görünmesini [3], ben deme addToBackStackişlem olduğunu göstermektedir parçası içinde [3]. Bu tamamen sezgisel görünüyor (belki de iOS dünyasından geliyor).

Her neyse, bu şekilde yaparsam, [1] > [2]geri gidip geri bastığımda [1]beklendiği gibi geri gelirim .

Gidip [1] > [2] > [3]sonra geri basarsam [1](beklendiği gibi) geri atlarım . Şimdi [2]tekrar denediğimde tuhaf davranış oluyor [1]. Öncelikle, [3]görünmeden önce kısaca görüntülenir [2]. Bu noktada geri basarsam [3]görüntülenir ve bir kez daha geri basarsam uygulamadan çıkılır.

Burada neler olup bittiğini anlamama yardım eden var mı?


Ve işte ana etkinliğim için düzen xml dosyası:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
          android:layout_width="fill_parent"
          android:layout_height="fill_parent"
          android:orientation="vertical" >

<fragment
        android:id="@+id/headerFragment"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        class="com.fragment_test.FragmentControls" >
    <!-- Preview: layout=@layout/details -->
</fragment>
<FrameLayout
        android:id="@+id/detailFragment"
        android:layout_width="match_parent"
        android:layout_height="fill_parent"

        />



Güncelle Bu, nav heirarchy tarafından oluşturmak için kullandığım koddur

    Fragment frag;
    FragmentTransaction transaction;


    //Create The first fragment [1], add it to the view, BUT Dont add the transaction to the backstack
    frag = new Fragment1();

    transaction = getSupportFragmentManager().beginTransaction();
    transaction.replace(R.id.detailFragment, frag);
    transaction.commit();

    //Create the second [2] fragment, add it to the view and add the transaction that replaces the first fragment to the backstack
    frag = new Fragment2();

    transaction = getSupportFragmentManager().beginTransaction();
    transaction.replace(R.id.detailFragment, frag);
    transaction.addToBackStack(null);
    transaction.commit();


    //Create third fragment, Dont add this transaction to the backstack, because we dont want to go back to [2] 
    frag = new Fragment3();
    transaction = getSupportFragmentManager().beginTransaction();
    transaction.replace(R.id.detailFragment, frag);
    transaction.commit();


     //END OF SETUP CODE-------------------------
    //NOW:
    //Press back once and then issue the following code:
    frag = new Fragment2();
    transaction = getSupportFragmentManager().beginTransaction();
    transaction.replace(R.id.detailFragment, frag);
    transaction.addToBackStack(null);
    transaction.commit();

    //Now press back again and you end up at fragment [3] not [1]

Çok teşekkürler


ama C Parçasından A parçasına geri bastığımda parça üst üste
Priyanka

bende de aynı sorun var, bunu nasıl düzeltirsiniz?
Priyanka

cevaplarıma bakın .. size yardımcı olabilir < stackoverflow.com/questions/14971780/… >
MD Khali

Yanıtlar:


203

Açıklama: burada neler oluyor?

Bunun belgelerden bildiğimizle .replace()aynı olduğunu aklımızda tutarsak .remove().add():

Bir kaba eklenen mevcut bir parçayı değiştirin. Bu, esasen remove(Fragment)aynı şekilde containerViewIdve daha sonra add(int, Fragment, String)burada verilen aynı argümanlarla eklenen tüm mevcut eklenmiş parçaları çağırmakla aynıdır.

sonra ne oluyor böyle (daha net hale getirmek için parçaya sayılar ekliyorum):

// transaction.replace(R.id.detailFragment, frag1);
Transaction.remove(null).add(frag1)  // frag1 on view

// transaction.replace(R.id.detailFragment, frag2).addToBackStack(null);
Transaction.remove(frag1).add(frag2).addToBackStack(null)  // frag2 on view

// transaction.replace(R.id.detailFragment, frag3);
Transaction.remove(frag2).add(frag3)  // frag3 on view

(burada tüm yanıltıcı şeyler olmaya başlar)

Unutmayın .addToBackStack()tasarrufu yalnızca işlem değil parçası kendisi kadar! Şimdi düzenimiz var frag3:

< press back button >
// System pops the back stack and find the following saved back entry to be reversed:
// [Transaction.remove(frag1).add(frag2)]
// so the system makes that transaction backward!!!
// tries to remove frag2 (is not there, so it ignores) and re-add(frag1)
// make notice that system doesn't realise that there's a frag3 and does nothing with it
// so it still there attached to view
Transaction.remove(null).add(frag1) //frag1, frag3 on view (OVERLAPPING)

// transaction.replace(R.id.detailFragment, frag2).addToBackStack(null);
Transaction.remove(frag3).add(frag2).addToBackStack(null)  //frag2 on view

< press back button >
// system makes saved transaction backward
Transaction.remove(frag2).add(frag3) //frag3 on view

< press back button >
// no more entries in BackStack
< app exits >

Olası çözüm

FragmentManager.BackStackChangedListenerArka yığındaki değişiklikleri izlemek için uygulamayı düşünün ve onBackStackChanged()yöntemde mantığınızı uygulayın :


İyi açıklama @arvis. Bununla birlikte, bu davranışı, DexterMoon'dan veya Nemanja'dan geçiş animasyonunu oynatırken Fragment'ı gösteren popBackStack gibi hantal yollara başvurmadan nasıl önleyebiliriz?
momo

@momo FragmentManager.BackStackChangedListener, arka yığındaki değişiklikleri izlemek için uygulayabilirsiniz . Tüm işlemlerinizi yöntemle izleyin onBackStackChanged()ve gerektiği gibi hareket edin: örn. BackStack'te bir işlem sayısını izleyin; belirli işlemi ada ( FragmentTransaction addToBackStack (String name)) vb. göre kontrol edin
Arvis

Cevapladığınız için teşekkürler. Aslında bunu bugün erken saatlerde denedim, dinleyiciyi kaydettim ve parçayı onBackstackChange'den kaldırdım. Patlama geçişi oynatılırken parça beyaz boş bir alan haline gelir. Sanırım yöntem patlatma animasyonu başlattığında ve bitince değil başlatıldığında çalıştırılıyor ...
momo

4
Sırt yığınlarını kullanmaktan kaçının! genel verimliliğe gerçekten yardımcı olmuyor! düz değiştirme () kullanın veya her gezinmek istediğinizde daha da iyisi kaldır / ekleyin!
stack_ved

@Arvis aynı sorunu almama yardım edebilir mi .... yığın sayısı 0 ama yine de parçam görünüyor mu?
Erum

33

Sağ!!! epeyce saç çektikten sonra nihayet bunun nasıl düzgün çalışacağını buldum.

Geri basıldığında [3] parçası görünümden kaldırılmamış gibi görünüyor, bu yüzden bunu manuel olarak yapmanız gerekiyor!

Her şeyden önce, replace () kullanmayın, bunun yerine remove ve add'i ayrı ayrı kullanın. Değiştir () düzgün çalışmıyor gibi görünüyor.

Bunun bir sonraki kısmı, onKeyDown yöntemini geçersiz kılmak ve geri düğmesine her basıldığında geçerli parçayı kaldırmaktır.

@Override
public boolean onKeyDown(int keyCode, KeyEvent event)
{
    if (keyCode == KeyEvent.KEYCODE_BACK)
    {
        if (getSupportFragmentManager().getBackStackEntryCount() == 0)
        {
            this.finish();
            return false;
        }
        else
        {
            getSupportFragmentManager().popBackStack();
            removeCurrentFragment();

            return false;
        }



    }

    return super.onKeyDown(keyCode, event);
}


public void removeCurrentFragment()
{
    FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();

    Fragment currentFrag =  getSupportFragmentManager().findFragmentById(R.id.detailFragment);


    String fragName = "NONE";

    if (currentFrag!=null)
        fragName = currentFrag.getClass().getSimpleName();


    if (currentFrag != null)
        transaction.remove(currentFrag);

    transaction.commit();

}

Bu yardımcı olur umarım!


bu işe yarıyor, ancak parça yeniden yüklendiğinden bu projemde kullanılamaz! baska öneri?
TharakaNirmana

Ama eğer bunu yapıyorsam. Fragment C için C'den A'ya döndüğümde boş ekran alıyorum
Nigam Patro

@Arvis'in yanıtının sorununuza biraz ışık tutması gerektiğinden şüpheleniyorum
Chris Birch

16

Öncelikle göz açıcı bir açıklama için @Arvis'e teşekkürler.

Bu sorun için burada kabul edilen cevaba farklı bir çözüm tercih ediyorum. Geriye dönük davranışı kesinlikle gerekenden daha fazla karıştırmayı sevmiyorum ve geri düğmesine basıldığında varsayılan arka yığın patlaması olmadan kendi başıma parça eklemeyi ve çıkarmayı denediğimde kendimi parça cehenneminde buldum :) Eğer sen. Kaldırdığınızda f1 yerine f2 ekleyin f1 onResume, onStart vb. gibi geri çağırma yöntemlerini çağırmaz ve bu çok talihsiz olabilir.

Her neyse, ben böyle yapıyorum:

Şu anda görüntülenen yalnızca f1 parçası.

f1 -> f2

Fragment2 f2 = new Fragment2();
this.getActivity().getSupportFragmentManager().beginTransaction().replace(R.id.main_content,f2).addToBackStack(null).commit();

burada sıra dışı hiçbir şey yok. F2 parçasında olduğundan bu kod sizi f3 parçasına götürür.

f2 -> f3

Fragment3 f3 = new Fragment3();
getActivity().getSupportFragmentManager().popBackStack();
getActivity().getSupportFragmentManager().beginTransaction().replace(R.id.main_content, f3).addToBackStack(null).commit();

Belgeleri okuyarak bunun işe yarayıp yaramayacağından emin değilim, bu poping işlem yönteminin eşzamansız olduğu söyleniyor ve belki daha iyi bir yol popBackStackImmediate () 'i çağırmak olabilir. Ancak cihazlarımda kusursuz çalıştığını görebildiğim kadarıyla.

Söz konusu alternatif şudur:

final FragmentActivity activity = getActivity();
activity.getSupportFragmentManager().popBackStackImmediate();
activity.getSupportFragmentManager().beginTransaction().replace(R.id.main_content, f3).addToBackStack(null).commit();

Burada f3'e geçmeden önce f1'e geri dönme kısa olacak, bu yüzden küçük bir aksaklık var.

Aslında yapmanız gereken tek şey bu, geri yığın davranışını geçersiz kılmanıza gerek yok ...


6
ama aksaklık, bu bir sorun
Zyoo

Bu, BackStack değişikliklerini dinlemekten daha az "hack-ey" görünüyor. Teşekkürler.
ahaisting

13

Bunun eski bir soru olduğunu biliyorum ama aynı sorunu yaşıyorum ve şu şekilde düzelttim:

Önce, Fragment1'i bir adla BackStack'e ekleyin (örneğin "Frag1"):

frag = new Fragment1();

transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.detailFragment, frag);
transaction.addToBackStack("Frag1");
transaction.commit();

Ve sonra, Fragment1'e geri dönmek istediğinizde (üstüne 10 parça ekledikten sonra bile), popBackStackImmediate'i şu adla çağırmanız yeterlidir:

getSupportFragmentManager().popBackStackImmediate("Frag1", 0);

Umarım birine yardımcı olur :)


1
Mükemmel cevap. GetSupportFragmentManager (). PopBackStackImmediate ("Frag1", FragmentManager.POP_BACK_STACK_INCLUSIVE); yine de kullanım durumum için çalışmasını sağlamak
eliasbagley

1
Bu kodu nereye koymam gerekiyor ???? getSupportFragmentManager (). popBackStackImmediate ("Frag1", 0); MainActivty'de veya onBackPress'te
pavel

çalışmıyor. :( bu benim kodum, Benim durumumda Parça Örtüşüyor. Parça A'yı açıyor, ancak Parça A, Parça B'de örtüşüyor.
Priyanka

FragmentManager fragmentManager = getActivity (). GetSupportFragmentManager (); fragmentManager.popBackStack (FragmentA.class.getName (), FragmentManager.POP_BACK_STACK_INCLUSIVE); FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction (); FragmentE fragmentE = new FragmentE (); fragmentTransaction.replace (R.id.fragment_content, fragmentE, fragmentE.getClass (). getName ()); fragmentTransaction.commit ();
Priyanka

5

@Arvis yanıtından sonra daha da derine inmeye karar verdim ve burada bununla ilgili bir teknik makale yazdım: http://www.andreabaccega.com/blog/2015/08/16/how-to-avoid-fragments-overlapping- bağlı-ile-backstack-kabus-içinde-robot /

Tembel geliştiriciler için. Çözümüm, her zaman işlemleri arka desteye eklemekten ve FragmentManager.popBackStackImmediate()gerektiğinde (otomatik olarak) fazladan bir işlem gerçekleştirmekten ibarettir .

Kod çok az kod satırından oluşuyor ve benim örneğimde, eğer kullanıcı backstack'te daha derine inmediyse (örneğin C'den D'ye gider) "B" ye geri dönmeden C'den A'ya atlamak istedim.

Dolayısıyla eklenen kod şu şekilde çalışacaktır: A -> B -> C (geri) -> A & A -> B -> C -> D (geri) -> C (geri) -> B (geri) -> A

nerede

fm.beginTransaction().replace(R.id.content, new CFragment()).commit()

soruda olduğu gibi "B" den "C" ye kadar çıktı.

Tamam, işte kod :)

public static void performNoBackStackTransaction(FragmentManager fragmentManager, String tag, Fragment fragment) {
  final int newBackStackLength = fragmentManager.getBackStackEntryCount() +1;

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

  fragmentManager.addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {
    @Override
    public void onBackStackChanged() {
      int nowCount = fragmentManager.getBackStackEntryCount();
      if (newBackStackLength != nowCount) {
        // we don't really care if going back or forward. we already performed the logic here.
        fragmentManager.removeOnBackStackChangedListener(this);

        if ( newBackStackLength > nowCount ) { // user pressed back
          fragmentManager.popBackStackImmediate();
        }
      }
    }
  });
}

1
bu satırda fragmentManager.popBackStackImmediate (); hata: java.lang.IllegalStateException: FragmentManager, com.example.myapplication.FragmentA $ 2 onBackStackChanged (FragmentA.java:43)
Priyanka

1

AddToBackStack () ve popBackStack () ile mücadele ediyorsanız,

FragmentTransaction ft =getSupportFragmentManager().beginTransaction();
ft.replace(R.id.content_frame, new HomeFragment(), "Home");
ft.commit();`

OnBackPressed () içindeki Etkinliğinizde etikete göre fargment bulun ve ardından işinizi yapın

Fragment home = getSupportFragmentManager().findFragmentByTag("Home");

if (home instanceof HomeFragment && home.isVisible()) {
    // do you stuff
}

Daha fazla bilgi için https://github.com/DattaHujare/NavigationDrawer Parçayı işlemek için asla addToBackStack () kullanmam.


0

Sanırım, hikayenizi okuduğumda [3] da arka planda kalıyor. Bu, neden parladığını gördüğünüzü açıklıyor.

Çözüm, yığın üzerinde asla [3] ayarlamamak olacaktır.


Merhaba jdekei Girişiniz için teşekkürler. Sorun şu ki [3] 'ü backstack'e nereye eklediğimi göremiyorum. Düğmeleri kullanarak gerçekleştirdiğim navigasyonu (programlı olarak) gösteren başka bir kod parçası ekledim.
Chris Birch

Bana da yardımcı oluyor, ancak sizden yalnızca removeCurFragment kodunu ve biraz başka bir parça denetleyicisi kullanıyorum. Değiştirme yöntemi benim için işe yarıyor, olabilir, s old method issue but now itsorun değil. Teşekkür
Viktor V.

0

Aynı [M1.F0] -> [M1.F1] -> [M1.F2] içinde 3 ardışık fragmanım ve Activityardından yeni bir Activity[M2] aramasıyla benzer bir sorun yaşadım . Kullanıcı [M2] 'de bir düğmeye bastıysa, [M1, F2] yerine [M1, F1]' e dönmek istedim ki bu zaten geri baskı davranışının yaptığı şeydi.

Bunu başarmak için [M1, F2] 'yi kaldırıyorum, [M1, F1]' de show çağırıyorum, işlemi gerçekleştiriyorum ve sonra [M1, F2] 'yi hide ile çağırarak geri ekliyorum. Bu, aksi takdirde geride bırakılacak ekstra geri baskıyı ortadan kaldırdı.

// Remove [M1.F2] to avoid having an extra entry on back press when returning from M2
final FragmentTransaction ftA = fm.beginTransaction();
ftA.remove(M1F2Fragment);
ftA.show(M1F1Fragment);
ftA.commit();
final FragmentTransaction ftB = fm.beginTransaction();
ftB.hide(M1F2Fragment);
ftB.commit();

Merhaba Bu kodu yaptıktan sonra: Back Tuşuna basıldığında Fragment2 değerini göremiyorum. Kodum:

FragmentTransaction ft = fm.beginTransaction();
ft.add(R.id.frame, f1);
ft.remove(f1);

ft.add(R.id.frame, f2);
ft.addToBackStack(null);

ft.remove(f2);
ft.add(R.id.frame, f3);

ft.commit();

@Override
    public boolean onKeyDown(int keyCode, KeyEvent event){

        if(keyCode == KeyEvent.KEYCODE_BACK){
            Fragment currentFrag =  getFragmentManager().findFragmentById(R.id.frame);
            FragmentTransaction transaction = getFragmentManager().beginTransaction();

            if(currentFrag != null){
                String name = currentFrag.getClass().getName();
            }
            if(getFragmentManager().getBackStackEntryCount() == 0){
            }
            else{
                getFragmentManager().popBackStack();
                removeCurrentFragment();
            }
       }
    return super.onKeyDown(keyCode, event);
   }

public void removeCurrentFragment()
    {
        FragmentTransaction transaction = getFragmentManager().beginTransaction();
        Fragment currentFrag =  getFragmentManager().findFragmentById(R.id.frame);

        if(currentFrag != null){
            transaction.remove(currentFrag);
        }
        transaction.commit();
    }

0

executePendingTransactions(), commitNow()çalışmadı (

Androidx (jetpack) ile çalıştı.

private final FragmentManager fragmentManager = getSupportFragmentManager();

public void removeFragment(FragmentTag tag) {
    Fragment fragmentRemove = fragmentManager.findFragmentByTag(tag.toString());
    if (fragmentRemove != null) {
        fragmentManager.beginTransaction()
                .remove(fragmentRemove)
                .commit();

        // fix by @Ogbe
        fragmentManager.popBackStackImmediate(tag.toString(), 
            FragmentManager.POP_BACK_STACK_INCLUSIVE);
    }
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.