Android 4.2: hành vi ngăn xếp trở lại với các phân đoạn lồng nhau


108

Với Android 4.2, thư viện hỗ trợ có hỗ trợ cho các đoạn lồng nhau, xem tại đây . Tôi đã thử với nó và tìm thấy một hành vi / lỗi thú vị liên quan đến back stack và getChildFragmentManager () . Khi sử dụng getChildFragmentManager () và addToBackStack (Tên chuỗi), bằng cách nhấn nút quay lại, hệ thống không chạy xuống ngăn xếp trở lại phân đoạn trước đó. Mặt khác, khi sử dụng getFragmentManager () và addToBackStack (Tên chuỗi), bằng cách nhấn nút quay lại, hệ thống sẽ quay lại phân đoạn trước đó.

Đối với tôi, hành vi này là bất ngờ. Bằng cách nhấn nút quay lại trên thiết bị của mình, tôi hy vọng rằng phân đoạn được thêm cuối cùng vào ngăn xếp phía sau sẽ được xuất hiện, ngay cả khi phân đoạn đã được thêm vào ngăn xếp sau trong trình quản lý phân mảnh của trẻ em.

Hành vi này có đúng không? Hành vi này có phải là lỗi không? Có một công việc xung quanh cho vấn đề này?

mã mẫu với getChildFragmentManager ():

public class FragmentceptionActivity extends FragmentActivity {

@Override
protected void onCreate(Bundle arg0) {
    super.onCreate(arg0);

    final FrameLayout wrapper1 = new FrameLayout(this);
    wrapper1.setLayoutParams(new FrameLayout.LayoutParams(
            FrameLayout.LayoutParams.MATCH_PARENT,
            FrameLayout.LayoutParams.MATCH_PARENT));
    wrapper1.setId(1);

    final FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
            FrameLayout.LayoutParams.MATCH_PARENT,
            FrameLayout.LayoutParams.WRAP_CONTENT);
    params.topMargin = 0;

    final TextView text = new TextView(this);
    text.setLayoutParams(params);
    text.setText("fragment 1");
    wrapper1.addView(text);

    setContentView(wrapper1);

    getSupportFragmentManager().beginTransaction().addToBackStack(null)
            .add(1, new Fragment1()).commit();
}

public class Fragment1 extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        final FrameLayout wrapper2 = new FrameLayout(getActivity());
        wrapper2.setLayoutParams(new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.MATCH_PARENT));
        wrapper2.setId(2);

        final FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.WRAP_CONTENT);
        params.topMargin = 100;

        final TextView text = new TextView(getActivity());
        text.setLayoutParams(params);
        text.setText("fragment 2");
        wrapper2.addView(text);

        return wrapper2;
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        getFragmentManager().beginTransaction().addToBackStack(null)
                .add(2, new Fragment2()).commit();
    }
}

public class Fragment2 extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        final FrameLayout wrapper3 = new FrameLayout(getActivity());
        wrapper3.setLayoutParams(new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.MATCH_PARENT));
        wrapper3.setId(3);

        final FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.WRAP_CONTENT);
        params.topMargin = 200;

        final TextView text = new TextView(getActivity());
        text.setLayoutParams(params);
        text.setText("fragment 3");
        wrapper3.addView(text);

        return wrapper3;
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        getChildFragmentManager().beginTransaction().addToBackStack(null)
                .add(3, new Fragment3()).commit();
    }
}

public class Fragment3 extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        final FrameLayout wrapper4 = new FrameLayout(getActivity());
        wrapper4.setLayoutParams(new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.MATCH_PARENT));
        wrapper4.setId(4);

        final FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.WRAP_CONTENT);
        params.topMargin = 300;

        final TextView text = new TextView(getActivity());
        text.setLayoutParams(params);
        text.setText("fragment 4");
        wrapper4.addView(text);

        return wrapper4;
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        getChildFragmentManager().beginTransaction().addToBackStack(null)
                .add(4, new Fragment4()).commit();
    }
}

public class Fragment4 extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        final FrameLayout wrapper5 = new FrameLayout(getActivity());
        wrapper5.setLayoutParams(new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.MATCH_PARENT));
        wrapper5.setId(5);

        final FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.WRAP_CONTENT);
        params.topMargin = 400;

        final TextView text = new TextView(getActivity());
        text.setLayoutParams(params);
        text.setText("fragment 5");
        wrapper5.addView(text);

        return wrapper5;
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
    }
}

}

mã mẫu với getFragmentManager ():

