按下后退按钮时显示的片段,即使未添加到后退堆栈

Fragment showing on back button press, even though not added to backstack

我有一个应用程序,其中 MainActiviy 使用底部导航在全屏片段之间切换。

我学会了通过确保在创建时将每个片段添加到后台堆栈来控制后退按钮导航。

fragmentManager.beginTransaction().add(R.id.contentContainer, fragment, fragment_tag).addToBackStack(fragment_tag).commit();

有一种类型的片段,即加载屏幕片段,我不想将其添加到后台堆栈,因此我在创建片段时排除了 addToBackStack() 方法。

如下面的 gif 所示不知何故加载片段在按下后退按钮时仍然出现,即使它不在后台堆栈上(我已经用调试器确认了这一点)。

如果有人能帮助我弄清楚它为什么会出现,我将非常感激,它已经困扰了我大约一个星期,我没有主意了!

代码如下:

package *package name*;

import *all import statements*

public class MainActivity extends AppCompatActivity implements LoaderManager.LoaderCallbacks<ArrayList> {
    BottomNavigation mBottomBar;
    private FloatingActionButton fab;
    private FirebaseDatabase database;
    private DatabaseReference DB_Storage_Ref, DB_Master_Ref;
    FragmentManager fragmentManager;
    CustomBottomBarSelectionListener bbListener;
    CustomBackStackChangeListener cBSCL;

    ArrayList<IngredientCard> master = new ArrayList<>();
    ArrayList<IngredientCard> all = new ArrayList<>();
    ArrayList<IngredientCard> fridge = new ArrayList<>();
    ArrayList<IngredientCard> freezer = new ArrayList<>();
    ArrayList<IngredientCard> pantry = new ArrayList<>();
    ArrayList<IngredientCard> ingredient_imports = new ArrayList<>();
    int arraysLoaded = 0;
    boolean loadingComplete = false;

    ArrayList<String> storageLocationList = new ArrayList<>();
    Map<String, ArrayList<IngredientCard>> storageLocationMapLists = new HashMap<>();

    final String[] tag = {null};
    boolean backButtonPressed = false;


    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

//      Establish FirebaseDatabase Instance and required DB References
        database = FirebaseDatabase.getInstance();
        DB_Storage_Ref = database.getReference("Storage");
        DB_Master_Ref = database.getReference("Master");

//      These Storage location must match branch titles in Firebase JSON database
//      Create a list of all Storage Room Titles (matching realtime database branch names)
        storageLocationList.add("All");
        storageLocationList.add("Fridge");
        storageLocationList.add("Freezer");
        storageLocationList.add("Pantry");

//      Create a hashmap mapping all storage room arrays to the associated storage room titles.
        storageLocationMapLists.put("All", all);
        storageLocationMapLists.put("Fridge", fridge);
        storageLocationMapLists.put("Freezer", freezer);
        storageLocationMapLists.put("Pantry", pantry);

//      Associate UI to Variables
        Toolbar myToolbar = (Toolbar) findViewById(R.id.my_toolbar);
        fab = (FloatingActionButton) findViewById(R.id.fab);
        mBottomBar = (BottomNavigation) findViewById(R.id.BottomNavigation);

        fragmentManager = getSupportFragmentManager();

        bbListener = new CustomBottomBarSelectionListener(this);
        mBottomBar.setOnMenuItemClickListener(bbListener);

        cBSCL = new CustomBackStackChangeListener(this);
        fragmentManager.addOnBackStackChangedListener(cBSCL);

//      Load arrays with data from Firebase Database.
        populateArrays();

//      Customise UI config where necessary
        setSupportActionBar(myToolbar);

        mBottomBar.setDefaultSelectedIndex(2);
        tag[0] = PLAN_FRAGMENT_TAG;
        fragmentManager.beginTransaction().add(R.id.contentContainer, new PlanFragment(), tag[0]).commit();

