Android 4.2: back stack behaviour with nested fragments

后端 未结 17 1724
爱一瞬间的悲伤
爱一瞬间的悲伤 2020-11-29 16:01

With Android 4.2, the support library got support for nested fragments see here. I\'ve played around with it and found an interesting behaviour / bug regarding back stack an

相关标签:
17条回答
  • 2020-11-29 16:37

    I have implemented it correctly if anybody didnt found any answer till now

    just add this method on your child nested fragment

       @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 讨论(0)
  • 2020-11-29 16:39

    i was able to handle fragment back stack by adding to the parent fragment this method at the onCreate View() method and passing the root view.

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

    The method isEnableFragmentBackStack() is a boolean flag to know when i'm on the main fragment or the next one.

    Make sure that when you commit the fragment that needs to be have stack, then you must add the addToBackstack Method.

    0 讨论(0)
  • 2020-11-29 16:39

    Thanks to everyone for their help, this (tweaked version) works for me:

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

    NOTE: You will probably also want to set the background color of these nested fragments to the app theme's window background color, as by default they are transparent. Somewhat outside of the scope of this question, but it is accomplished by resolving the attribute android.R.attr.windowBackground, and setting the Fragment view's background to that resource ID.

    0 讨论(0)
  • 2020-11-29 16:41

    More than 5 years and this issue is still relevant. If you do not want to use the fragmentManager.getFragments() due to its restriction. Extend and use the below classes:

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

    Explanation:

    Each activity and fragment extended from the above classes manages their own back stack for each of their children, and children's children, and so on. The backstack is simply a record of "active fragment" tags/ids. So the caveat is to always provide a tag and/or id for your fragment.

    When adding to the backstack in a childFragmentManager, you will need to also call "addToParentBackStack()". This ensures that the fragment's tag/id is added in the parent fragment/activity for later pops.

    Example:

        getChildFragmentManager().beginTransaction().replace(
                R.id.fragment,
                fragment,
                fragment.getTag()
        ).addToBackStack(null).commit();
        addToParentBackStack();
    
    0 讨论(0)
  • 2020-11-29 16:42

    Seems like a bug. Take a look at: http://code.google.com/p/android/issues/detail?id=40323

    For a workaround I've used successfully (as suggested in comments):

        @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();          
        }
    }
    
    0 讨论(0)
  • 2020-11-29 16:43

    if you are using fragment in a fragment then use getChildFragmentManager()

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

    if you are using child fragment and replace it use getParentFragmentManager()

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

    if both are not working for you try this getActivity().getSupportFragmentManager()

    getActivity().getSupportFragmentManager().beginTransaction().replace(R.id.fragment_name, fragment).addToBackStack(null).commit();
    
    0 讨论(0)
提交回复
热议问题