public class FragmentceptionActivity extends FragmentActivity {

@Override
protected void onCreate(Bundle arg0) {
    super.onCreate(arg0);

    final FrameLayout wrapper1 = new FrameLayout(this);
    wrapper1.setLayoutParams(new FrameLayout.LayoutParams(
            FrameLayout.LayoutParams.MATCH_PARENT,
            FrameLayout.LayoutParams.MATCH_PARENT));
    wrapper1.setId(1);

    final FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
            FrameLayout.LayoutParams.MATCH_PARENT,
            FrameLayout.LayoutParams.WRAP_CONTENT);
    params.topMargin = 0;

    final TextView text = new TextView(this);
    text.setLayoutParams(params);
    text.setText("fragment 1");
    wrapper1.addView(text);

    setContentView(wrapper1);

    getSupportFragmentManager().beginTransaction().addToBackStack(null)
            .add(1, new Fragment1()).commit();
}

public class Fragment1 extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        final FrameLayout wrapper2 = new FrameLayout(getActivity());
        wrapper2.setLayoutParams(new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.MATCH_PARENT));
        wrapper2.setId(2);

        final FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.WRAP_CONTENT);
        params.topMargin = 100;

        final TextView text = new TextView(getActivity());
        text.setLayoutParams(params);
        text.setText("fragment 2");
        wrapper2.addView(text);

        return wrapper2;
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        getFragmentManager().beginTransaction().addToBackStack(null)
                .add(2, new Fragment2()).commit();
    }
}

public class Fragment2 extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        final FrameLayout wrapper3 = new FrameLayout(getActivity());
        wrapper3.setLayoutParams(new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.MATCH_PARENT));
        wrapper3.setId(3);

        final FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.WRAP_CONTENT);
        params.topMargin = 200;

        final TextView text = new TextView(getActivity());
        text.setLayoutParams(params);
        text.setText("fragment 3");
        wrapper3.addView(text);

        return wrapper3;
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        getFragmentManager().beginTransaction().addToBackStack(null)
                .add(3, new Fragment3()).commit();
    }
}

public class Fragment3 extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        final FrameLayout wrapper4 = new FrameLayout(getActivity());
        wrapper4.setLayoutParams(new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.MATCH_PARENT));
        wrapper4.setId(4);

        final FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.WRAP_CONTENT);
        params.topMargin = 300;

        final TextView text = new TextView(getActivity());
        text.setLayoutParams(params);
        text.setText("fragment 4");
        wrapper4.addView(text);

        return wrapper4;
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        getFragmentManager().beginTransaction().addToBackStack(null)
                .add(4, new Fragment4()).commit();
    }
}

public class Fragment4 extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        final FrameLayout wrapper5 = new FrameLayout(getActivity());
        wrapper5.setLayoutParams(new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.MATCH_PARENT));
        wrapper5.setId(5);

        final FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.WRAP_CONTENT);
        params.topMargin = 400;

        final TextView text = new TextView(getActivity());
        text.setLayoutParams(params);
        text.setText("fragment 5");
        wrapper5.addView(text);

        return wrapper5;
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
    }
}

}

hmmm điều đó rất thú vị. Tôi cũng không mong đợi hành vi đó. Nếu đúng như vậy thì có vẻ như chúng ta có thể đã ghi đè trên OnBackPressed và trong phương thức đó, lấy một phần của phân đoạn với các phân đoạn con, sau đó lấy một phần ChildFragmentManager để thực hiện một giao dịch pop.
Marco RS

@Marco, Chính xác, nhưng đó là một cách giải quyết. Tại sao API không hoạt động bình thường?
Egor

Câu trả lời:


67

Giải pháp này có thể là phiên bản tốt hơn của @Sean answer:

@Override
public void onBackPressed() {
    // if there is a fragment and the back stack of this fragment is not empty,
    // then emulate 'onBackPressed' behaviour, because in default, it is not working
    FragmentManager fm = getSupportFragmentManager();
    for (Fragment frag : fm.getFragments()) {
        if (frag.isVisible()) {
            FragmentManager childFm = frag.getChildFragmentManager();
            if (childFm.getBackStackEntryCount() > 0) {
                childFm.popBackStack();
                return;
            }
        }
    }
    super.onBackPressed();
}

Một lần nữa, tôi đã chuẩn bị giải pháp này dựa trên câu trả lời @Sean ở trên.

Như @ AZ13 đã nói, giải pháp này chỉ khả thi trong các tình huống phân mảnh con một cấp. Trong trường hợp phân đoạn nhiều cấp, các công việc trở nên phức tạp một chút, vì vậy tôi khuyên bạn chỉ nên thử giải pháp này trong trường hợp khả thi mà tôi đã nói. =)

