How to implement onBackPressed() in Fragments?

2018-12-31 03:15发布

Is there a way in which we can implement onBackPressed() in Android Fragment similar to the way in which we implement in Android Activity?

As the Fragment lifecycle do not have onBackPressed(). Is there any other alternative method to over ride onBackPressed() in Android 3.0 fragments?

30条回答
有味是清欢
2楼-- · 2018-12-31 03:59

since this question and some of the answers are over five years old, let me share my solution. This is a follow-up and modernization to the answer from @oyenigun

UPDATE: At the bottom of this article, I added an alternative implementation using an abstract Fragment extension that won't involve the Activity at all, which would be useful for anyone with a more complex fragment hierarchy involving nested fragments that require different back behavior.

I needed to implement this because some of the fragments I use have smaller views that I would like to dismiss with the back button, such as small information views that pop up, etc, but this is good for anyone who needs to override the behavior of the back button inside fragments.

First, define an Interface

public interface Backable {
    boolean onBackPressed();
}

This interface, which I call Backable (I'm a stickler for naming conventions), has a single method onBackPressed() that must return a boolean value. We need to enforce a boolean value because we will need to know if the back button press has "absorbed" the back event. Returning true means that it has, and no further action is needed, otherwise, false says that the default back action still must take place. This interface should be it's own file (preferably in a separate package named interfaces). Remember, separating your classes into packages is good practice.

Second, find the top fragment

I created a method that returns the last Fragment object in the back stack. I use tags... if you use ID's, make the necessary changes. I have this static method in a utility class that deals with navigation states, etc... but of course, put it where it best suits you. For edification, I've put mine in a class called NavUtils.

public static Fragment getCurrentFragment(Activity activity) {
    FragmentManager fragmentManager = activity.getFragmentManager();
    if (fragmentManager.getBackStackEntryCount() > 0) {
        String lastFragmentName = fragmentManager.getBackStackEntryAt(
                fragmentManager.getBackStackEntryCount() - 1).getName();
        return fragmentManager.findFragmentByTag(lastFragmentName);
    }
    return null;
}

Make sure the back stack count is greater than 0, otherwise an ArrayOutOfBoundsException could be thrown at runtime. If it isn't greater than 0, return null. We'll check for a null value later...

Third, Implement in a Fragment

Implement the Backable interface in whichever fragment where you need to override the back button behavior. Add the implementation method.

public class SomeFragment extends Fragment implements 
        FragmentManager.OnBackStackChangedListener, Backable {

...

    @Override
    public boolean onBackPressed() {

        // Logic here...
        if (backButtonShouldNotGoBack) {
            whateverMethodYouNeed();
            return true;
        }
        return false;
    }

}

In the onBackPressed() override, put whatever logic you need. If you want the back button to not pop the back stack (the default behavior), return true, that your back event has been absorbed. Otherwise, return false.

Lastly, in your Activity...

Override the onBackPressed() method and add this logic to it:

@Override
public void onBackPressed() {

    // Get the current fragment using the method from the second step above...
    Fragment currentFragment = NavUtils.getCurrentFragment(this);

    // Determine whether or not this fragment implements Backable
    // Do a null check just to be safe
    if (currentFragment != null && currentFragment instanceof Backable) {

        if (((Backable) currentFragment).onBackPressed()) {
            // If the onBackPressed override in your fragment 
            // did absorb the back event (returned true), return
            return;
        } else {
            // Otherwise, call the super method for the default behavior
            super.onBackPressed();
        }
    }

    // Any other logic needed...
    // call super method to be sure the back button does its thing...
    super.onBackPressed();
}

We get the current fragment in the back stack, then we do a null check and determine if it implements our Backable interface. If it does, determine if the event was absorbed. If so, we're done with onBackPressed() and can return. Otherwise, treat it as a normal back press and call the super method.

Second Option to not involve the Activity

At times, you don't want the Activity to handle this at all, and you need to handle it directly within the fragment. But who says you can't have Fragments with a back press API? Just extend your fragment to a new class.

Create an abstract class that extends Fragment and implements the View.OnKeyListner interface...

import android.app.Fragment;
import android.os.Bundle;
import android.view.KeyEvent;
import android.view.View;

public abstract class BackableFragment extends Fragment implements View.OnKeyListener {

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        view.setFocusableInTouchMode(true);
        view.requestFocus();
        view.setOnKeyListener(this);
    }

    @Override
    public boolean onKey(View v, int keyCode, KeyEvent event) {
        if (event.getAction() == KeyEvent.ACTION_UP) {
            if (keyCode == KeyEvent.KEYCODE_BACK) {
                onBackButtonPressed();
                return true;
            }
        }

        return false;
    }

    public abstract void onBackButtonPressed();
}

As you can see, any fragment that extends BackableFragment will automatically capture back clicks using the View.OnKeyListener interface. Just call the abstract onBackButtonPressed() method from within the implemented onKey() method using the standard logic to discern a back button press. If you need to register key clicks other than the back button, just be sure to call the super method when overriding onKey() in your fragment, otherwise you'll override the behavior in the abstraction.

Simple to use, just extend and implement:

public class FragmentChannels extends BackableFragment {

    ...

    @Override
    public void onBackButtonPressed() {
        if (doTheThingRequiringBackButtonOverride) {
            // do the thing
        } else {
            getActivity().onBackPressed();
        }
    }

    ...
}

