web-dev-qa-db-fra.com

BottomNavigationView - Comment éviter de reconstituer des fragments et les réutiliser

Je voudrais faire une barre de navigation en bas de mon projet. Chaque vue a son propre fragment. Le problème est que chaque fois que je clique sur le bouton pour changer la vue, par exemple de récentes à des favoris, un nouveau fragment est créé avec de nouveaux états (par exemple, la position de défilement, le texte modifié quel que soit le contenu de mon fragment). Je sais que dans la documentation officielle d'Android, il était écrit que la barre de navigation inférieure devait réinitialiser les états de tâche, mais je pense que cela est trop inconfortable pour les utilisateurs ... Je voudrais avoir une sorte de fonctionnalité similaire, telle que instagram, que vous passez de flux à explorer et reculer pour alimenter la position de défilement de l'image dans la mémoire cache, tout est sauvegardé. J'ai essayé presque tous les moyens pour résoudre ce problème, la seule chose qui a fonctionné est de définir la visibilité GONE et VISIBLE en fonction de la situation, mais je comprends que ce n'est pas la bonne façon, il devrait y avoir une meilleure façon de le faire et je ne parle pas manuellement sauvegarder les instances nécessaires. J'ai suivi presque tous les tutoriels sur les fragments de navigation du bas, mais ce qui est intéressant, c'est que personne n'est intéressé à le faire utiliser sans en appeler de nouveau à chaque fois.

 enter image description here

 enter image description here

FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.replace(R.id.frameLayout, FirstFragment.newInstance());
fragmentTransaction.commit();

bottomNavigationView = (BottomNavigationView) findViewById(R.id.navigation);
bottomNavigationView.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
    @Override
    public boolean onNavigationItemSelected(@NonNull MenuItem item) {
        Fragment fragment = null;
        switch (item.getItemId()) {
            case R.id.menu_dialer:
                fragment = FirstFragment.newInstance();
                break;
            case R.id.menu_email:
                fragment = SecondFragment.newInstance();
                break;
            case R.id.menu_map:
                fragment = ThirdFragment.newInstance();
                break;
        }
        if (fragment != null) {
            FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
            fragmentTransaction.replace(R.id.frameLayout, fragment);
            fragmentTransaction.commit();
        }
        return true;
    }
});

J'ai aussi essayé les solutions onAttach et Deattach, mais là encore sans succès.

VIDEO LINK: nouvelle version de Nino Handler que j'ai essayée, elle ne fonctionne que si je tape sur le même bouton de fragment

Peut-être que c'est lié au fait que j'utilise la version Canary ou quelque chose qui ne va pas dans mes dépendances? enter image description here

 NEW UPDATES:

NOUVELLES MISES À JOUR:

public class MainActivity extends AppCompatActivity {

    private TextView mTextMessage;


    private static final String TAG_FRAGMENT_ONE = "fragment_one";
    private static final String TAG_FRAGMENT_TWO = "fragment_two";

    private FragmentManager fragmentManager;
    private Fragment currentFragment;

    String TAG = "babken";
    private BottomNavigationView.OnNavigationItemSelectedListener mOnNavigationItemSelectedListener
            = new BottomNavigationView.OnNavigationItemSelectedListener() {

        Fragment fragment = null;
        @Override
        public boolean onNavigationItemSelected(@NonNull MenuItem item) {
            switch (item.getItemId()) {
                case R.id.navigation_home:
                   fragment = fragmentManager.findFragmentByTag(TAG_FRAGMENT_ONE);
                    if (fragment == null) {
                        fragment = FragmentFirst.newInstance();
                    }
                    replaceFragment(fragment, TAG_FRAGMENT_ONE);

                    break;
                case R.id.navigation_dashboard:

                     fragment = fragmentManager.findFragmentByTag(TAG_FRAGMENT_TWO);
                    if (fragment == null) {
                        fragment = FragmentSecond.newInstance();
                    }
                    replaceFragment(fragment, TAG_FRAGMENT_TWO);

                    break;
            }
            return true;

        }
    };

    private void replaceFragment(@NonNull Fragment fragment, @NonNull String tag) {
        if (!fragment.equals(currentFragment)) {
            fragmentManager
                    .beginTransaction()
                    .replace(R.id.armen, fragment, tag)
                    .commit();
            currentFragment = fragment;
        }
    }
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        fragmentManager = getSupportFragmentManager();
        Fragment fragment = fragmentManager.findFragmentByTag(TAG_FRAGMENT_ONE);
        if (fragment == null) {
            fragment = FragmentFirst.newInstance();
        }
        replaceFragment(fragment, TAG_FRAGMENT_ONE);
        BottomNavigationView navigation = (BottomNavigationView) findViewById(R.id.navigation);
        navigation.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener);
    }

}
10
Armen Hovhannisian