Lưu ý:getFragmentsphương thức hiện là phương thức riêng tư, giải pháp này sẽ không hoạt động. Bạn có thể kiểm tra các nhận xét để tìm liên kết gợi ý giải pháp cho tình huống này.


2
林奕忠câu trả lời nên được sử dụng thay thế này - nó xử lý một cách chính xác nhiều mảnh vỡ lồng nhau
Hameno

Nó có thực sự là mã hoạt động không? Ngay cả getFragment cũng không phải là phương thức công khai? stackoverflow.com/a/42572412/4729203
wonsuc

Nó đã hoạt động một lần nhưng có thể không hoạt động ngay bây giờ nếu getFragment không được công khai nữa.
ismailarilik

1
getFragmentshiện đã được công khai.
grrigore

điều này không hoạt động chính xác nếu bạn có FragA -> Frag B -> Frag C bên trong Pager ...
Zhar

57

Có vẻ như một lỗi. Hãy xem tại: http://code.google.com/p/android/issues/detail?id=40323

Đối với giải pháp thay thế tôi đã sử dụng thành công (như được đề xuất trong nhận xét):

    @Override
public void onBackPressed() {

    // If the fragment exists and has some back-stack entry
    if (mActivityDirectFragment != null && mActivityDirectFragment.getChildFragmentManager().getBackStackEntryCount() > 0){
        // Get the fragment fragment manager - and pop the backstack
        mActivityDirectFragment.getChildFragmentManager().popBackStack();
    }
    // Else, nothing in the direct fragment back stack
    else{
        // Let super handle the back press
        super.onBackPressed();          
    }
}

12
Thật không may, đây không phải là một giải pháp thích hợp và chỉ là một cách giải quyết cho trường hợp đơn giản nhất. Chỉ cần giả sử bạn có một phân đoạn, trong đó có một phân đoạn khác, chứa một phân đoạn khác, v.v. Sau đó, bạn phải truyền xuống cuộc gọi đến đoạn cuối cùng, trong đó có một khác hoặc mảnh hơn: /
AZ13

@MuhammadBabar Không, bạn sẽ không. Nếu là giá trị rỗng, phần thứ hai của câu lệnh sẽ không được đánh giá vì nó đã được xác định là sai. toán hạng là & và không | .
Sean

25

Câu trả lời thực sự cho câu hỏi này nằm trong chức năng của Giao dịch Phân mảnh được gọi là setPrimaryNavigationFragment.

/**
 * Set a currently active fragment in this FragmentManager as the primary navigation fragment.
 *
 * <p>The primary navigation fragment's
 * {@link Fragment#getChildFragmentManager() child FragmentManager} will be called first
 * to process delegated navigation actions such as {@link FragmentManager#popBackStack()}
 * if no ID or transaction name is provided to pop to. Navigation operations outside of the
 * fragment system may choose to delegate those actions to the primary navigation fragment
 * as returned by {@link FragmentManager#getPrimaryNavigationFragment()}.</p>
 *
 * <p>The fragment provided must currently be added to the FragmentManager to be set as
 * a primary navigation fragment, or previously added as part of this transaction.</p>
 *
 * @param fragment the fragment to set as the primary navigation fragment
 * @return the same FragmentTransaction instance
 */
public abstract FragmentTransaction setPrimaryNavigationFragment(Fragment fragment);

Bạn phải đặt chức năng này trên phân đoạn mẹ ban đầu khi hoạt động thêm nó. Tôi có một hàm ReplaceFragment bên trong hoạt động của mình trông giống như sau:

public void replaceFragment(int containerId, BaseFragment fragment, boolean addToBackstack) {
    FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
    fragmentTransaction.setPrimaryNavigationFragment(fragment);
    if (addToBackstack) {
        fragmentTransaction.addToBackStack(fragment.TAG);
    }

    fragmentTransaction.replace(containerId, fragment).commit();
}

Điều này cung cấp cho bạn hành vi tương tự như khi bạn nhấp trở lại từ Phân đoạn B thông thường trở lại Phân đoạn A, ngoại trừ bây giờ nó cũng ở trên các phân đoạn con!


3
Tìm thấy tuyệt vời! Điều này giải quyết vấn đề được OP đề cập theo cách rõ ràng hơn và ít hackie hơn.
Jona

Đó là sự cố trừ khi một phần của thư viện điều hướng dưới dạng HavHostFragment
Didi

22

Giải pháp này có thể là phiên bản tốt hơn của câu trả lời @ismailarilik:

Phiên bản phân mảnh lồng nhau