        //      Set onClick Listener for FAB button. The FAB should change/animate as user switches between BottomBar options
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Fragment fragment;

//              Find the IngredientsFragment in the Fragment Manager
                fragment = fragmentManager.findFragmentByTag(INGREDIENT_FRAGMENT_TAG);

//              If the Fragment exists and is visible then carryout action
                if (fragment != null && fragment.isVisible()) {
                    Intent SelectIngredient = new Intent(getBaseContext(), Ingred_MasterList.class);
                    Bundle args = new Bundle();
                    args.putParcelableArrayList(ARG_INGREDIENTS_LIST, master);
                    args.putStringArrayList(ARG_STORAGE_LOCATIONS, storageLocationList);
                    SelectIngredient.putExtras(args);
                    startActivity(SelectIngredient,args);
                }
            }
        });

    }

    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        setIntent(intent);

        if(getIntent().getExtras() != null) {
            Bundle args = getIntent().getExtras();

            if (args.containsKey(INGREDIENT_IMPORTS)) {
                ingredient_imports = (args.getParcelableArrayList(INGREDIENT_IMPORTS));
                bbListener.switchFragment(LOADING_FRAGMENT_TAG, new LoadingFragment());
                fragmentManager.popBackStackImmediate();
                distributeItems(ingredient_imports);
            }
        }
    }

    private void distributeItems(ArrayList<IngredientCard> array) {
        for(IngredientCard ingredient : array){
            DB_Storage_Ref.child("All").child(ingredient.getItemName()).setValue(ingredient);
            DB_Storage_Ref.child(ingredient.getStorageLocation()).child(ingredient.getItemName()).setValue(ingredient);
        }
        ingredient_imports.clear();
    }

    private void populateArrays() {

//      Cycle through storageLocationList array and add the storage location title (which must match a branch name on the Firebase Database.
        for (int i = 0; i < storageLocationList.size(); i++) {
            Bundle args = new Bundle();
            args.putString(TEMP_BUNDLE_STORAGE_TITLE, storageLocationList.get(i));

//          For each storage location create a loader to retrieve its data from the Firebase Database
            getSupportLoaderManager().initLoader(i, args, this);
        }
//      Create a loader that retrieves the master list of food icons
        getSupportLoaderManager().initLoader(MASTER_LIST_ARRAY_ID, null, this);
    }

    @Override
    public Loader<ArrayList> onCreateLoader(int id, Bundle args) {
        String DBbranch;

        if (args == null) {
            //If bundle args don't exist assume we want data from 'Master' branch of DB
            DBbranch = "Food_Items";
            return new IngredientsListLoader(this, DB_Master_Ref, DBbranch, this);
        } else {
            //If bundle args exist, extract them and add them as IngredientListLoader variable
            DBbranch = args.getString(TEMP_BUNDLE_STORAGE_TITLE);
            return new IngredientsListLoader(this, DB_Storage_Ref, DBbranch, this);
        }
    }

    @Override
