Sự cố với ngăn xếp Android Fragment trở lại


121

Tôi đã gặp một vấn đề lớn với cách hoạt động của backstack phân mảnh android và rất biết ơn nếu có bất kỳ sự trợ giúp nào được cung cấp.

Hãy tưởng tượng bạn có 3 mảnh vỡ

[1] [2] [3]

Tôi muốn người dùng có thể điều hướng [1] > [2] > [3]nhưng đang trên đường quay lại (nhấn nút quay lại) [3] > [1].

Như tôi đã tưởng tượng, điều này sẽ được thực hiện bằng cách không gọi addToBackStack(..)khi tạo giao dịch đưa phân đoạn [2]vào trình giữ phân đoạn được định nghĩa trong XML.

Thực tế của điều này dường như là nếu tôi không muốn [2]xuất hiện lại khi người dùng nhấn nút quay lại [3], tôi không được gọi addToBackStacktrong giao dịch hiển thị phân đoạn [3]. Điều này có vẻ hoàn toàn phản trực quan (có lẽ đến từ thế giới iOS).

Dù sao nếu tôi làm theo cách này, khi tôi đi từ [1] > [2]và nhấn lại, tôi sẽ quay trở lại [1]như mong đợi.

Nếu tôi đi [1] > [2] > [3]và sau đó nhấn trở lại, tôi sẽ quay lại [1](như mong đợi). Bây giờ hành vi kỳ lạ xảy ra khi tôi thử và nhảy [2]lại từ đó [1]. Trước hết [3]được hiển thị ngắn gọn trước khi [2]xuất hiện. Nếu tôi nhấn lại vào thời điểm [3]này sẽ hiển thị và nếu tôi nhấn lại một lần nữa, ứng dụng sẽ thoát.

Bất cứ ai có thể giúp tôi hiểu chuyện gì đang xảy ra ở đây?


Và đây là tệp xml bố cục cho hoạt động chính của tôi:

<?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"

        />



Cập nhật Đây là mã tôi đang sử dụng để xây dựng bởi 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]

Cảm ơn nhiều


nhưng phân đoạn chồng chéo lên nhau, khi tôi backpress từ Phân đoạn C sang phân đoạn A.
Priyanka

Tôi có cùng một vấn đề, làm thế nào để bạn khắc phục điều đó?
Priyanka

tham khảo ans của tôi .. nó có thể giúp bạn < stackoverflow.com/questions/14971780/... >
MD Khali

Câu trả lời:


203

Giải thích: chuyện gì đang xảy ra ở đây?

Nếu chúng ta ghi nhớ rằng điều đó .replace()ngang bằng với những .remove().add()gì chúng ta biết trong tài liệu:

Thay thế một phân đoạn hiện có đã được thêm vào vùng chứa. Điều này về cơ bản giống như việc gọi remove(Fragment)cho tất cả các đoạn được thêm hiện tại đã được thêm với cùng một containerViewIdvà sau đó add(int, Fragment, String)với cùng các đối số được đưa ra ở đây.

thì những gì đang xảy ra là như thế này (Tôi đang thêm các số vào khung để làm rõ hơn):

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

(ở đây tất cả những thứ gây hiểu lầm bắt đầu xảy ra)

Hãy nhớ rằng .addToBackStack()chỉ lưu giao dịch không phải là phân đoạn như chính nó! Vì vậy, bây giờ chúng tôi có frag3trên bố cục:

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

Giải pháp khả thi

Cân nhắc triển khai FragmentManager.BackStackChangedListenerđể theo dõi những thay đổi trong ngăn xếp phía sau và áp dụng logic của bạn trong onBackStackChanged()methode:


@Arvis giải thích tốt. Tuy nhiên, làm thế nào chúng ta có thể ngăn chặn hành vi này mà không sử dụng các cách hack như cách từ DexterMoon hoặc từ Nemanja bằng cách sử dụng popBackStack hiển thị Fragment trong khi phát hoạt ảnh chuyển tiếp?
momo

@momo bạn có thể triển khai FragmentManager.BackStackChangedListenerđể theo dõi các thay đổi đối với ngăn xếp phía sau. Giám sát tất cả các giao dịch của bạn với onBackStackChanged()methode và hành động khi cần thiết: đối với ví dụ. theo dõi số lượng giao dịch trong BackStack; kiểm tra giao dịch cụ thể bằng tên ( FragmentTransaction addToBackStack (String name)), v.v.
Arvis 27/09/13