private boolean onBackPressed(FragmentManager fm) {
    if (fm != null) {
        if (fm.getBackStackEntryCount() > 0) {
            fm.popBackStack();
            return true;
        }

        List<Fragment> fragList = fm.getFragments();
        if (fragList != null && fragList.size() > 0) {
            for (Fragment frag : fragList) {
                if (frag == null) {
                    continue;
                }
                if (frag.isVisible()) {
                    if (onBackPressed(frag.getChildFragmentManager())) {
                        return true;
                    }
                }
            }
        }
    }
    return false;
}

@Override
public void onBackPressed() {
    FragmentManager fm = getSupportFragmentManager();
    if (onBackPressed(fm)) {
        return;
    }
    super.onBackPressed();
}

nó không xuất hiện để làm việc khi có nhiều cấp độ mảnh lồng nhau
splinter123

Cloud, bạn cho tôi kiến ​​trúc của bạn về các mảnh lồng nhau? Trong ứng dụng của tôi, nó hoạt động. Có lẽ tôi bỏ lỡ điều gì đó.
林奕 忠

9
Thứ tự cho việc mở bao sau không như tôi mong đợi trong ví dụ này. Tôi tin rằng trước tiên bạn nên tìm thấy mảnh có thể nhìn thấy được lồng sâu nhất trong cây và cố gắng mở bao lưng trên đó, sau đó dần dần di chuyển lên trên cây để tìm kiếm các mảnh có thể nhìn thấy khác, nơi bạn có thể bật bao sau cho đến khi nhận được. Một bản sửa lỗi nhanh chóng xử lý các trường hợp đơn giản nhất là thay đổi chức năng onBackPressed (FragmentManager fm) và chuyển khối bật backstack với khối liệt kê các đoạn. Theo cách đó, việc xuất hiện các mảnh vỡ trong các mảnh con lồng nhau sâu hơn sẽ xảy ra đầu tiên
Theo

1
Ngoài ra, khi bạn có nhiều phân mảnh lồng nhau, mỗi phân mảnh con của chúng có thể che một phần màn hình, ý tưởng về việc có một ngăn lưng riêng biệt cho từng trình quản lý phân mảnh con sẽ không còn ý nghĩa nữa. Backstack phải được thống nhất trên tất cả các phân đoạn trong hoạt động và theo dõi các thay đổi của phân đoạn theo thứ tự mà chúng xảy ra, bất kể mức độ lồng ghép của phân đoạn.
Theo

Có thể sử dụng giải pháp này mà không có SupportFragmentManagervà thay vào đó chỉ là Tiêu chuẩn FragmentManagerkhông?
Peter Chappy

13

Với câu trả lời này, nó sẽ xử lý việc kiểm tra ngược đệ quy và cho mỗi phân đoạn cơ hội ghi đè hành vi mặc định. Điều này có nghĩa là bạn có thể có một phân đoạn lưu trữ ViewPager thực hiện một điều gì đó đặc biệt như cuộn đến trang dưới dạng back-stack hoặc cuộn đến trang chủ và sau đó ở lần quay lại tiếp theo, nhấn thoát.

Thêm điều này vào Hoạt động của bạn để mở rộng AppCompatActivity.

@Override
public void onBackPressed()
{
    if(!BaseFragment.handleBackPressed(getSupportFragmentManager())){
        super.onBackPressed();
    }
}

Thêm cái này vào BaseFragment của bạn hoặc lớp mà bạn có thể có tất cả các đoạn của mình kế thừa.

public static boolean handleBackPressed(FragmentManager fm)
{
    if(fm.getFragments() != null){
        for(Fragment frag : fm.getFragments()){
            if(frag != null && frag.isVisible() && frag instanceof BaseFragment){
                if(((BaseFragment)frag).onBackPressed()){
                    return true;
                }
            }
        }
    }
    return false;
}

protected boolean onBackPressed()
{
    FragmentManager fm = getChildFragmentManager();
    if(handleBackPressed(fm)){
        return true;
    }
    else if(getUserVisibleHint() && fm.getBackStackEntryCount() > 0){
        fm.popBackStack();
        return true;
    }
    return false;
}

đây là câu trả lời tuyệt vời, làm việc trên mức API 23/24 và với sự hỗ trợ lib 24.1.1
Rinav

Đã được sử dụng điều này một thời gian, đó là một giải pháp tuyệt vời - nhưng gần đây có cảnh báo biên dịch về việc truy cập trực tiếp vào getFragment () từ nhóm thư viện bên ngoài. Bạn đã cập nhật công việc xung quanh chưa?
Simon Huckett

có sự cố nào đó không hay vào năm 2020, chúng tôi vẫn gặp sự cố và nên thực hiện giải pháp của bạn ?? !!
Zhar

3

