วิธีการคืนค่า Fragment จาก BackStack หากมีอยู่


151

ฉันกำลังเรียนรู้วิธีใช้ชิ้นส่วนต่างๆ ฉันมีสามกรณีของการFragmentเริ่มต้นที่ด้านบนของชั้นเรียน ฉันกำลังเพิ่มส่วนในกิจกรรมเช่นนี้:

การประกาศและการเริ่มต้น:

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

การเปลี่ยน / เพิ่ม:

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

ตัวอย่างเหล่านี้ทำงานอย่างถูกต้อง ทุกส่วนจะถูกแนบกับกิจกรรมและถูกบันทึกลงในกองหลังโดยไม่มีปัญหาใด ๆ

ดังนั้นเมื่อผมเปิดA, Cแล้วB, สแต็คลักษณะเช่นนี้

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

และเมื่อฉันกดปุ่ม 'ย้อนกลับ' BถูกทำลายและCกลับมาทำงานต่อ

แต่เมื่อฉันเปิดใช้แฟรกเมนต์Aเป็นครั้งที่สองแทนที่จะกลับมาทำงานต่อจากสแต็กหลังจะถูกเพิ่มที่ด้านบนของสแต็กหลัง

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

แต่ฉันต้องการดำเนินการต่อAและทำลายชิ้นส่วนทั้งหมดที่อยู่ด้านบน (ถ้ามี) ที่จริงแล้วฉันชอบพฤติกรรมแบ็คสแต็คเริ่มต้น

ฉันจะทำสิ่งนี้ได้อย่างไร

คาดว่า: ( Aควรดำเนินการต่อและชิ้นส่วนบนสุดควรถูกทำลาย)

| |
| |
| |
|A|
___

แก้ไข: (แนะนำโดย A - C)

นี่คือรหัสทดลองของฉัน:

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

ปัญหาคือเมื่อฉันเปิดAและจากBนั้นกด 'ย้อนกลับ' BจะถูกลบออกและAกลับมาทำงานต่อ และกด 'ย้อนกลับ' เป็นครั้งที่สองควรออกจากแอป แต่มันกำลังแสดงหน้าต่างว่างเปล่าและฉันต้องกดปุ่มนี้เป็นครั้งที่สามเพื่อปิดมัน

นอกจากนี้เมื่อฉันเปิดตัวAจากBนั้นCจากนั้นBอีกครั้ง ...

ที่คาดว่าจะ

| |
| |
|B|
|A|
___

จริง:

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

ฉันควรแทนที่onBackPressed()ด้วยการปรับแต่งใด ๆ หรือฉันขาดอะไร

คำตอบ:


279

การอ่านเอกสารมีวิธีที่จะแสดงป๊อปอัพสแต็คตามชื่อธุรกรรมหรือรหัสที่ระบุโดยการส่งมอบ การใช้ชื่ออาจง่ายกว่าเพราะไม่ควรต้องติดตามหมายเลขที่อาจมีการเปลี่ยนแปลงและตอกย้ำตรรกะ "Unique back stack stack"

เนื่องจากคุณต้องการรายการสแต็กกลับเพียงครั้งเดียวต่อFragmentให้ตั้งชื่อสถานะย้อนกลับเป็นชื่อคลาสของ Fragment (ผ่านgetClass().getName()) จากนั้นเมื่อแทนที่ a Fragmentให้ใช้popBackStackImmediate()วิธีการ ถ้ามันคืนค่าจริงมันหมายความว่ามีอินสแตนซ์ของ Fragment ในแบ็คสแต็ค ถ้าไม่ให้ดำเนินการตรรกะการเปลี่ยนชิ้นส่วนจริง

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

แก้ไข

ปัญหาคือ - เมื่อฉันเปิด A และ B จากนั้นกดปุ่มย้อนกลับ B จะถูกลบและ A จะกลับมาทำงานต่อ และกดปุ่มย้อนกลับอีกครั้งควรออกจากแอพ แต่มันกำลังแสดงหน้าต่างว่างและต้องการสื่ออื่นเพื่อปิดมัน

นี่เป็นเพราะFragmentTransactionกำลังถูกเพิ่มลงในสแต็กหลังเพื่อให้แน่ใจว่าเราสามารถป๊อปเศษชิ้นส่วนด้านบนได้ในภายหลัง การแก้ไขอย่างรวดเร็วสำหรับสิ่งนี้กำลังแทนที่onBackPressed()และจบกิจกรรมหากสแต็กด้านหลังมีเพียง 1Fragment

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

เกี่ยวกับรายการสแต็กหลังซ้ำคำสั่งเงื่อนไขของคุณที่แทนที่ส่วนถ้ามันไม่ได้ถูกแตกแตกต่างอย่างชัดเจนกว่าสิ่งที่รหัสต้นฉบับของฉัน สิ่งที่คุณกำลังทำคือการเพิ่มไปที่สแต็คด้านหลังโดยไม่คำนึงว่าสแต็กหลังถูกเปิดหรือไม่

สิ่งนี้ควรใกล้เคียงกับสิ่งที่คุณต้องการ:

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

เงื่อนไขมีการเปลี่ยนแปลงเล็กน้อยตั้งแต่การเลือกชิ้นส่วนเดียวกันในขณะที่มองเห็นได้ก็เป็นสาเหตุให้เกิดรายการซ้ำกัน