J'ai donc recherché cela depuis longtemps et j'ai compris qu'il n'y avait aucun moyen de réutiliser des fragments avec des états sauvegardés AUTOMATIQUEMENT. Vous devez sauvegarder vos états nécessaires manuellement, puis les récupérer à chaque fois qu'un nouveau fragment est créé. trop difficile et même dans certains cas, il est impossible de sauvegarder l’état de la position de la vue de défilement (par exemple, la vue du recycleur). J'ai donc utilisé le concept appelé VISIBILITÉ lorsque je clique sur le bouton pour que le fragment devienne visible, les autres se masquant automatiquement.

1
Armen Hovhannisian

J'ai eu le même problème, mais ce code a résolu mon problème.

public class MainActivity extends AppCompatActivity {

final Fragment fragment1 = new HomeFragment();
final Fragment fragment2 = new DashboardFragment();
final Fragment fragment3 = new NotificationsFragment();
final FragmentManager fm = getSupportFragmentManager();
Fragment active = fragment1;

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

    Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
    setSupportActionBar(toolbar);


    BottomNavigationView navigation = (BottomNavigationView) findViewById(R.id.navigation);
    navigation.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener);

    fm.beginTransaction().add(R.id.main_container, fragment3, "3").hide(fragment3).commit();
    fm.beginTransaction().add(R.id.main_container, fragment2, "2").hide(fragment2).commit();
    fm.beginTransaction().add(R.id.main_container,fragment1, "1").commit();

}


private BottomNavigationView.OnNavigationItemSelectedListener mOnNavigationItemSelectedListener
        = new BottomNavigationView.OnNavigationItemSelectedListener() {

    @Override
    public boolean onNavigationItemSelected(@NonNull MenuItem item) {
        switch (item.getItemId()) {
            case R.id.navigation_home:
                fm.beginTransaction().hide(active).show(fragment1).commit();
                active = fragment1;
                return true;

            case R.id.navigation_dashboard:
                fm.beginTransaction().hide(active).show(fragment2).commit();
                active = fragment2;
                return true;

            case R.id.navigation_notifications:
                fm.beginTransaction().hide(active).show(fragment3).commit();
                active = fragment3;
                return true;
        }
        return false;
    }
};

J'espère que cela t'aides.

source: BottomNavigationView With Fragments (Aucune reproduction de fragment).

8
Bukunmi

Je ne garderais pas les occurrences de fragments globalement . À la place, ajoutez une balise au fragment lors de leur création.

getSupportFragmentManager()
            .beginTransaction()
            .replace(R.id.container, new PlaceholderFragment(), TAG_PLACEHOLDER)
            .commit();

Ensuite, vous pouvez toujours le récupérer comme ceci:

Fragment fragment = getSupportFragmentManager().findFragmentByTag(TAG_PLACEHOLDER);
    if (fragment == null) {
        fragment = new PlaceholderFragment();
    }
    getSupportFragmentManager()
            .beginTransaction()
            .replace(R.id.container, fragment, TAG_PLACEHOLDER)
            .commit();

MISE À JOUR: J'ai mis à jour ma réponse et à fournir une solution complète:

private static final String TAG_FRAGMENT_ONE = "fragment_one";
private static final String TAG_FRAGMENT_TWO = "fragment_two";
private static final String TAG_FRAGMENT_THREE = "fragment_three";