Mã này sẽ điều hướng cây các trình quản lý phân mảnh và trả về cây cuối cùng đã được thêm vào có bất kỳ phân mảnh nào mà nó có thể bật ra khỏi ngăn xếp:

private FragmentManager getLastFragmentManagerWithBack(FragmentManager fm)
{
  FragmentManager fmLast = fm;

  List<Fragment> fragments = fm.getFragments();

  for (Fragment f : fragments)
  {
    if ((f.getChildFragmentManager() != null) && (f.getChildFragmentManager().getBackStackEntryCount() > 0))
    {
      fmLast = f.getFragmentManager();
      FragmentManager fmChild = getLastFragmentManagerWithBack(f.getChildFragmentManager());

      if (fmChild != fmLast)
        fmLast = fmChild;
    }
  }

  return fmLast;
}

Gọi phương thức:

@Override
public void onBackPressed()
{
  FragmentManager fm = getLastFragmentManagerWithBack(getSupportFragmentManager());

  if (fm.getBackStackEntryCount() > 0)
  {
    fm.popBackStack();
    return;
  }

  super.onBackPressed();
}

Điều này hoạt động cho tất cả các phân đoạn lồng nhau nếu nó được đăng ký chính xác !! +100
Pratik Saluja

2

Lý do là Hoạt động của bạn bắt nguồn từ FragmentActivity, xử lý việc nhấn phím QUAY LẠI (xem dòng 173 của FragmentActivity .

Trong ứng dụng của chúng tôi, tôi đang sử dụng ViewPager (với các phân đoạn) và mỗi phân đoạn có thể có các phân đoạn lồng nhau. Cách tôi xử lý điều này là:

  • xác định giao diện OnBackKeyPressedListener bằng một phương pháp duy nhất void onBackKeyPressed()
  • đã triển khai giao diện này trong các đoạn "trên cùng" mà ViewPager hiển thị
  • ghi đè onKeyDown và phát hiện nhấn BACK và gọi onBackKeyPressed trong một phân đoạn hiện đang hoạt động trong chế độ xem pager.

Cũng lưu ý rằng tôi đang sử dụng getChildFragmentManager()trong các mảnh để lồng các mảnh một cách chính xác. Bạn có thể xem một cuộc thảo luận và giải thích trong bài đăng của nhà phát triển android này .


2

nếu bạn đang sử dụng phân mảnh trong một phân mảnh thì hãy sử dụng getChildFragmentManager()

getChildFragmentManager().beginTransaction().replace(R.id.fragment_name, fragment).addToBackStack(null).commit();

nếu bạn đang sử dụng phân đoạn con và thay thế nó, hãy sử dụng getParentFragmentManager()

getParentFragmentManager().beginTransaction().replace(R.id.fragment_name, fragment).addToBackStack(null).commit();

nếu cả hai đều không hiệu quả với bạn, hãy thử cái này getActivity().getSupportFragmentManager()

getActivity().getSupportFragmentManager().beginTransaction().replace(R.id.fragment_name, fragment).addToBackStack(null).commit();

1

tôi đã có thể xử lý ngăn xếp ngược phân đoạn bằng cách thêm vào phân đoạn mẹ phương thức này tại phương thức onCreate View () và chuyển chế độ xem gốc.

private void catchBackEvent(View v){
    v.setFocusableInTouchMode(true);
    v.requestFocus();
    v.setOnKeyListener( new OnKeyListener()
    {
        @Override
        public boolean onKey( View v, int keyCode, KeyEvent event )
        {
            if( keyCode == KeyEvent.KEYCODE_BACK )
            {
                if(isEnableFragmentBackStack()){
                    getChildFragmentManager().popBackStack();
                                    setEnableFragmentBackStack(false);
                    return true;
                }
                else
                    return false;   
            }
            return false;
        }
    } );
}

Phương thức isEnableFragmentBackStack () là một cờ boolean để biết khi nào tôi đang ở trên phân đoạn chính hoặc phân đoạn tiếp theo.

Đảm bảo rằng khi bạn cam kết phân đoạn cần có ngăn xếp, thì bạn phải thêm Phương thức addToBackstack.


1

Giải pháp này có thể tốt hơn, vì nó kiểm tra tất cả các cấp độ của các đoạn lồng nhau:

 /**
 * This method will go check all the back stacks of the added fragments and their nested fragments
 * to the the {@code FragmentManager} passed as parameter.
 * If there is a fragment and the back stack of this fragment is not empty,
 * then emulate 'onBackPressed' behaviour, because in default, it is not working.
 *
 * @param fm the fragment manager to which we will try to dispatch the back pressed event.
 * @return {@code true} if the onBackPressed event was consumed by a child fragment, otherwise {@code false}.
 */
public static boolean dispatchOnBackPressedToFragments(FragmentManager fm) {

    List<Fragment> fragments = fm.getFragments();
    boolean result;
    if (fragments != null && !fragments.isEmpty()) {
        for (Fragment frag : fragments) {
            if (frag != null && frag.isAdded() && frag.getChildFragmentManager() != null) {
                // go to the next level of child fragments.
                result = dispatchOnBackPressedToFragments(frag.getChildFragmentManager());
                if (result) return true;
            }
        }
    }

    // if the back stack is not empty then we pop the last transaction.
    if (fm.getBackStackEntryCount() > 0) {
        fm.popBackStack();
        fm.executePendingTransactions();
        return true;
    }

    return false;
}

trong hoạt động của onBackPressedbạn, bạn có thể đơn giản gọi nó theo cách này:

FragmentManager fm = getSupportFragmentManager();
                // if there is a fragment and the back stack of this fragment is not empty,
                // then emulate 'onBackPressed' behaviour, because in default, it is not working
                if (!dispatchOnBackPressedToFragments(fm)) {
                    // if no child fragment consumed the onBackPressed event,
                    // we execute the default behaviour.
                    super.onBackPressed();
                }

1

Cảm ơn mọi người đã giúp đỡ, bản này (phiên bản đã được tinh chỉnh) phù hợp với tôi:

@Override
public void onBackPressed() {
    if (!recursivePopBackStack(getSupportFragmentManager())) {
        super.onBackPressed();
    }
}

/**
 * Recursively look through nested fragments for a backstack entry to pop
 * @return: true if a pop was performed
 */
public static boolean recursivePopBackStack(FragmentManager fragmentManager) {
    if (fragmentManager.getFragments() != null) {
        for (Fragment fragment : fragmentManager.getFragments()) {
            if (fragment != null && fragment.isVisible()) {
                boolean popped = recursivePopBackStack(fragment.getChildFragmentManager());
                if (popped) {
                    return true;
                }
            }
        }
    }

    if (fragmentManager.getBackStackEntryCount() > 0) {
        fragmentManager.popBackStack();
        return true;
    }

    return false;
}

LƯU Ý: Có thể bạn cũng sẽ muốn đặt màu nền của các đoạn lồng nhau này thành màu nền cửa sổ của chủ đề ứng dụng, vì theo mặc định, chúng là trong suốt. Hơi nằm ngoài phạm vi của câu hỏi này, nhưng nó được hoàn thành bằng cách giải quyết thuộc tính android.R.attr.windowBackground và đặt nền của chế độ xem Fragment thành ID tài nguyên đó.


1

Hơn 5 năm và vấn đề này vẫn còn phù hợp. Nếu bạn không muốn sử dụnggmentManager.getFragment () do hạn chế của nó. Mở rộng và sử dụng các lớp dưới đây:

NestedFragmentActivity.java

abstract public class NestedFragmentActivity extends AppCompatActivity {

    private final Stack<Integer> mActiveFragmentIdStack = new Stack<>();
    private final Stack<String> mActiveFragmentTagStack = new Stack<>();

    @Override
    public void onBackPressed() {
        if (mActiveFragmentIdStack.size() > 0 && mActiveFragmentTagStack.size() > 0) {

            // Find by id
            int lastFragmentId = mActiveFragmentIdStack.lastElement();
            NestedFragment nestedFragment = (NestedFragment) getSupportFragmentManager().findFragmentById(lastFragmentId);

            // If cannot find by id, find by tag
            if (nestedFragment == null) {
                String lastFragmentTag = mActiveFragmentTagStack.lastElement();
                nestedFragment = (NestedFragment) getSupportFragmentManager().findFragmentByTag(lastFragmentTag);
            }

            if (nestedFragment != null) {
                nestedFragment.onBackPressed();
            }

            // If cannot find by tag, then simply pop
            mActiveFragmentTagStack.pop();
            mActiveFragmentIdStack.pop();

        } else {
            super.onBackPressed();
        }
    }

    public void addToBackStack(int fragmentId, String fragmentTag) {
        mActiveFragmentIdStack.add(fragmentId);
        mActiveFragmentTagStack.add(fragmentTag);
    }
}

NestedFragment.java

abstract public class NestedFragment extends Fragment {

    private final Stack<Integer> mActiveFragmentIdStack = new Stack<>();
    private final Stack<String> mActiveFragmentTagStack = new Stack<>();

    private NestedFragmentActivity mParentActivity;
    private NestedFragment mParentFragment;

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);

        if (getParentFragment() == null) {
            try {
                mParentActivity = (NestedFragmentActivity) context;
            } catch (ClassCastException e) {
                throw new ClassCastException(context.toString()
                        + " must implement " + NestedFragmentActivity.class.getName());
            }
        } else {
            try {
                mParentFragment = (NestedFragment) getParentFragment();
            } catch (ClassCastException e) {
                throw new ClassCastException(getParentFragment().getClass().toString()
                        + " must implement " + NestedFragment.class.getName());
            }
        }
    }

    public void onBackPressed() {

        if (mActiveFragmentIdStack.size() > 0 && mActiveFragmentTagStack.size() > 0) {

            // Find by id
            int lastFragmentId = mActiveFragmentIdStack.lastElement();
            NestedFragment nestedFragment = (NestedFragment) getChildFragmentManager().findFragmentById(lastFragmentId);

            // If cannot find by id, find by tag
            if (nestedFragment == null) {
                String lastFragmentTag = mActiveFragmentTagStack.lastElement();
                nestedFragment = (NestedFragment) getChildFragmentManager().findFragmentByTag(lastFragmentTag);
            }

            if (nestedFragment != null) {
                nestedFragment.onBackPressed();
            }

            // If cannot find by tag, then simply pop
            mActiveFragmentIdStack.pop();
            mActiveFragmentTagStack.pop();

        } else {
            getChildFragmentManager().popBackStack();
        }
    }

    private void addToBackStack(int fragmentId, String fragmentTag) {
        mActiveFragmentIdStack.add(fragmentId);
        mActiveFragmentTagStack.add(fragmentTag);
    }

    public void addToParentBackStack() {
        if (mParentFragment != null) {
            mParentFragment.addToBackStack(getId(), getTag());
        } else if (mParentActivity != null) {
            mParentActivity.addToBackStack(getId(), getTag());
        }
    }
}

