即使没有添加到后台按钮

时间:2017-06-14 08:05:19

标签: android android-fragments fragment-backstack

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

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

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

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

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

Demo of loading fragment appearing even though not on backstack

如果有人能帮我弄清楚它为什么会出现我真的很感激,它已经困扰了我一个星期而且我没有想法!

以下是代码:

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

1 个答案:

答案 0 :(得分:0)

每当你切换到你不想要的那个片段中的另一个片段时,你也可以在切换之前完成该片段。 这可以通过声明片段的说明对象并将该对象作为其实例来完成。 然后,如果它的静态对象为null,则在片段名称的帮助下切换片段检查。 如果它不为null则完成片段