private FragmentManager fragmentManager;
private Fragment currentFragment;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // instantiate the fragment manager
    fragmentManager = getSupportFragmentManager();

    Fragment fragment = fragmentManager.findFragmentByTag(TAG_FRAGMENT_ONE);
    if (fragment == null) {
        fragment = FirstFragment.newInstance();
    }
    replaceFragment(fragment, TAG_FRAGMENT_ONE);

    bottomNavigationView = (BottomNavigationView) findViewById(R.id.navigation);
    bottomNavigationView.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
        @Override
        public boolean onNavigationItemSelected(@NonNull MenuItem item) {
            Fragment fragment = null;
            switch (item.getItemId()) {
                case R.id.menu_dialer:
                    // I'm aware that this code can be optimized by a method which accepts a class definition and returns the proper fragment
                    Fragment fragment = fragmentManager.findFragmentByTag(TAG_FRAGMENT_ONE);
                    if (fragment == null) {
                        fragment = FirstFragment.newInstance();
                    }
                    replaceFragment(fragment, TAG_FRAGMENT_ONE);
                    break;
                case R.id.menu_email:
                    Fragment fragment = fragmentManager.findFragmentByTag(TAG_FRAGMENT_TWO);
                    if (fragment == null) {
                        fragment = SecondFragment.newInstance();
                    }
                    replaceFragment(fragment, TAG_FRAGMENT_TWO);
                    break;
                case R.id.menu_map:
                    Fragment fragment = fragmentManager.findFragmentByTag(TAG_FRAGMENT_THREE);
                    if (fragment == null) {
                        fragment = ThirdFragment.newInstance();
                    }
                    replaceFragment(fragment, TAG_FRAGMENT_THREE);
                    break;
            }
            return true;
        }
    });
}

private void replaceFragment(@NonNull Fragment fragment, @NonNull String tag) {
    if (!fragment.equals(currentFragment)) {
        fragmentManager
            .beginTransaction()
            .replace(R.id.frameLayout, fragment, tag)
            .commit();
        currentFragment = fragment;
    }
}

INFORMATIONS SUPPLÉMENTAIRES: Si vous voulez être sûr que les états des fragments ne changent pas et si vous voulez aussi pouvoir glisser les fragments, vous devriez envisager d'utiliser un ViewPager avec un FragmentStatePagerAdapter et changer le courant. fragmenter l'adaptateur avec chaque événement de clic

2
Nino Handler

Vous pouvez utiliser les méthodes attach () et detach ():

private FirstFragment firstFragment = FirstFragment.newInstance();
private SecondFragment secondFragment= SecondFragment.newInstance();
private ThirdFragment thirdFragment = ThirdFragment.newInstance();

navigation.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
        @Override
        public boolean onNavigationItemSelected(@NonNull MenuItem item) {
            switch (item.getItemId()) {
                case R.id.menu_dialer:
                    changeFragment(firstFragment, "firstFragment");
                    return true;
                case R.id.menu_email:
                    changeFragment(secondFragment, "secondFragment");
                    return true;
                case R.id.menu_map:
                    changeFragment(thirdFragment, "thirdFragment");
                    return true;
            }
            return false;
        }
    });

public void changeFragment(Fragment fragment, String tagFragmentName) {

    FragmentManager mFragmentManager = getSupportFragmentManager();
    FragmentTransaction fragmentTransaction = mFragmentManager.beginTransaction();

    Fragment currentFragment = mFragmentManager.getPrimaryNavigationFragment();
    if (currentFragment != null) {
        fragmentTransaction.detach(currentFragment);
    }

    Fragment fragmentTemp = mFragmentManager.findFragmentByTag(tagFragmentName);
    if (fragmentTemp == null) {
        fragmentTemp = fragment;
        fragmentTransaction.add(R.id.content, fragmentTemp, tagFragmentName);
    } else {
        fragmentTransaction.attach(fragmentTemp);
    }

    fragmentTransaction.setPrimaryNavigationFragment(fragmentTemp);
    fragmentTransaction.setReorderingAllowed(true);
    fragmentTransaction.commitNowAllowingStateLoss();
}
1
Albert

Toutes les réponses précédentes utilisent fragmentTransaction.replace(...). Cela remplacera le fragment actuel en le détruisant (ce qui cause le problème). Par conséquent, toutes ces solutions ne fonctionneront pas réellement. 

Voici la solution la plus proche pour résoudre ce problème:

private void selectContentFragment(Fragment fragmentToSelect)
{
    FragmentTransaction fragmentTransaction = this.getSupportFragmentManager().beginTransaction();

    if (this.getSupportFragmentManager().getFragments().contains(fragmentToSelect)) {
        // Iterate through all cached fragments.
        for (Fragment cachedFragment : this.getSupportFragmentManager().getFragments()) {
            if (cachedFragment != fragmentToSelect) {
                // Hide the fragments that are not the one being selected.
                fragmentTransaction.hide(cachedFragment);
            }
        }
        // Show the fragment that we want to be selected.
        fragmentTransaction.show(fragmentToSelect);
    } else {
        // The fragment to be selected does not (yet) exist in the fragment manager, add it.
        fragmentTransaction.add(R.id.fragment_container, fragmentToSelect);
    }

    fragmentTransaction.commit();
}