Giải trình:

Mỗi hoạt động và phân đoạn mở rộng từ các lớp trên quản lý ngăn xếp phía sau của riêng chúng cho từng con của họ, và con của trẻ, v.v. Backstack chỉ đơn giản là một bản ghi các thẻ / id "phân đoạn hoạt động". Vì vậy, lưu ý là luôn cung cấp thẻ và / hoặc id cho phân đoạn của bạn.

Khi thêm vào backstack trong childFragmentManager, bạn cũng sẽ cần gọi "addToParentBackStack ()". Điều này đảm bảo rằng thẻ / id của phân đoạn được thêm vào phân đoạn / hoạt động chính cho các cửa sổ bật lên sau này.

Thí dụ:

    getChildFragmentManager().beginTransaction().replace(
            R.id.fragment,
            fragment,
            fragment.getTag()
    ).addToBackStack(null).commit();
    addToParentBackStack();

1

Tôi đã thực hiện nó một cách chính xác nếu bất kỳ ai không tìm thấy bất kỳ câu trả lời nào cho đến bây giờ

chỉ cần thêm phương thức này vào đoạn lồng nhau con của bạn

   @Override
public void onCreate(@Nullable Bundle savedInstanceState) {
    // This callback will only be called when MyFragment is at least Started.
    OnBackPressedCallback callback = new OnBackPressedCallback(true ) {
        @Override
        public void handleOnBackPressed() {
            // Handle the back button event
            FragmentManager fm= getFragmentManager();
            if (fm != null) {
                if (fm.getBackStackEntryCount() > 0) {
                    fm.popBackStack();
                    Log.e( "Frag","back" );

                }

                List<Fragment> fragList = fm.getFragments();
                if (fragList != null && fragList.size() > 0) {
                    for (Fragment frag : fragList) {
                        if (frag == null) {
                            continue;
                        }
                        if (frag.isVisible()) {
                          Log.e( "Frag","Visible" );
                        }
                    }
                }
            }


        }
    };
    requireActivity().getOnBackPressedDispatcher().addCallback(this, callback);
    super.onCreate( savedInstanceState );
}

