Varsa BackStack'tan Parçayı Sürdürme


151

Parçaların nasıl kullanılacağını öğreniyorum. FragmentSınıfın üstünde başlatılan üç örneğim var . Ben böyle bir etkinliğe parça ekliyorum:

Beyan ve başlatma:

Fragment A = new AFragment();
Fragment B = new BFragment();
Fragment C = new CFragment();

Ekleme / Değiştirme:

FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.replace(R.id.content_frame, A);
ft.addToBackStack(null);
ft.commit();

Bu snippet'ler düzgün çalışıyor. Her parça etkinliğe eklenir ve herhangi bir sorun olmadan arka yığına kaydedilir.

Yani, başlattığımda A, Cve sonra B, yığın şöyle görünür:

| |
|B|
|C|
|A|
___

Ve 'geri' düğmesine bastığımda Byok edildi ve Cyeniden başlatıldı.

Ancak, parçayı Aikinci kez başlattığımda , arka yığından devam etmek yerine, arka yığının üstüne eklenir

| |
|A|
|C|
|A|
___

Ama Aüstündeki tüm parçaları (varsa) devam ettirmek ve yok etmek istiyorum . Aslında, varsayılan geri yığın davranışını seviyorum.

Bunu nasıl başarabilirim?

Beklenen: ( Adevam ettirilmeli ve üst kısımlar imha edilmelidir)

| |
| |
| |
|A|
___

Düzenleme: (A - C tarafından önerilir)

Bu benim deneme kodum:

private void selectItem(int position) {
        Fragment problemSearch = null, problemStatistics = null;
        FragmentManager manager = getSupportFragmentManager();
        FragmentTransaction ft = manager.beginTransaction();
        String backStateName = null;
        Fragment fragmentName = null;
        boolean fragmentPopped = false;
        switch (position) {
        case 0:
            fragmentName = profile;
            break;
        case 1:
            fragmentName = submissionStatistics;
            break;
        case 2:
            fragmentName = solvedProblemLevel;
            break;
        case 3:
            fragmentName = latestSubmissions;
            break;
        case 4:
            fragmentName = CPExercise;
            break;
        case 5:
            Bundle bundle = new Bundle();
            bundle.putInt("problem_no", problemNo);
            problemSearch = new ProblemWebView();
            problemSearch.setArguments(bundle);
            fragmentName = problemSearch;
            break;
        case 6:
            fragmentName = rankList;
            break;
        case 7:
            fragmentName = liveSubmissions;
            break;
        case 8:
            Bundle bundles = new Bundle();
            bundles.putInt("problem_no", problemNo);
            problemStatistics = new ProblemStatistics();
            problemStatistics.setArguments(bundles);
            fragmentName = problemStatistics;
        default:
            break;
        }
        backStateName = fragmentName.getClass().getName();
        fragmentPopped = manager.popBackStackImmediate(backStateName, 0);
        if (!fragmentPopped) {
            ft.replace(R.id.content_frame, fragmentName);
        }
        ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
        ft.addToBackStack(backStateName);
        ft.commit();

        // I am using drawer layout
        mDrawerList.setItemChecked(position, true);
        setTitle(title[position]);
        mDrawerLayout.closeDrawer(mDrawerList);
    }

Ben başlattığınızda sorundur, Ave sonra B, daha sonra basın 'geri', Bçıkarılır ve Adevam ettirilir. ve 'geri' düğmesine ikinci kez basmak uygulamadan çıkmalıdır. Ama boş bir pencere gösteriyor ve kapatmak için üçüncü kez basmam gerekiyor.

Ayrıca, fırlattığımda A, sonra B, sonra C, sonra Btekrar ...

Beklenen:

| |
| |
|B|
|A|
___

Gerçek:

| |
|B|
|B|
|A|
___

Herhangi onBackPressed()bir özelleştirme ile geçersiz kılmalı mıyım yoksa bir şey mi kaçırıyorum?

Yanıtlar:


279