Cảm ơn vì đã trả lời. Tôi thực sự đã thử điều đó sớm hơn ngày hôm nay, đăng ký trình nghe và xóa phân đoạn khỏi onBackstackChange. Trong khi quá trình chuyển đổi popping được phát, phân đoạn sẽ trở thành một vùng trống màu trắng. Tôi đoán phương pháp được kích hoạt khi popping bắt đầu hoạt ảnh chứ không phải khi kết thúc ...
momo

4
Tránh sử dụng ngăn xếp trở lại! nó không thực sự giúp ích cho hiệu quả tổng thể! sử dụng thay thế thuần túy () hoặc thậm chí tốt hơn là xóa / thêm mỗi khi bạn muốn điều hướng!
stack_ved

@Arvis bạn có thể giúp tôi không, tôi đang gặp vấn đề tương tự .... số lượng ngăn xếp là 0 nhưng phân đoạn của tôi vẫn hiển thị?
Erum

33

Đúng!!! sau nhiều lần nhổ tóc, cuối cùng tôi đã tìm ra cách để làm cho nó hoạt động đúng cách.

Có vẻ như phân đoạn [3] không bị xóa khỏi chế độ xem khi nhấn trở lại nên bạn phải làm thủ công!

Trước hết, không sử dụng Replace () mà sử dụng loại bỏ và thêm riêng. Có vẻ như thay thế () không hoạt động bình thường.

Phần tiếp theo của việc này là ghi đè phương thức onKeyDown và loại bỏ phân đoạn hiện tại mỗi khi nhấn nút quay lại.

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

}

Hi vọng điêu nay co ich!


điều này hoạt động, nhưng điều này không thể được sử dụng trong dự án của tôi vì phân đoạn được tải lại! bất kỳ đề xuất?
TharakaNirmana

Nhưng, nếu tôi đang làm điều này. Tôi nhận được màn hình trống khi tôi trở về từ C đến A cho Fragment C.
Nigam Patro

Tôi nghi ngờ câu trả lời @Arvis nên ném một số ánh sáng vào vấn đề của bạn
Chris Birch

16

Trước hết, cảm ơn @Arvis vì một lời giải thích mở mang tầm mắt.

Tôi thích giải pháp khác cho câu trả lời được chấp nhận ở đây cho vấn đề này. Tôi không thích gây rối với hành vi ghi đè trở lại bất kỳ quá mức thực sự cần thiết và khi tôi cố gắng tự mình thêm và xóa các phân đoạn mà không bật lên ngăn xếp lùi mặc định khi nhấn nút quay lại, tôi thấy mình đang ở trong địa ngục phân mảnh :) Nếu bạn. thêm f2 trên f1 khi bạn loại bỏ nó f1 sẽ không gọi bất kỳ phương thức gọi lại nào như onResume, onStart, v.v. và điều đó có thể rất đáng tiếc.

Nhưng dù sao thì đây là cách tôi làm:

Hiện trên màn hình chỉ là mảnh f1.

f1 -> f2

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

không có gì khác thường ở đây. Hơn trong phân đoạn f2, mã này đưa bạn đến phân mảnh f3.

f2 -> f3

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

Tôi không chắc bằng cách đọc tài liệu nếu điều này có hoạt động hay không, phương thức giao dịch bật lên này được cho là không đồng bộ và có thể cách tốt hơn là gọi popBackStackIm Instant (). Nhưng theo như tôi có thể nói trên các thiết bị của mình, nó hoạt động hoàn hảo.

Giải pháp thay thế đã nói sẽ là:

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

Ở đây thực sự sẽ có một đoạn ngắn quay trở lại f1 và chuyển sang f3, vì vậy sẽ có một chút trục trặc ở đó.

Đây thực sự là tất cả những gì bạn phải làm, không cần ghi đè hành vi ngăn xếp ngược ...


6
nhưng trục trặc, đó là một vấn đề
Zyoo

Điều này có vẻ ít "hack-ey" hơn là lắng nghe các thay đổi của BackStack. Cảm ơn.
ahaisting

13

Tôi biết đó là một hàng đợi cũ nhưng tôi gặp vấn đề tương tự và sửa nó như thế này:

Đầu tiên, thêm Fragment1 vào BackStack với một tên (ví dụ: "Frag1"):

frag = new Fragment1();

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

Và sau đó, bất cứ khi nào bạn muốn quay lại Fragment1 (ngay cả sau khi thêm 10 đoạn bên trên nó), chỉ cần gọi popBackStackIm ngay với tên:

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

Hy vọng nó sẽ giúp ích cho ai đó :)


1
Câu trả lời chính xác. Tôi đã phải sử dụng getSupportFragmentManager (). PopBackStackIm Instant ("Frag1", FragmentManager.POP_BACK_STACK_INCLUSIVE); để làm cho nó hoạt động cho trường hợp sử dụng của tôi
eliasbagley