//  Should be called after loadInBackground has completed but seems to return earlier. The method returnResults has been created in IngredientsListLoader to deal with this.
    public void onLoadFinished(Loader<ArrayList> loader, ArrayList data) {

        if (loader.getId() == MASTER_LIST_ARRAY_ID) {
//          if MASTER_LIST Loader set master ArrayList to data
            master = data;
        } else {
//          cycle through each item in storageLocationList Array (the Array position -eq loader id) and replace Array in storageLocationList position with data Array
            for (int i = 0; i < storageLocationList.size(); i++) {
                if (loader.getId() == i) {
                    storageLocationMapLists.put(storageLocationList.get(i), data);
                }
            }
        }
    }

    @Override
    public void onLoaderReset(Loader<ArrayList> loader) {
    }

    @Override
    public void onBackPressed() {
        backButtonPressed = true;
        if (fragmentManager.getBackStackEntryCount() > 0) {
            Log.i("MainActivity", "popping fragment backstack");
            fragmentManager.popBackStack();
        } else {
            Log.i("MainActivity", "nothing on backstack, calling super");
            super.onBackPressed();
        }

    }

    void bottomBarUpdate(){
        Fragment currentBackStackFragment = getBackstackFragment();

        if(currentBackStackFragment instanceof Ingredients_BottomBarFrag || currentBackStackFragment instanceof LoadingFragment){
            mBottomBar.setSelectedIndex(0,true);
            return;
        }
        if(currentBackStackFragment instanceof MealsFragment){
            mBottomBar.setSelectedIndex(1,true);
            return;
        }
        if(currentBackStackFragment instanceof PlanFragment){
            mBottomBar.setSelectedIndex(2,true);
            return;
        }
        if(currentBackStackFragment instanceof ShoppingFragment){
            mBottomBar.setSelectedIndex(3,true);
            return;
        }
        if(currentBackStackFragment instanceof SettingsFragment){
            mBottomBar.setSelectedIndex(4,true);
            return;
        }
    }

    private Fragment getBackstackFragment(){
        String fragmentTag;

        if(fragmentManager.getBackStackEntryCount() > 0) {
            fragmentTag = fragmentManager.getBackStackEntryAt(fragmentManager.getBackStackEntryCount() - 1).getName();

        }else{
            fragmentTag = PLAN_FRAGMENT_TAG;
            fragmentManager.beginTransaction().add(R.id.contentContainer, new PlanFragment(), tag[0]).commit();
        }
        return fragmentManager.findFragmentByTag(fragmentTag);

    }
}

class IngredientsListLoader extends AsyncTaskLoader {
    private DatabaseReference DBRef;
    private String DBBranch;
    private ArrayList<IngredientCard> food_Items_List = new ArrayList<>();
    private MainActivity ma;

    IngredientsListLoader(Context context, DatabaseReference instance, String DBBranch, MainActivity main) {
        super(context);
        DBRef = instance;
        this.DBBranch = DBBranch;
        ma = main;
        forceLoad();
    }

    @Override
    public ArrayList<IngredientCard> loadInBackground() {
        food_Items_List.clear();
        DBRef = DBRef.child(DBBranch);

        CustomListener cl = new CustomListener(ma);
        DBRef.addValueEventListener(cl);

        Log.v("TAG", "Returning LIST of size " + food_Items_List.size());

        return cl.returnResults();
    }
}

class CustomListener implements ValueEventListener {
    private ArrayList<IngredientCard> food_Items_List = new ArrayList<>();
    private MainActivity ma;


    CustomListener(MainActivity main){
        ma = main;
    }

    @Override
    public void onDataChange(DataSnapshot dataSnapshot) {
        Iterable<DataSnapshot> children = dataSnapshot.getChildren();
        food_Items_List.clear();

        for (DataSnapshot child : children) {
            IngredientCard ingredientCard = child.getValue(IngredientCard.class);
            food_Items_List.add(ingredientCard);
            Log.v("ValueEventLisenter", "Accessing Firebase!");
        }
        returnResults();
        removeLoadingScreen();
    }


    @Override
    public void onCancelled(DatabaseError databaseError) {

    }

    ArrayList<IngredientCard> returnResults() {
        return food_Items_List;
    }

    void removeLoadingScreen(){
        //If all arrays have been loaded and the ingredient_import array has been cleared...
        if(ma.arraysLoaded == ma.storageLocationList.size() && ma.ingredient_imports.size() == 0) {
            ma.loadingComplete = true;

            // tag[0] represents the tag of the currently displayed fragment. It changes to the first parameter of the switchFragment method each time it is called.
            //If the displayed fragment is the LOADING_FRAGMENT switch it out for the INGREDIENT_FRAGMENT
            if (ma.tag[0] == LOADING_FRAGMENT_TAG) {
                ma.bbListener.switchFragment(INGREDIENT_FRAGMENT_TAG, new Ingredients_BottomBarFrag());
            }
        }else{
            //For each loader that completes and calls this method, the values of arraysLoaded increases until it matches the number of loaders expected to return.
            ma.arraysLoaded++;
        }
    }


}