การดำเนินงาน:

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

ซึ่งหมายความว่าคุณควรคัดลอกreplaceFragment()วิธีการอัพเดทในชั้นเรียนของคุณแล้วเปลี่ยน

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

ดังนั้นมันจึงเป็นเพียง

replaceFragment (fragmentName);

แก้ไข # 2

หากต้องการอัปเดตลิ้นชักเมื่อเปลี่ยนสแต็คหลังให้สร้างเมธอดที่ยอมรับใน Fragment และเปรียบเทียบชื่อคลาส หากมีสิ่งใดที่ตรงกันให้เปลี่ยนชื่อและการเลือก เพิ่มOnBackStackChangedListenerและให้มันเรียกวิธีการอัปเดตของคุณหากมีส่วนที่ถูกต้อง

ตัวอย่างเช่นในกิจกรรมของonCreate()เพิ่ม

getSupportFragmentManager().addOnBackStackChangedListener(new OnBackStackChangedListener() {

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

  }
});

และวิธีอื่น ๆ :

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

Fragmentตอนนี้เมื่อใดก็ตามที่มีการเปลี่ยนแปลงกลับกองชื่อและตำแหน่งการตรวจสอบจะสะท้อนให้เห็นถึงการมองเห็น


ขอบคุณสำหรับคำตอบ. :) มันทำงานบางส่วน ฉันได้แก้ไขคำถามที่มีความคืบหน้า โปรดดู)
Kaidul

2
@RishabhSrivastava โปรดทราบว่า "ป๊อป" มักจะทำลายส่วนที่สูงที่สุด วิธีการที่เรียกว่าอันดับแรกขึ้นกับ - ดูที่ Fragment Lifecycle สำหรับกรณีของคุณฉันจะใช้OnBackstackChangedListenerเพื่อให้คุณสามารถรับประกันได้ว่าคุณกำลังจัดการกับส่วนที่ถูกต้อง
A - C

2
@RishabhSrivastava ซึ่งเป็นสาเหตุที่ฉันแนะนำOnBackstackChangedListenerเช่นกัน
A - C

1
@AlexanderFarber คุณถูกต้อง ย้อนกลับไปเมื่อฉันตอบคำถามนี้ฉันไม่ได้คิดinstanceof:)
A - C

2
วิธีการแก้ปัญหาของคุณเสียงดี แต่ถ้าคุณมีสามชิ้นส่วนและคลิกที่พวกเขาA-B-C-Bและกดกลับมาครั้งหนึ่งคุณจะจะกลับไปและไม่กลับไปA Cนั่นเป็นเพราะpopBackStackImmediateไม่เพียง แต่ปรากฏแท็กที่กำหนด แต่ยังรวมถึงทุกอย่างข้างต้น มีงานรอบ ๆ บ้างไหม?
Raeglan

10

ฉันคิดว่าวิธีนี้แก้ปัญหาของคุณ:

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

ซึ่งโพสต์ครั้งแรกใน คำถามนี้


โปรดแจ้งให้เราทราบหากการตอบแบบนี้ขัดต่อกฎที่ฉันต้องการให้คนอื่นเห็นในโพสต์นี้ ขอบคุณ ..
erluxman

ฉันไม่แน่ใจว่ามันขัดกับกฎ (อาจจะไม่) แต่สิ่งนี้มีประโยชน์อย่างแน่นอน
Kathir

1
การใช้บรรทัดที่สามคืออะไร -> manager.findFragmentByTag (แท็ก); ดูเหมือนว่ามันจะไม่ทำอะไรเลย ..
Rik van Velzen

3

ขั้นตอนที่ 1:ติดตั้งส่วนต่อประสานกับคลาสกิจกรรมของคุณ

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

ขั้นตอนที่ 2:เมื่อคุณเรียกแฟรกเมนต์อื่นให้เพิ่มเมธอดนี้:

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

ฉันรู้ว่ามันค่อนข้างช้าในการตอบคำถามนี้ แต่ฉันแก้ไขปัญหานี้ด้วยตัวเองและคิดว่าควรแบ่งปันกับทุกคน "

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

และใน theBackPressed ()

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

1
@ X-Black ... BaseFragment เป็นคลาสพื้นฐานสำหรับทุกแฟรกเมนต์ที่ใช้ในแอปพลิเคชัน
Vivek Pratap Singh

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

    @Override
    public void onBackStackChanged() {

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

0

วิธีที่ง่ายกว่าจะเปลี่ยนบรรทัดนี้

ft.replace(R.id.content_frame, A); ถึง ft.add(R.id.content_frame, A);

และภายในเลย์เอาต์ XML ของคุณโปรดใช้

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

Clickable หมายความว่าสามารถคลิกได้โดยอุปกรณ์ตัวชี้หรือแตะด้วยอุปกรณ์แบบสัมผัส

Focusableหมายความว่าสามารถรับโฟกัสจากอุปกรณ์ป้อนข้อมูลเช่นแป้นพิมพ์ อุปกรณ์อินพุตเช่นแป้นพิมพ์ไม่สามารถตัดสินใจได้ว่ามุมมองใดที่จะส่งเหตุการณ์อินพุตให้เป็นไปตามสัญญาณอินพุตดังนั้นจึงส่งไปยังมุมมองที่มีโฟกัส

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