0

Tôi đã giải quyết vấn đề này bằng cách giữ phân đoạn hiện đang mở trong thuộc tính của hoạt động. Sau đó, tôi ghi đè phương thức

 // INSIDE ACTIVITY
 override fun onBackPressed() {
    val fragment = currentFragment

    if(fragment != null && fragment.childFragmentManager.fragments.any()){
        fragment.childFragmentManager.popBackStack()
    }
    else {
        super.onBackPressed()
    }
}

Đây là cách tôi thêm phân đoạn con vào phân đoạn hiện đang mở từ bên trong chính nó.

 // INSIDE FRAGMENT
 menu_fragment_view.setBackgroundColor(-((Math.random() * 10000 ).toInt() % 30000)) // to see change
 add_fragment_button.setOnClickListener {
        val transaction = childFragmentManager.beginTransaction()

        transaction.add(R.id.menu_fragment_fragment_holder, MenuFragment())

        transaction.addToBackStack(null)

        transaction.commit()
    }

Đây là bố cục xml của phân đoạn mẹ và phân đoạn được thêm vào

 <?xml version="1.0" encoding="utf-8"?>
 <androidx.constraintlayout.widget.ConstraintLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:id="@+id/menu_fragment_view">
     <Button
         app:layout_constraintTop_toTopOf="parent"
         app:layout_constraintLeft_toLeftOf="parent"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:id="@+id/add_fragment_button"
         android:text="Just add fragment"/>
     <FrameLayout
         android:id="@+id/menu_fragment_fragment_holder"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         app:layout_constraintTop_toTopOf="parent"
         app:layout_constraintLeft_toLeftOf="parent"/> 