class CustomBottomBarSelectionListener implements OnMenuItemSelectionListener {
    private MainActivity ma;

    CustomBottomBarSelectionListener(MainActivity main){
        ma = main;
    }

    @Override
    public void onMenuItemSelect(@IdRes int tabId, int position, boolean fromUser) {

        //if this is triggered via pressing the back button, then simply return as fragmentManager.popBackStack() will handle switching fragments.
        if(ma.backButtonPressed){
            ma.backButtonPressed = false;
            return;
        }

        switch (tabId) {
            case R.id.menu_ingredients:
                //if items have not completed loading show loading screen
                if(!ma.loadingComplete && ma.ingredient_imports.size() == 0){
                    switchFragment(LOADING_FRAGMENT_TAG, new LoadingFragment());
                }else{
                    switchFragment(INGREDIENT_FRAGMENT_TAG, new Ingredients_BottomBarFrag());
                }
                break;
            //TODO: Have RecyclerView scroll position restored when fragment comes back into view
            case R.id.menu_meals:
                switchFragment(MEAL_FRAGMENT_TAG, new MealsFragment());
                break;

            case R.id.menu_plan:
                switchFragment(PLAN_FRAGMENT_TAG, new PlanFragment());
                break;

            case R.id.menu_groceries:
                switchFragment(SHOPPING_FRAGMENT_TAG, new ShoppingFragment());
                break;

            case R.id.menu_settings:
                switchFragment(SETTINGS_FRAGMENT_TAG, new SettingsFragment());
                break;
        }
    }

    @Override
    public void onMenuItemReselect(@IdRes int i, int i1, boolean b) {
        //TODO Add reselect code
    }

    protected void switchFragment(String fragTag, Fragment frag) {

//      Sets a reference of current fragments Tag
        ma.tag[0] = fragTag;

        if(ma.tag[0]== LOADING_FRAGMENT_TAG){
            //load LOADING_FRAGMENT but DONT add to backstack
            ma.fragmentManager.beginTransaction().add(R.id.contentContainer, frag, ma.tag[0]).commit();
        }else {
            //Add every other fragment to backstack
            ma.fragmentManager.beginTransaction().add(R.id.contentContainer, frag, ma.tag[0]).addToBackStack(ma.tag[0]).commit();
        }
    }
};

class CustomBackStackChangeListener implements FragmentManager.OnBackStackChangedListener{
    private MainActivity ma;


    CustomBackStackChangeListener(MainActivity main){
        ma = main;
    }

    @Override
    public void onBackStackChanged() {

        //If BackStackChanged is triggered due to anything other than pressing the back button, return.
        if(!ma.backButtonPressed){
            return;
        }

        ma.bottomBarUpdate();
    }
}

改进代码演示 (抱歉,在评论中添加代码很糟糕,所以我会在这里做)

    protected void switchFragment(String fragTag, Fragment frag) {

//      Sets a reference of current fragments Tag
    ma.tag[0] = fragTag;

    if(ma.tag[0]== LOADING_FRAGMENT_TAG){
        //load LOADING_FRAGMENT but DONT add to backstack
        ma.fragmentManager.beginTransaction().add(R.id.contentContainer, frag, ma.tag[0]).commit();
    }else {
        Fragment fragment = ma.getSupportFragmentManager().findFragmentByTag(LOADING_FRAGMENT_TAG);
        if(fragment != null && fragment.isVisible()){
            ma.fragmentManager.beginTransaction().remove(fragment);
        }
        //Add every other fragment to backstack
        ma.fragmentManager.beginTransaction().add(R.id.contentContainer, frag, ma.tag[0]).addToBackStack(ma.tag[0]).commit();
    }
}

每当您从那个片段切换到另一个片段时,如果您不希望太包含在后台堆栈中,您可以在切换之前先完成该片段。 这可以通过声明片段的陈述对象并为该对象提供其实例来完成。 然后在片段名称的帮助下切换片段检查它的静态对象是否为空。 如果它不为空,则完成片段