Since the onBackButtonPressed() method in the super class is abstract, once you extend you must implement onBackButtonPressed(). It returns void because it just needs to perform an action within the fragment class, and does not need to relay the absorption of the press back to the Activity. Make sure you do call the Activity onBackPressed() method if whatever you're doing with the back button doesn't require handling, otherwise, the back button will be disabled... and you don't want that!

Caveats As you can see, this sets the key listener to the root view of the fragment, and we'll need to focus it. If there are edit texts involved (or any other focus-stealing views) in your fragment that extends this class, (or other inner fragments or views that have the same), you'll need to handle that separately. There's a good article on extending an EditText to lose focus on a back press.

I hope someone finds this useful. Happy coding.

查看更多
倾城一夜雪
3楼-- · 2018-12-31 04:00

I know it's too late but I had the same problem last week. None of the answers helped me. I then was playing around with the code and this worked, since I already added the fragments.

In your Activity, set an OnPageChangeListener for the ViewPager so that you will know when the user is in the second activity. If he is in the second activity, make a boolean true as follows:

    mSectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager());
    // Set up the ViewPager with the sections adapter.
    mViewPager = (ViewPager) findViewById(R.id.pager);
    mViewPager.setAdapter(mSectionsPagerAdapter);
    mViewPager.setCurrentItem(0);
    mViewPager.addOnPageChangeListener(new OnPageChangeListener() {

        @Override
        public void onPageSelected(int position) {
            // TODO Auto-generated method stub
                mSectionsPagerAdapter.instantiateItem(mViewPager, position);
                if(position == 1)
                    inAnalytics = true;
                else if(position == 0)
                    inAnalytics = false;
        }

        @Override
        public void onPageScrolled(int position, float arg1, int arg2) {
            // TODO Auto-generated method stub
        }

        @Override
        public void onPageScrollStateChanged(int arg0) {
            // TODO Auto-generated method stub

        }
    });

Now check for the boolean whenever back button is pressed and set the current item to your first Fragment:

@Override
public void onBackPressed() {
    if(inAnalytics)
        mViewPager.setCurrentItem(0, true);
    else 
        super.onBackPressed();
}
查看更多
柔情千种
4楼-- · 2018-12-31 04:02

If you use EventBus, it is probably a far more simpler solution :

In your Fragment :

@Override
public void onAttach(Activity activity) {
    super.onAttach(activity);
    EventBus.getDefault().register(this);
}

@Override
public void onDetach() {
    super.onDetach();
    EventBus.getDefault().unregister(this);
}


// This method will be called when a MessageEvent is posted
public void onEvent(BackPressedMessage type){
    getSupportFragmentManager().popBackStack();
}

and in your Activity class you can define :

@Override
public void onStart() {
    super.onStart();
    EventBus.getDefault().register(this);
}

@Override
public void onStop() {
    EventBus.getDefault().unregister(this);
    super.onStop();
}

// This method will be called when a MessageEvent is posted
public void onEvent(BackPressedMessage type){
    super.onBackPressed();
}

@Override
public void onBackPressed() {
    EventBus.getDefault().post(new BackPressedMessage(true));
}

BackPressedMessage.java is just a POJO object

This is super clean and there is no interface/implementation hassle.

查看更多
与风俱净
5楼-- · 2018-12-31 04:02

on mainActivity

protected DrawerHomeActivity.OnBackPressedListener onBackPressedListener;

public interface OnBackPressedListener {
    void doBack();
}

public void setOnBackPressedListener(DrawerHomeActivity.OnBackPressedListener onBackPressedListener) {
    this.onBackPressedListener = onBackPressedListener;
}

@Override
public void onBackPressed() {
    if (onBackPressedListener != null) {
        onBackPressedListener.doBack();


    }
    else
        super.onBackPressed();



}

on fragment implement base activity/fragment like

implements DrawerHomeActivity.OnBackPressedListener

DraweHomeActivity is my base activity write

((DrawerHomeActivity) getActivity()).setOnBackPressedListener(this);

and create method doBack

@Override
public void doBack() {
    //call base fragment 
    }
查看更多
流年柔荑漫光年
6楼-- · 2018-12-31 04:04

Ok guys I finally found out a good solution.

In your onCreate() in your activity housing your fragments add a backstack change listener like so:

    fragmentManager.addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {
        @Override
        public void onBackStackChanged() {
            List<Fragment> f = fragmentManager.getFragments();
            //List<Fragment> f only returns one value
            Fragment frag = f.get(0);
            currentFragment = frag.getClass().getSimpleName();
        }
    });

(Also adding my fragmenManager is declared in the activities O Now every time you change fragment the current fragment String will become the name of the current fragment. Then in the activities onBackPressed() you can control the actions of your back button as so:

    @Override
    public void onBackPressed() {

    switch (currentFragment) {
        case "FragmentOne":
            // your code here
            return;
        case "FragmentTwo":
            // your code here
            return;
        default:
            fragmentManager.popBackStack();
            // default action for any other fragment (return to previous)
    }

}

I can confirm that this method works for me.

查看更多
刘海飞了
7楼-- · 2018-12-31 04:06

I solved in this way override onBackPressed in the Activity. All the FragmentTransaction are addToBackStack before commit:

@Override
public void onBackPressed() {

    int count = getFragmentManager().getBackStackEntryCount();

    if (count == 0) {
        super.onBackPressed();
        //additional code
    } else {
        getFragmentManager().popBackStack();
    }

}
查看更多
登录 后发表回答