Belgeleri okurken, işlem adını veya kesinleştirme tarafından sağlanan kimliği temel alarak arka yığını açmanın bir yolu vardır. Adını kullanarak olabilir o değiştirip "benzersiz arka yığını girişi" mantığı pekiştiriyor olabilecek bir dizi takip gerektirmemelidir beri kolaylaşacaktır.

Başına yalnızca bir arka yığın girişi istediğiniz Fragmentiçin, geri durum adını Parça'nın sınıf adı (yoluyla getClass().getName()) yapın. Sonra a değiştirirken Fragment, popBackStackImmediate()yöntemi kullanın . True değerini döndürürse, arka yığında Fragment'ın bir örneği olduğu anlamına gelir. Değilse, aslında Parça değiştirme mantığını yürütün.

private void replaceFragment (Fragment fragment){
  String backStateName = fragment.getClass().getName();

  FragmentManager manager = getSupportFragmentManager();
  boolean fragmentPopped = manager.popBackStackImmediate (backStateName, 0);

  if (!fragmentPopped){ //fragment not in back stack, create it.
    FragmentTransaction ft = manager.beginTransaction();
    ft.replace(R.id.content_frame, fragment);
    ft.addToBackStack(backStateName);
    ft.commit();
  }
}

DÜZENLE

Sorun şu: A ve ardından B'yi başlattığımda, sonra geri düğmesine bastığımda, B kaldırılıyor ve A devam ediyor. ve tekrar geri düğmesine basıldığında uygulamadan çıkılmalıdır. Ama boş bir pencere gösteriyor ve kapatmak için başka bir basın gerekiyor.

Bunun nedeni, FragmentTransactionparçaları daha sonra üst üste getirebilmemizi sağlamak için arka yığına eklenmesi. Bunun için hızlı bir düzeltme onBackPressed(), arka yığın yalnızca içeriyorsa Etkinliği geçersiz kılar ve bitirir 1Fragment

@Override
public void onBackPressed(){
  if (getSupportFragmentManager().getBackStackEntryCount() == 1){
    finish();
  }
  else {
    super.onBackPressed();
  }
}

Yinelenen geri yığın girdileri ile ilgili olarak, eğer patlatılmamışsa parçayı değiştiren koşullu ifadeniz orijinal kod snippet'imden açıkça farklıdır . Yaptığınız şey, arka yığının atmış olup olmadığına bakılmaksızın arka yığını eklemektir.

Böyle bir şey istediğiniz şeye daha yakın olmalıdır:

private void replaceFragment (Fragment fragment){
  String backStateName =  fragment.getClass().getName();
  String fragmentTag = backStateName;

  FragmentManager manager = getSupportFragmentManager();
  boolean fragmentPopped = manager.popBackStackImmediate (backStateName, 0);

  if (!fragmentPopped && manager.findFragmentByTag(fragmentTag) == null){ //fragment not in back stack, create it.
    FragmentTransaction ft = manager.beginTransaction();
    ft.replace(R.id.content_frame, fragment, fragmentTag);
    ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
    ft.addToBackStack(backStateName);
    ft.commit();
  } 
}

Koşulludur, görünürken aynı parçayı seçmek de yinelenen girişlere neden olduğu için biraz değiştirildi.

Uygulama:

replaceFragment()Kodunuzda yaptığınız gibi güncellenmiş yöntemi ayırmamanızı tavsiye ederim. Tüm mantık bu yöntemde bulunur ve hareket eden parçalar sorunlara neden olabilir.

Bu, güncellenen replaceFragment()yöntemi sınıfınıza kopyalamanız ve değiştirmeniz gerektiği anlamına gelir

backStateName = fragmentName.getClass().getName();
fragmentPopped = manager.popBackStackImmediate(backStateName, 0);
if (!fragmentPopped) {
            ft.replace(R.id.content_frame, fragmentName);
}
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
ft.addToBackStack(backStateName);
ft.commit();

bu yüzden basitçe

replaceFragment (fragmentName);

DÜZENLEME # 2

