حدثت مشكلات في نظام Android Fragment back stack




android-fragments back-stack (6)

شرح: ما الذي يحدث هنا؟

إذا وضعنا في اعتبارنا أن .replace() يساوي .remove().add() التي نعرفها بالوثائق:

استبدال جزء موجود تمت إضافته إلى حاوية. هذا هو في الأساس نفس استدعاء remove(Fragment) لكافة الأجزاء المضافة حالياً التي تمت إضافتها مع نفس containerViewId ثم قم add(int, Fragment, String) بنفس الوسيطات المعطاة هنا.

إذن ما يحدث هو مثل هذا (أقوم بإضافة الأرقام إلى الشريحة لجعلها أكثر وضوحًا):

// 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

(هنا كل الأشياء المضللة تبدأ في الحدوث)

تذكر أن .addToBackStack() يتم حفظ المعاملة فقط وليس الجزء نفسه! حتى الآن لدينا 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 >

حل ممكن

جرب تنفيذ FragmentManager.BackStackChangedListener لمشاهدة التغييرات في المكدس الخلفي وتطبيق المنطق الخاص بك في onBackStackChanged() :

لدي مشكلة كبيرة في الطريقة التي يبدو بها الجزء الخلفي من شظية الروبوت وستكون ممتنة لأية مساعدة يتم تقديمها.

تخيل أن لديك 3 أجزاء

[1] [2] [3]

أريد أن يتمكن المستخدم من التنقل [1] > [2] > [3] ولكن في طريق العودة (الضغط على زر الرجوع) [3] > [1] .

كما كنت أتصور أن يتم تحقيق ذلك عن طريق عدم استدعاء addToBackStack(..) عند إنشاء المعاملة التي تجلب جزءًا [2] إلى حامل الأجزاء المحدد في XML.

واقع هذا يبدو كما لو أنه إذا لم أكن أرغب في الظهور مرة أخرى عندما يقوم المستخدم بالضغط على الزر مرة أخرى على [3] ، فلا يجب أن أتصل بـ addToBackStack في المعاملة التي تعرض addToBackStack [3] . هذا يبدو غير بديهي تماما (ربما قادم من عالم iOS).

على أي حال إذا كنت أفعل ذلك بهذه الطريقة ، عندما أذهب من [1] > [2] ثم أضغط مرة أخرى للعودة إلى [1] كما هو متوقع.

إذا ذهبت [1] > [2] > [3] ، ثم اضغط للرجوع ، فأنا أقفز مرة أخرى إلى [1] (كما هو متوقع). الآن يحدث السلوك الغريب عندما أحاول وأقفز إلى [2] مرة أخرى من [1] . أولاً وقبل كل شيء يتم عرض [3] لفترة وجيزة قبل ظهور [2] . إذا قمت بالضغط على هذه النقطة [3] ، فسيتم عرضها ، وإذا قمت بالضغط مرة أخرى ، يتم إنهاء التطبيق.

هل يمكن لأي أحد أن يساعدني على فهم ما يجري هنا؟


وهنا ملف تخطيط XML لنشاطي الرئيسي:

<?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: [email protected]/details -->
</fragment>
<FrameLayout
        android:id="@+id/detailFragment"
        android:layout_width="match_parent"
        android:layout_height="fill_parent"

        />



تحديث هذه هي الشفرة التي أستخدمها للبناء من خلال التسلسل الهرمي للملاحة

    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]

تشكرات


أظن ، عندما قرأت قصتك أن [3] هو أيضا على الظهر. هذا يفسر لماذا تراه وامض.

الحل هو أن لا تضبط أبداً [3] على المكدس.


إذا كنت تعاني من addToBackStack () و popBackStack () ثم استخدم ببساطة

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

في نشاطك في OnBackPressed () تعرّف على التسعيرة بعلامة ثم نفذ الأشياء الخاصة بك

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

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

لمزيد من المعلومات https://github.com/DattaHujare/NavigationDrawer لا أستخدم أبداً addToBackStack () لمعالجة الجزء.


بعد ردArvis قررت أن حفر أعمق ولقد كتبت مقالة التكنولوجيا حول هذا هنا: http://www.andreabaccega.com/blog/2015/08/16/how-to-avoid-fragments-overlapping-due-to-backstack-nightmare-in-android/

للمطورين الكسالى حولها. يتألف الحل الخاص بي من إضافة المعاملات إلى backstack دائمًا وتنفيذ FragmentManager.popBackStackImmediate() إضافية عند الحاجة (تلقائيًا).

الرمز هو عدد قليل جدًا من أسطر التعليمات البرمجية ، وفي المثال الذي كنت أرغب في تخطيه من C إلى A بدون الرجوع إلى "B" إذا لم يكن المستخدم أكثر تعمقًا في backstack (على سبيل المثال ، من C navigates إلى D).

ومن ثم فإن الشفرة المرفقة ستعمل كما يلي A -> B -> C (back) -> A & A -> B -> C -> D (back) -> C (back) -> B (back) -> A

أين

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

صدرت من "ب" إلى "ج" كما في السؤال.

حسنا ، حسنا هنا هو الكود :)

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();
        }
      }
    }
  });
}

كان لدي مشكلة مماثلة حيث كان لدي 3 أجزاء متتالية في نفس Activity [M1.F0] -> [M1.F1] -> [M1.F2] تليها دعوة إلى Activity جديد [M2]. إذا قام المستخدم بالضغط على زر في [M2] ، أردت العودة إلى [M1، F1] بدلاً من [M1، F2] وهو ما فعله بالفعل سلوك الصحافة الخلفية.

من أجل تحقيق هذا ، أزلت [M1 ، F2] ، عرض مكالمة على [M1 ، F1] ، ارتكبت الصفقة ، ثم أضف [M1، F2] مرة أخرى عن طريق الاتصال بها مع الاختباء. هذا إزالة الصحافة الخلفية الإضافية التي لولاها قد تركت وراءها.

// 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();

مرحبًا بعد تنفيذ هذا الرمز: لن أتمكن من رؤية قيمة Fragment2 عند الضغط على "رجوع". رمز بلدي:

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();
    }

وأنا أعلم أنه quetion قديم ولكن حصلت على نفس المشكلة وإصلاحه مثل هذا:

أولاً ، أضف Fragment1 إلى BackStack مع اسم (على سبيل المثال "Frag1"):

frag = new Fragment1();

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

وبعد ذلك ، كلما أردت العودة إلى Fragment1 (حتى بعد إضافة 10 أجزاء فوقها) ، فقط اتصل popBackStackImmediate بالاسم:

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

آمل أن يساعد شخص ما :)







back-stack