ปัญหากับ Android Fragment back stack


121

ฉันมีปัญหาใหญ่เกี่ยวกับวิธีที่ส่วนแบ็คสแต็กของ Android ใช้งานได้และจะขอบคุณมากที่สุดสำหรับความช่วยเหลือใด ๆ ที่มีให้

ลองนึกภาพคุณมี 3 Fragments

[1] [2] [3]

ฉันต้องการให้ผู้ใช้จะสามารถนำทาง[1] > [2] > [3]แต่ในทางกลับ [3] > [1](กดปุ่มด้านหลัง)

อย่างที่ฉันคิดไว้ว่าสิ่งนี้จะทำได้โดยการไม่โทรaddToBackStack(..)เมื่อสร้างธุรกรรมที่นำส่วน[2]เข้าสู่ตัวยึดส่วนที่กำหนดไว้ใน XML

ความจริงของเรื่องนี้ดูเหมือนกับว่าว่าถ้าผมไม่ต้องการ[2]ที่จะปรากฏขึ้นอีกครั้งเมื่อผู้ใช้กดปุ่มย้อนกลับบน[3]ผมต้องไม่เรียกในการทำธุรกรรมที่ส่วนแสดงให้เห็นว่า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: layout=@layout/details -->
</fragment>
<FrameLayout
        android:id="@+id/detailFragment"
        android:layout_width="match_parent"
        android:layout_height="fill_parent"

        />



อัปเดต นี่คือรหัสที่ฉันใช้สร้างโดย nav heirarchy

    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]

ขอบคุณมาก


แต่ส่วนที่ทับซ้อนกันเมื่อฉันถอยหลังจาก Fragment C เป็น Fragment A.
Priyanka

ฉันมีปัญหาเดียวกันคุณจะแก้ไขได้อย่างไร
Priyanka

อ้างอิง ans ของฉัน .. มันอาจช่วยคุณได้ < stackoverflow.com/questions/14971780/… >
MD Khali

คำตอบ:


203

คำอธิบาย: เกิดอะไรขึ้นที่นี่?

หากเราจำไว้ว่า.replace()มันเท่าเทียมกับ.remove().add()ที่เรารู้ในเอกสาร:

แทนที่ส่วนที่มีอยู่ซึ่งถูกเพิ่มลงในคอนเทนเนอร์ โดยพื้นฐานแล้วสิ่งนี้เหมือนกับการเรียกremove(Fragment)ชิ้นส่วนที่เพิ่มในปัจจุบันทั้งหมดที่ถูกเพิ่มด้วยสิ่งเดียวกันcontainerViewIdและตามadd(int, Fragment, String)ด้วยอาร์กิวเมนต์เดียวกันที่ให้ไว้ที่นี่

แล้วสิ่งที่เกิดขึ้นก็เป็นเช่นนี้ (ฉันกำลังเพิ่มตัวเลขให้กับ Frag เพื่อให้ชัดเจนยิ่งขึ้น):

// 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เพื่อดูการเปลี่ยนแปลงใน back stack และใช้ตรรกะของคุณในonBackStackChanged()Methode:


คำอธิบายที่ดี @arvis อย่างไรก็ตามเราจะป้องกันพฤติกรรมนี้ได้อย่างไรโดยไม่ต้องใช้วิธีแฮ็กเช่นจาก DexterMoon หรือจาก Nemanja โดยใช้ popBackStack ซึ่งแสดง Fragment ในขณะที่เล่นภาพเคลื่อนไหวการเปลี่ยนแปลง
momo

@momo คุณอาจใช้FragmentManager.BackStackChangedListenerเพื่อดูการเปลี่ยนแปลงในกองหลัง ตรวจสอบธุรกรรมทั้งหมดของคุณด้วยonBackStackChanged()methode และดำเนินการตามความจำเป็น: เช่น ติดตามจำนวนธุรกรรมใน BackStack; ตรวจสอบการทำธุรกรรมโดยใช้ชื่อ ( FragmentTransaction addToBackStack (String name)) ฯลฯ
Arvis

ขอบคุณสำหรับคำตอบ. ฉันได้ลองทำก่อนหน้านี้ในวันนี้ลงทะเบียนผู้ฟังและลบส่วนออกจาก onBackstackChange ในขณะที่การเปลี่ยนแปลงที่เกิดขึ้นทำให้ชิ้นส่วนกลายเป็นพื้นที่ว่างสีขาว ฉันเดาว่าวิธีนี้จะเริ่มทำงานเมื่อ popping เริ่มแอนิเมชั่นไม่ใช่เมื่อสิ้นสุด ...
momo