1
Tôi phải đặt mã này ở đâu ???? getSupportFragmentManager (). popBackStackIm Instant ("Frag1", 0); Trên MainActivty hoặc onBackPress
pavel

nó không hoạt động. :( đây là mã của tôi, Trong trường hợp của tôi, Fragment is Overlaping. Nó mở Fragment A, nhưng Frgment A lại chồng lên Fragment B.
Priyanka

FragmentManager segmentManager = getActivity (). GetSupportFragmentManager (); gmentManager.popBackStack (FragmentA.class.getName (), FragmentManager.POP_BACK_STACK_INCLUSIVE); FragmentTransaction mảnhTransaction = mảnhManager.beginTransaction (); FragmentEgmentE = new FragmentE (); mảnhTransaction.replace (R.id.fragment_content, mảnhE, mảnhE.getClass (). getName ()); mảnhTransaction.commit ();
Priyanka

5

Sau khi @Arvis trả lời, tôi quyết định tìm hiểu sâu hơn nữa và tôi đã viết một bài báo công nghệ về điều này tại đây: http://www.andreabaccega.com/blog/2015/08/16/how-to-avoid-fragment-overlapping- do-to-backstack-evil-in-android /

Đối với những nhà phát triển lười biếng xung quanh. Giải pháp của tôi bao gồm việc luôn thêm các giao dịch vào backstack và thực hiện thêm FragmentManager.popBackStackImmediate()khi cần (tự động).

Mã này có rất ít dòng mã và, trong ví dụ của tôi, tôi muốn bỏ qua từ C đến A mà không quay lại "B" nếu người dùng không tìm hiểu sâu hơn về phía sau (ví dụ: từ C điều hướng đến D).

Do đó, mã đính kèm sẽ hoạt động như sau A -> B -> C (quay lại) -> A & A -> B -> C -> D (quay lại) -> C (quay lại) -> B (quay lại) -> A

Ở đâu

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

được cấp từ "B" đến "C" như trong câu hỏi.

Ok, Ok đây là mã :)

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
gặp sự cố trên dòng này segmentManager.popBackStackIm Instant (); error: java.lang.IllegalStateException: FragmentManager đã thực hiện các giao dịch tại com.example.myapplication.FragmentA $ 2.onBackStackChanged (FragmentA.java:43)
Priyanka

1

Nếu bạn đang gặp khó khăn với addToBackStack () & popBackStack () thì chỉ cần sử dụng

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

Trong Hoạt động của bạn Trong OnBackPressed (), tìm phân đoạn theo thẻ và sau đó thực hiện công việc của bạn

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

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

Để biết thêm thông tin https://github.com/DattaHujare/NavigationDrawer Tôi không bao giờ sử dụng addToBackStack () để xử lý phân đoạn.


0

Tôi nghĩ, khi tôi đọc câu chuyện của bạn, điều đó [3] cũng đang trở nên khó khăn hơn. Điều này giải thích tại sao bạn thấy nó nhấp nháy.

Giải pháp là không bao giờ đặt [3] trên ngăn xếp.


Xin chào jdekei Cảm ơn bạn đã đóng góp ý kiến. Vấn đề là tôi không thể thấy nơi tôi đang thêm [3] vào backstack. Tôi đã thêm một đoạn mã khác chứng minh (theo chương trình) chính xác điều hướng mà tôi đang thực hiện bằng cách sử dụng các nút.
Chris Birch

Nó cũng giúp tôi nhưng tôi chỉ sử dụng mã removeCurFragment từ bạn và một chút trình kiểm tra phân đoạn khác. Phương pháp thay thế hoạt động tốt đối với tôi, có thể được, điều đó s old method issue but now ittốt. Cảm ơn
Viktor V.

0

Tôi đã gặp sự cố tương tự trong đó tôi có 3 đoạn liên tiếp trong cùng một Activity[M1.F0] -> [M1.F1] -> [M1.F2] theo sau bởi một lệnh gọi đến Activity[M2] mới. Nếu người dùng nhấn một nút trong [M2], tôi muốn quay lại [M1, F1] thay vì [M1, F2], đó là hành vi nhấn lùi đã thực hiện.

Để thực hiện điều này, tôi xóa [M1, F2], gọi hiển thị trên [M1, F1], thực hiện giao dịch và sau đó thêm lại [M1, F2] bằng cách gọi nó với ẩn. Điều này đã loại bỏ báo chí phía sau thừa mà nếu không sẽ bị bỏ lại.

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

Xin chào Sau khi thực hiện mã này: Tôi không thể thấy giá trị của Fragment2 khi nhấn phím Quay lại. Mã của tôi:

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()không hoạt động (

Làm việc trong 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);
    }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.