Pour que cela fonctionne, vous devez garder une trace des fragments dans un tableau (ou dans des variables séparées) dans votre activité. Pour référence, je pré-instancié tous les fragments dans un SparseArray.  

0
Thomas Neuteboom

Que diriez-vous de cela Vous déclarez les fragments dans la classe.

Fragment firstFragment,secondFragment,thirdFragment;

puis dans le switch-case vous pouvez coder comme ceci:

switch (item.getItemId()) {
    case R.id.menu_dialer:
        if(firstFragment != null) {
            fragment = firstFragment;
        }else{
            fragment = FirstFragment.newInstance();
        }
        break;
    case R.id.menu_email:
        // the same ...
        break;
    case R.id.menu_map:
        //the same ...
        break;
}
0
Relish Wang

Les solutions avec masquage/affichage des fragments sont mauvaises car cela bouffe la mémoire. Et aussi, si dans d'autres fragments il y a un travail difficile (chargement de données avec l'API par exemple), tout cela est chargé en même temps, donc quand l'application démarre, cela prendra longtemps. Je pense que c'est une bonne idée de sauvegarder les fragments uniquement lorsque vous appuyez dessus. En cas de transition répétée, l'objet existant est appelé. Très probablement, il y a des pièges, écrivez si vous tombez sur;) (p. Je ne suis pas locuteur natif)

Activité principale

public class MainActivity extends AppCompatActivity implements BottomNavigationView.OnNavigationItemSelectedListener {

private Fragment fragment1;
private Fragment fragment2;
private Fragment fragment3;

private FragmentManager fragmentManager;

private RelativeLayout fullLayout;
private BottomNavigationView bottomNavigationMenuView;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    fullLayout = (RelativeLayout) getLayoutInflater().inflate(R.layout.activity_body, null);
    super.setContentView(R.layout.activity_body);


    // Bottom navigation
    initBottomNavigation();

    // Init fragments
    initFragments();
}

private void initFragments() {
    fragmentManager = getSupportFragmentManager();
    fragment1 = new Fragment1();
    replaceFragment(fragment1);
}

protected void initBottomNavigation() {
    bottomNavigationMenuView = findViewById(R.id.bottomNavigation);
    bottomNavigationMenuView.setOnNavigationItemSelectedListener(this);
}

@Override
public boolean onNavigationItemSelected(@NonNull MenuItem menuItem) {
    switch (menuItem.getItemId()) {
        case R.id.bottom_menu_home:
            if (fragment1 == null) {
                fragment1 = new Fragment1();
            }
            replaceFragment(fragment1);
            return true;

        case R.id.bottom_menu_add:
            if (fragment2 == null) {
                fragment2 = new Fragment2();
            }
            replaceFragment(fragment2);
            return true;

        case R.id.bottom_menu_profile:
            if (fragment3 == null) {
                fragment3 = new Fragment3();
            }
            replaceFragment(fragment3);
            return true;
    }

    return true;
}

private void replaceFragment(@NonNull Fragment fragment) {
    fragmentManager
            .beginTransaction()
            .replace(R.id.mainContainer, fragment, fragment.getTag())
            .commit();
}
0
izmail

Créez les trois fragments en tant que membres de la classe et réutilisez-les.

public class MainActivity extends AppCompatActivity {
    private final Fragment mFirstFragment = new FirstFragment();
    private final Fragment mSecondFragment = new SecondFragment();
    private final Fragment mThirdFragment = new ThirdFragment();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...... 
        ......
        FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
        fragmentTransaction.replace(R.id.frameLayout, mFirstFragment);
        fragmentTransaction.commit();

        bottomNavigationView.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
            @Override
            public boolean onNavigationItemSelected(@NonNull MenuItem item) {
                if (bottomNavigationView.getSelectedItemId() != item.getItemId()) {
                    switch (item.getItemId()) {
                        R.id.menu_dialer:
                            replaceFragment(mFirstFragment);
                            break;
                        case R.id.menu_email:
                            replaceFragment(mSecondFragment);
                            break;
                        case R.id.menu_map:
                            replaceFragment(mThirdFragment);
                            break;
                    }
                }
                return true;
            }
        });
    }

    private void replaceFragment(Fragment fragment) {
        FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
        fragmentTransaction.replace(R.id.frameLayout, fragment);
        fragmentTransaction.commit();
    }

}
0
kws