4
หลีกเลี่ยงการใช้กองหลัง! มันไม่ได้ช่วยเรื่องประสิทธิภาพโดยรวมจริงๆ! ใช้การแทนที่ธรรมดา () หรือดีกว่าลบ / เพิ่มทุกครั้งที่คุณต้องการนำทาง!
stack_ved

@ อาร์วิสคุณช่วยฉันได้ไหมฉันได้รับปัญหาเดียวกัน .... สแต็คนับ 0 แต่ยังคงมองเห็นส่วนของฉัน?
Erum

33

ขวา!!! หลังจากดึงผมมามากในที่สุดฉันก็ได้หาวิธีทำให้มันทำงานได้อย่างถูกต้อง

ดูเหมือนว่า Fragment [3] จะไม่ถูกลบออกจากมุมมองเมื่อกดย้อนกลับดังนั้นคุณต้องทำด้วยตนเอง!

ก่อนอื่นอย่าใช้แทนที่ () แต่ใช้ลบและเพิ่มแยกกันแทน ดูเหมือนว่าการแทนที่ () ทำงานไม่ถูกต้อง

ส่วนถัดไปคือการลบล้างเมธอด onKeyDown และลบส่วนปัจจุบันทุกครั้งที่กดปุ่มย้อนกลับ

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

}

หวังว่านี่จะช่วยได้!


ใช้งานได้ แต่ไม่สามารถใช้ในโครงการของฉันได้เนื่องจากส่วนย่อยถูกโหลดใหม่! ข้อเสนอแนะใด ๆ
TharakaNirmana

แต่ถ้าฉันทำสิ่งนี้ ฉันได้รับหน้าจอว่างเปล่าเมื่อฉันกลับจาก C ไป A สำหรับ Fragment C
Nigam Patro

ฉันสงสัยว่าคำตอบของ @Arvis ควรให้ความสำคัญกับปัญหาของคุณ
Chris Birch

16

ก่อนอื่นขอบคุณ @Arvis สำหรับคำอธิบายการเปิดตา

ฉันชอบวิธีแก้ปัญหาที่แตกต่างกันสำหรับคำตอบที่ยอมรับสำหรับปัญหานี้ ฉันไม่ชอบยุ่งกับการลบล้างพฤติกรรมย้อนกลับมากเกินความจำเป็นและเมื่อฉันได้ลองเพิ่มและลบชิ้นส่วนด้วยตัวเองโดยไม่มีแบ็คสแต็กเริ่มต้นปรากฏขึ้นเมื่อกดปุ่มย้อนกลับฉันพบว่าตัวเองอยู่ในนรกเศษ :) ถ้าคุณ เพิ่ม f2 ทับ f1 เมื่อคุณลบออก f1 จะไม่เรียกวิธีการโทรกลับใด ๆ เช่น onResume, onStart เป็นต้นและนั่นอาจเป็นเรื่องที่โชคร้ายมาก

อย่างไรก็ตามนี่คือวิธีที่ฉันทำ:

ที่แสดงอยู่ในปัจจุบันเป็นเพียงแฟรกเมนต์ f1

f1 -> f2

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

ไม่มีอะไรผิดปกติที่นี่ กว่าในแฟรกเมนต์ f2 รหัสนี้จะพาคุณไปยังแฟรกเมนต์ f3

f2 -> f3

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

ฉันไม่แน่ใจว่าการอ่านเอกสารจะใช้งานได้หรือไม่วิธีการทำธุรกรรมป๊อปอัปนี้ถูกกล่าวว่าเป็นแบบอะซิงโครนัสและวิธีที่ดีกว่าคือเรียกใช้ popBackStackImmediate () แต่เท่าที่ฉันบอกได้บนอุปกรณ์ของฉันมันทำงานได้อย่างไม่มีที่ติ

ทางเลือกดังกล่าวจะเป็น:

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

ที่นี่จะมีการสรุปสั้น ๆ เกี่ยวกับการกลับไปที่ f1 โดยเปลี่ยนไปที่ f3 ดังนั้นจึงมีข้อผิดพลาดเล็กน้อยที่นั่น

นี่เป็นสิ่งที่คุณต้องทำจริงๆไม่จำเป็นต้องลบล้างพฤติกรรมย้อนกลับ ...


6
แต่ความผิดพลาดนั่นคือปัญหา
Zyoo

ดูเหมือนว่าจะ "แฮ็กอาย" น้อยกว่าการฟังการเปลี่ยนแปลงของ BackStack ขอบคุณ
ahaisting

13

ฉันรู้ว่ามันเป็นคำถามเก่า แต่ฉันมีปัญหาเดียวกันและแก้ไขดังนี้:

ขั้นแรกให้เพิ่ม 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);

หวังว่ามันจะช่วยใครสักคน :)


1
คำตอบที่ดี ฉันต้องใช้ getSupportFragmentManager (). popBackStackImmediate ("Frag1", FragmentManager.POP_BACK_STACK_INCLUSIVE); เพื่อให้ใช้งานได้สำหรับกรณีการใช้งานของฉัน
eliasbagley

1
ต้องใส่รหัสนี้ตรงไหน ???? getSupportFragmentManager (). popBackStackImmediate ("Frag1", 0); บน MainActivty หรือ onBackPress
pavel

มันไม่ทำงาน :( นี่คือรหัสของฉันในกรณีของฉัน Fragment คือ Overlaping มันเปิด Fragment A แต่ Frgment A ซ้อนทับกับ Fragment B
Priyanka

FragmentManager fragmentManager = getActivity (). getSupportFragmentManager (); fragmentManager.popBackStack (FragmentA.class.getName () FragmentManager.POP_BACK_STACK_INCLUSIVE); FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction (); FragmentE fragmentE = FragmentE ใหม่ (); fragmentTransaction.replace (R.id.fragment_content, fragmentE, fragmentE.getClass (). getName ()); fragmentTransaction.commit ();
Priyanka

5

หลังจาก @Arvis ตอบกลับฉันตัดสินใจที่จะขุดลึกลงไปอีกและฉันได้เขียนบทความเทคโนโลยีเกี่ยวกับเรื่องนี้ที่นี่: http://www.andreabaccega.com/blog/2015/08/16/how-to-avoid-fragments-overlapping- เนื่องจากการ backstack ฝันร้ายใน Android /

สำหรับนักพัฒนาที่ขี้เกียจ โซลูชันของฉันประกอบด้วยการเพิ่มธุรกรรมลงในแบ็คสแต็คเสมอและดำเนินการเพิ่มเติมFragmentManager.popBackStackImmediate()เมื่อจำเป็น (โดยอัตโนมัติ)

โค้ดเป็นโค้ดไม่กี่บรรทัดและในตัวอย่างของฉันฉันต้องการข้ามจาก C ไป A โดยไม่ต้องข้ามกลับไปที่ "B" หากผู้ใช้ไม่ได้ลึกลงไปในกองหลัง (เช่นจาก C ไปที่ D)

ดังนั้นรหัสที่แนบมาจะทำงานดังต่อไปนี้ A -> B -> C (ย้อนกลับ) -> A & A -> B -> C -> D (ย้อนกลับ) -> C (กลับ) -> B (ย้อนกลับ) -> A

ที่ไหน

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

ออกจาก "B" ถึง "C" เหมือนในคำถาม

ตกลงนี่คือรหัส :)

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
ได้รับความผิดพลาดในบรรทัดนี้ fragmentManager.popBackStackImmediate (); ข้อผิดพลาด: java.lang.IllegalStateException: FragmentManager กำลังดำเนินการธุรกรรมที่ com.example.myapplication.FragmentA $ 2.onBackStackChanged (FragmentA.java:43)
Priyanka

1

หากคุณกำลังดิ้นรนกับ 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 () ในการจัดการชิ้นส่วน


0

ฉันคิดว่าเมื่อฉันอ่านเรื่องราวของคุณที่ [3] อยู่ในกองหลังด้วย สิ่งนี้อธิบายว่าทำไมคุณจึงเห็นมันกะพริบ

วิธีแก้ไขคือห้ามตั้งค่า [3] บนสแต็ก


สวัสดี jdekei ขอบคุณสำหรับข้อมูลของคุณ ปัญหาคือฉันไม่เห็นว่าฉันกำลังเพิ่ม [3] ไปที่ด้านหลังตรงไหน ฉันได้เพิ่มโค้ดอีกชิ้นที่แสดงให้เห็นถึงการนำทางที่ฉันใช้งานโดยใช้ปุ่ม
Chris Birch

มันช่วยฉันด้วย แต่ฉันใช้แค่ removeCurFragment จากโค้ดของคุณและตัวตรวจสอบส่วนย่อยอีกเล็กน้อย เปลี่ยนวิธีการทำงานที่ดีสำหรับฉันอาจจะว่าs old method issue but now itไม่เป็นไร ขอบคุณ
Viktor V.

0

ฉันมีปัญหาคล้ายกันที่ฉันมีชิ้นส่วนติดต่อกัน 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();
    }

0

executePendingTransactions(), commitNow()ไม่ได้ทำงาน (

ทำงานใน androidx (jetpack)

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);
    }
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.