Arka yığın değiştiğinde çekmeceyi güncellemek için bir Parçada kabul edilen ve sınıf adlarını karşılaştıran bir yöntem yapın. Eşleşen bir şey varsa başlığı ve seçimi değiştirin. Ayrıca OnBackStackChangedListenergeçerli bir Parçacık varsa ve ekleyin ve güncelleme yönteminizi çağırmasını sağlayın.

Örneğin, Etkinliklerde onCreate(),

getSupportFragmentManager().addOnBackStackChangedListener(new OnBackStackChangedListener() {

  @Override
  public void onBackStackChanged() {
    Fragment f = getSupportFragmentManager().findFragmentById(R.id.content_frame);
    if (f != null){
      updateTitleAndDrawer (f);
    }

  }
});

Ve diğer yöntem:

private void updateTitleAndDrawer (Fragment fragment){
  String fragClassName = fragment.getClass().getName();

  if (fragClassName.equals(A.class.getName())){
    setTitle ("A");
    //set selected item position, etc
  }
  else if (fragClassName.equals(B.class.getName())){
    setTitle ("B");
    //set selected item position, etc
  }
  else if (fragClassName.equals(C.class.getName())){
    setTitle ("C");
    //set selected item position, etc
  }
}

Şimdi, arka yığın her değiştiğinde, başlık ve işaretli konum görünür olanı yansıtacaktır Fragment.


Cevabınız için teşekkürler. :) Kısmen çalışıyor. Sorumu ilerlemeyle düzenledim. Lütfen bir göz atın)
Kaidul

2
@RishabhSrivastava bir "pop" un genellikle en üstteki parçayı yok ettiğini aklınızda bulundurun. İlk olarak hangi yöntemin çağrıldığına bağlı olarak, Fragment Yaşam Döngüsüne bir göz atın. Sizin durumunuz için, OnBackstackChangedListenerdoğru Fragman ile uğraştığınızı garanti edebilmeniz için bir tane kullanırım .
A - C

2
@RishabhSrivastava Bu yüzden de önerdim OnBackstackChangedListener.
A - C

1
@AlexanderFarber Haklısın. Bu yanıtı aldığımda hiç düşünmemiştim instanceof:)
A - C

2
Çözümünüz iyi görünüyor, ancak üç parçanız varsa ve üzerlerine tıklayın A-B-C-Bve bir kez geri basın, geri Adönmeyeceksiniz C. Çünkü popBackStackImmediateverilen etiketi değil, yukarıdaki her şeyi de çıkarır. Orada herhangi bir iş var mı?
Raeglan

10

Sanırım bu yöntem benim sorununuzu çözer:

public static void attachFragment ( int fragmentHolderLayoutId, Fragment fragment, Context context, String tag ) {


    FragmentManager manager = ( (AppCompatActivity) context ).getSupportFragmentManager ();
    FragmentTransaction ft = manager.beginTransaction ();

    if (manager.findFragmentByTag ( tag ) == null) { // No fragment in backStack with same tag..
        ft.add ( fragmentHolderLayoutId, fragment, tag );
        ft.addToBackStack ( tag );
        ft.commit ();
    }
    else {
        ft.show ( manager.findFragmentByTag ( tag ) ).commit ();
    }
}

aslen Bu Soruya gönderildi


Bu şekilde yanıtlamanın, insanların bu yayında görmesini kolaylaştırmak istediğim kurallara aykırı olup olmadığını lütfen bize bildirin. Teşekkürler ..
erluxman

Kurallara aykırı olup olmadığından emin değilim (muhtemelen değil), ama bu kesinlikle yararlı
Kathir

1
Üçüncü satırın kullanımı nedir? -> manager.findFragmentByTag (etiket); Görünüşe göre hiçbir şey yapmıyor ..
Rik van Velzen

3

1. Adım: Etkinlik sınıfınızla bir arayüz uygulayın