</androidx.constraintlayout.widget.ConstraintLayout>

0

Sau khi quan sát một số giải pháp được trình bày ở đây, tôi nhận thấy rằng để cho phép tính linh hoạt và kiểm soát đối với phân đoạn mẹ khi bật ngăn xếp hoặc khi hành động quay lại bị nó bỏ qua, tôi nên sử dụng triển khai như vậy:

Xác định giao diện "ParentFragment":

interface ParentFragment {
/**
 * Fragments that host child fragments and want to control their BackStack behaviour when the back button is pressed should implement this
 *
 * @return true if back press was handled, false otherwise
 */
fun onBackPressed(): Boolean

}

Ghi đè "onBackPressed" trong hoạt động chính (hoặc trong BaseActivity):

override fun onBackPressed() {
    val fm: FragmentManager = supportFragmentManager
    for (frag in fm.fragments) {
        if (frag.isVisible && frag is ParentFragment && frag.onBackPressed()) {
            return
        }
    }
    super.onBackPressed()
}

Và sau đó cho phép phân đoạn mẹ xử lý theo ý muốn, ví dụ:

override fun onBackPressed(): Boolean {
    val childFragment = childFragmentManager.findFragmentByTag(SomeChildFragment::class.java.simpleName)
    if (childFragment != null && childFragment.isVisible) {
        // Only for that case, pop the BackStack (perhaps when other child fragments are visible don't)
        childFragmentManager.popBackStack()
        return true
    }
    return false
}

Điều này cho phép tránh nghĩ rằng có một số đoạn con hợp pháp cần loại bỏ khi sử dụng máy nhắn tin dạng xem chẳng hạn (và số mục nhập ngăn xếp trở lại> 0).


-1

Nếu bạn có một DialogFragmentmà đến lượt nó có các đoạn lồng nhau, thì 'cách giải quyết' hơi khác một chút. Thay vì đặt một onKeyListenerthành rootView, bạn sẽ cần phải làm điều đó với Dialog. Ngoài ra, bạn sẽ thiết lập một DialogInterface.OnKeyListenerchứ không phải Viewmột. Tất nhiên, hãy nhớ addToBackStack!

Btw, có 1 mảnh trên ngăn lưng để ủy quyền cuộc gọi trở lại hoạt động là trường hợp sử dụng cá nhân của tôi. Các tình huống điển hình có thể là số đếm là 0.

Đây là những gì bạn phải làm trong onCreateDialog

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        Dialog dialog =  super.onCreateDialog(savedInstanceState);
        dialog.setOnKeyListener(new DialogInterface.OnKeyListener() {
            @Override
            public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
                if(keyCode == KeyEvent.KEYCODE_BACK){
                    FragmentManager cfm = getChildFragmentManager();
                    if(cfm.getBackStackEntryCount()>1){
                        cfm.popBackStack();
                        return true;
                    }   
                }   
                return false;
            }
        });
        return dialog;
    }

Không thực sự hiểu hoặc không đầy đủ. DialogFragment hoặc nó là Superclass Fragment không có setOnKeyListener.
Ixx

@Ixx Bạn phải bật trình nghe Dialog(không phải DialogFragment) và trình nghe bạn sử dụng là DialogInterface.OnKeyListener- mã đang hoạt động và hoàn chỉnh (và đang được sử dụng). Tại sao bạn không cung cấp tình huống của bạn và có lẽ tôi có thể giúp.
Sachin Ahuja

-1

đối với ChildFragment, điều này hoạt động ..

@Override
    public void onBackPressed() {

 if (getSupportFragmentManager().getBackStackEntryCount() > 0) {
            getSupportFragmentManager().popBackStack();
        } else {
            doExit(); //super.onBackPressed();
        }
}
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.