public class AuthenticatedMainActivity extends Activity implements FragmentManager.OnBackStackChangedListener{

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        .............
        FragmentManager fragmentManager = getFragmentManager();           
        fragmentManager.beginTransaction().add(R.id.frame_container,fragment, "First").addToBackStack(null).commit();
    }

    private void switchFragment(Fragment fragment){            
      FragmentManager fragmentManager = getFragmentManager();
      fragmentManager.beginTransaction()
        .replace(R.id.frame_container, fragment).addToBackStack("Tag").commit();
    }

    @Override
    public void onBackStackChanged() {
    FragmentManager fragmentManager = getFragmentManager();

    System.out.println("@Class: SummaryUser : onBackStackChanged " 
            + fragmentManager.getBackStackEntryCount());

    int count = fragmentManager.getBackStackEntryCount();

    // when a fragment come from another the status will be zero
    if(count == 0){

        System.out.println("again loading user data");

        // reload the page if user saved the profile data

        if(!objPublicDelegate.checkNetworkStatus()){

            objPublicDelegate.showAlertDialog("Warning"
                    , "Please check your internet connection");

        }else {

            objLoadingDialog.show("Refreshing data..."); 

            mNetworkMaster.runUserSummaryAsync();
        }

        // IMPORTANT: remove the current fragment from stack to avoid new instance
        fragmentManager.removeOnBackStackChangedListener(this);

    }// end if
   }       
}

Adım 2: Başka bir parçayı çağırdığınızda bu yöntemi ekleyin:

String backStateName = this.getClass().getName();

FragmentManager fragmentManager = getFragmentManager();
fragmentManager.addOnBackStackChangedListener(this); 

Fragment fragmentGraph = new GraphFragment();
Bundle bundle = new Bundle();
bundle.putString("graphTag",  view.getTag().toString());
fragmentGraph.setArguments(bundle);

fragmentManager.beginTransaction()
.replace(R.id.content_frame, fragmentGraph)
.addToBackStack(backStateName)
.commit();

2

Bunun bu soruyu cevaplamak için oldukça geç olduğunu biliyorum, ancak bu sorunu kendim çözdüm ve herkesle paylaşmaya değer olduğunu düşündüm.

public void replaceFragment(BaseFragment fragment) {
    FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
    final FragmentManager fManager = getSupportFragmentManager();
    BaseFragment fragm = (BaseFragment) fManager.findFragmentByTag(fragment.getFragmentTag());
    transaction.setCustomAnimations(R.anim.enter_from_right, R.anim.exit_to_left, R.anim.enter_from_left, R.anim.exit_to_right);

    if (fragm == null) {  //here fragment is not available in the stack
        transaction.replace(R.id.container, fragment, fragment.getFragmentTag());
        transaction.addToBackStack(fragment.getFragmentTag());
    } else { 
        //fragment was found in the stack , now we can reuse the fragment
        // please do not add in back stack else it will add transaction in back stack
        transaction.replace(R.id.container, fragm, fragm.getFragmentTag()); 
    }
    transaction.commit();
}

Ve onBackPressed () içinde

 @Override
public void onBackPressed() {
    if(getSupportFragmentManager().getBackStackEntryCount()>1){
        super.onBackPressed();
    }else{
        finish();
    }
}

1
@ X-Black ... BaseFragment uygulamada kullanılan her parça için bir temel sınıftır.
Vivek Pratap Singh

0
getFragmentManager().addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {

    @Override
    public void onBackStackChanged() {

        if(getFragmentManager().getBackStackEntryCount()==0) {
            onResume();
        }    
    }
});

0

Daha kolay çözüm bu hattı değiştirecek

ft.replace(R.id.content_frame, A); için ft.add(R.id.content_frame, A);

Ve XML düzeninizin içinde lütfen

  android:background="@color/white"
  android:clickable="true"
  android:focusable="true"

Clickable bir işaretçi aygıtı tarafından tıklanabileceği veya bir dokunmatik aygıt tarafından tıklanabileceği anlamına gelir.

Focusableklavye gibi bir giriş aygıtından odak elde edebileceği anlamına gelir. Klavyeler gibi giriş aygıtları, giriş olaylarının kendilerine göre hangi olayların giriş olaylarını göndereceğine karar veremezler, böylece bunları odaklanan görünüme gönderirler.

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.