Comment implémenter onBackPressed () dans des fragments?

460

Existe-t-il un moyen de mettre en œuvre onBackPressed() dans Android Fragment similaire à celui que nous implémentons dans Android Activity?

Comme le cycle de vie du fragment n'a pas onBackPressed(). Existe-t-il une autre méthode alternative pour contourner onBackPressed()les fragments d'Android 3.0?

Android_programmer_camera
la source
13
À mon humble avis, un fragment ne doit ni connaître ni se soucier du bouton RETOUR. Une activité peut se soucier du bouton RETOUR, mais avec des fragments qui sont normalement gérés par FragmentManager. Mais comme le fragment ne connaît pas son environnement plus large (par exemple, s'il fait partie ou non de l'activité), il n'est pas vraiment sûr qu'un fragment essaie de déterminer ce qu'est la fonctionnalité BACK appropriée.
CommonsWare
61
Que diriez-vous quand tout le fragment fait-il pour afficher une WebView, et que vous voulez que la WebView "revienne" à la page précédente lorsque le bouton de retour est enfoncé?
mharper
La réponse de Michael Herbig était parfaite pour moi. Mais pour ceux qui ne peuvent pas utiliser getSupportFragmentManager (). Utilisez plutôt getFragmentManager ().
Dantalian
La réponse de Dmitry Zaitsev sur [Question similaire] [1] fonctionne très bien. [1]: stackoverflow.com/a/13450668/1372866
ashakirov
6
pour répondre à mharper, vous pouvez faire quelque chose comme webview.setOnKeyListener (new OnKeyListener () {@Override public boolean onKey (View v, int keyCode, événement KeyEvent) {if (keyCode == KeyEvent.KEYCODE_BACK && webview.canGoBack ()) {webview.goBack (); return true;} return false;}});
Omid Aminiva

Réponses:

307

J'ai résolu de cette manière la dérogation onBackPresseddans l'activité. Tous FragmentTransactionsont addToBackStackavant de valider:

@Override
public void onBackPressed() {

    int count = getSupportFragmentManager().getBackStackEntryCount();

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

}
Hw.Master
la source
même problème stackoverflow.com/questions/32132623/…
Aditya Vyas-Lakhan
4
count == 1 permettra de fermer le premier fragment en appuyant sur un seul bouton de retour. Mais sinon, la meilleure solution
Kunalxigxag
2
Si vous utilisez la bibliothèque de support v7 et que votre activité s'étend de FragmentActivity (ou d'une sous-classe, telle que AppCompatActivity), cela se produira par défaut. Voir FragmentActivity # onBackPressed
Xiao
3
mais vous ne pouvez pas utiliser les variables, classes et fonctions de la classe de fragment dans ce code
user25
2
@Prabs si vous utilisez un fragment de support v4, assurez-vous que vous utilisez getSupportFragmentManager (). GetBackStackEntryCount ();
Veer3383
152

À mon avis, la meilleure solution est:

SOLUTION JAVA

Créez une interface simple:

public interface IOnBackPressed {
    /**
     * If you return true the back press will not be taken into account, otherwise the activity will act naturally
     * @return true if your processing has priority if not false
     */
    boolean onBackPressed();
}

Et dans votre activité

public class MyActivity extends Activity {
    @Override public void onBackPressed() {
    Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.main_container);
       if (!(fragment instanceof IOnBackPressed) || !((IOnBackPressed) fragment).onBackPressed()) {
          super.onBackPressed();
       }
    } ...
}

Enfin dans votre fragment:

public class MyFragment extends Fragment implements IOnBackPressed{
   @Override
   public boolean onBackPressed() {
       if (myCondition) {
            //action not popBackStack
            return true; 
        } else {
            return false;
        }
    }
}

SOLUTION KOTLIN

1 - Créer une interface

interface IOnBackPressed {
    fun onBackPressed(): Boolean
}

2 - Préparez votre activité

class MyActivity : AppCompatActivity() {
    override fun onBackPressed() {
        val fragment =
            this.supportFragmentManager.findFragmentById(R.id.main_container)
        (fragment as? IOnBackPressed)?.onBackPressed()?.not()?.let {
            super.onBackPressed()
        }
    }
}

3 - Implémentez dans votre fragment cible

class MyFragment : Fragment(), IOnBackPressed {
    override fun onBackPressed(): Boolean {
        return if (myCondition) {
            //action not popBackStack
            true
        } else {
            false
        }
    }
}
Maxime Jallu
la source
1
Qu'est-ce que c'est R.id.main_container? Est-ce l'ID du FragmentPager?
Nathan F.24
1
@MaximeJallu dans la solution Kotlin, mélanger les appels sécurisés ( .?) et appliquer des valeurs non nulles ( !!) peut conduire à des NPE - la distribution n'est pas toujours sûre. Je préfère écrire if ((fragment as? IOnBackPressed)?.onBackPressed()?.not() == true) { ... }ou plus kotliney(fragment as? IOnBackPressed)?.onBackPressed()?.not()?.let { ... }
Antek
1
@Antek Bonjour, Merci pour ton retour, tu aurais pu éditer le post, pour faire ta correction. Ne jugez pas sévèrement la solution globale.
Maxime Jallu
4
N'est-ce pas .onBackPressed()?.takeIf { !it }?.let{...}? .not()renvoie juste l'inverse.
David Miguel
1
val fragment = this.supportFragmentManager.findFragmentById(R.id.flContainer) as? NavHostFragment val currentFragment = fragment?.childFragmentManager?.fragments?.get(0) as? IOnBackPressed currentFragment?.onBackPressed()?.takeIf { !it }?.let{ super.onBackPressed() }Ceci est pour les personnes qui utilisent Kotlin et NavigationController
Pavle Pavlov
97

Selon @HaMMeRed, la réponse est le pseudocode comment cela devrait-il fonctionner. Disons que votre activité principale est appelée et BaseActivityqu'elle contient des fragments enfants (comme dans l'exemple de la bibliothèque SlidingMenu). Voici les étapes:

Nous devons d'abord créer une interface et une classe qui implémentent son interface pour avoir une méthode générique

  1. Créer une interface de classe OnBackPressedListener

    public interface OnBackPressedListener {
        public void doBack();
    }
  2. Créer une classe qui met en œuvre les compétences de OnBackPressedListener

    public class BaseBackPressedListener implements OnBackPressedListener {
        private final FragmentActivity activity;
    
        public BaseBackPressedListener(FragmentActivity activity) {
            this.activity = activity;
        }
    
        @Override
        public void doBack() {
            activity.getSupportFragmentManager().popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
        }
    }
  3. Depuis, nous allons travailler sur notre code BaseActivityet ses fragments

  4. Créez un auditeur privé au-dessus de votre classe BaseActivity

    protected OnBackPressedListener onBackPressedListener;
  5. créer une méthode pour définir l'écouteur dans BaseActivity

    public void setOnBackPressedListener(OnBackPressedListener onBackPressedListener) {
        this.onBackPressedListener = onBackPressedListener;
    }
  6. en remplacement onBackPressedmettre en œuvre quelque chose comme ça

    @Override
    public void onBackPressed() {
        if (onBackPressedListener != null)
            onBackPressedListener.doBack();
        else
            super.onBackPressed();
  7. dans votre fragment, onCreateViewvous devez ajouter notre auditeur

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        activity = getActivity();
    
        ((BaseActivity)activity).setOnBackPressedListener(new BaseBackPressedListener(activity));
    
        View view = ... ;
    //stuff with view
    
        return view;
    }

Voila, maintenant, lorsque vous cliquez en arrière sur un fragment, vous devez attraper votre méthode personnalisée sur le dos.

poisson mort
la source
Si plus d'un fragment est un écouteur, comment déterminez-vous, dans onBackPressed()quel fragment était affiché lorsque le bouton de retour a été enfoncé?
Al Lelopath
2
vous pouvez personnaliser l'écouteur de clics à la place du nouveau baseBackPressedListener et l'appeler comme anonyme pour définir son propre comportement, quelque chose comme ceci:((BaseActivity)activity).setOnBackPressedListener(new OnBackpressedListener(){ public void doBack() { //...your stuff here }});
deadfish
Merci, j'ai changé de position cependant, je recommanderais maintenant de découpler avec les messages de diffusion locale. Je pense que cela donnerait des composants de meilleure qualité qui sont fortement découplés.
HaMMeReD
isue here stackoverflow.com/questions/32132623/…
Aditya Vyas-Lakhan
Cela a parfaitement fonctionné pour moi, je redémarre l'activité
raphaelbgr
86

Cela a fonctionné pour moi: https://stackoverflow.com/a/27145007/3934111

@Override
public void onResume() {
    super.onResume();

    if(getView() == null){
        return;
    }

    getView().setFocusableInTouchMode(true);
    getView().requestFocus();
    getView().setOnKeyListener(new View.OnKeyListener() {
        @Override
        public boolean onKey(View v, int keyCode, KeyEvent event) {

            if (event.getAction() == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK){
                // handle back button's click listener
                return true;
            }
            return false;
        }
    });
}
Rodrigo Rodrigues
la source
2
Fonctionne parfaitement! Mais vous devez également gérer l'action par défaut, en écrivant probablement une autre condition IF. Depuis, je baissais mon slideUpPanel s'il était développé. Donc, j'ai d'abord dû cocher si (le panneau est en place) alors .....
sud007
18
Le problème avec cette solution est que getView () ne renvoie que la vue principale (et non la sous-vue, si elles gagnent le focus). Donc, si vous avez un EditText et tapez du texte, puis appuyez une fois sur "Retour" pour masquer le clavier, la deuxième pression sur "Retour" est lancée à partir du "EditText" (une vue elle-même) et cette méthode ne semble pas intercepter le " retour "appuyez ensuite sur le bouton (car il n'accroche qu'à la vue principale). Si je comprends bien, vous pouvez attraper les événements de pression de touche sur votre "EditText" ainsi que toutes les autres vues, mais si vous avez un formulaire "complexe", ce n'est pas aussi propre et maintenable qu'il pourrait l'être.
Pelpotronic
Il s'agit d'une excellente solution simple pour le cauchemar qui est des fragments. Si vous avez un formulaire edittext «complexe», vous devriez probablement supprimer votre projet et recommencer. Renvoyez false lorsque vous souhaitez utiliser le comportement standard. Revenez vrai lorsque vous avez intercepté la presse arrière et que vous en avez fait quelque chose
behelit
65

Si vous vouliez ce type de fonctionnalité, vous devrez la remplacer dans votre activité, puis ajouter un YourBackPressed interface à tous vos fragments, que vous appelez le fragment approprié chaque fois que vous appuyez sur le bouton Précédent.

Edit: je voudrais ajouter ma réponse précédente.

Si je devais le faire aujourd'hui, j'utiliserais une diffusion, ou éventuellement une diffusion ordonnée si je m'attendais à ce que d'autres panneaux soient mis à jour à l'unisson vers le panneau de contenu principal / principal.

LocalBroadcastManagerdans la bibliothèque de support peut vous aider, et vous envoyez simplement la diffusion onBackPressedet vous abonnez dans vos fragments qui vous intéressent. Je pense que la messagerie est une implémentation plus découplée et évoluerait mieux, donc ce serait ma recommandation officielle de mise en œuvre maintenant. Utilisez simplement leIntent action de comme filtre pour votre message. envoyez votre nouvellement créé ACTION_BACK_PRESSED, envoyez-le de votre activité et écoutez-le dans les fragments appropriés.

HaMMeReD
la source
4
Idée assez astucieuse! quelques réflexions supplémentaires: + il est important de réaliser cette logique avec la diffusion allant de l'activité> des fragments (pas l'inverse). OnBackPressed est uniquement disponible au niveau de l'activité, il est donc essentiel de piéger l'événement et de le diffuser à tous les fragments "d'écoute". + L'utilisation d'un EventBus (comme Otto de Square) rend l'implémentation pour un cas comme celui-ci trivial!
Kaushik Gopal
2
Je n'ai pas encore utilisé EventBus de Square, mais je serai dans les futurs produits. Je pense que c'est une meilleure solution pure java, et si vous pouvez supprimer les sections Android de votre architecture, tant mieux.
HaMMeReD
Comment ne laissez-vous que les fragments supérieurs pour gérer l'événement? par exemple, vous voudriez détruire le fragment actuellement affiché
eugene
Le récepteur de diffusion doit être lié au cycle de vie si vous l'utilisez, donc lorsqu'il n'est pas visible, il ne devrait pas recevoir l'événement.
HaMMeReD
Notez également que LocalBroadcastManagervous ne pouvez pas faire d'émissions commandées
Xiao
46

Rien de tout cela n'est facile à mettre en œuvre et ne fonctionnera pas de manière optimale.

Les fragments ont un appel de méthode onDetach qui fera le travail.

@Override
    public void onDetach() {
        super.onDetach();
        PUT YOUR CODE HERE
    }

CECI FERA LE TRAVAIL.

Sterling Diaz
la source
20
mais le problème ici est que vous ne savez pas si le fragment a été détaché en raison d'une contre-pression ou en raison d'une autre action, ce qui a conduit l'utilisateur à effectuer une autre activité ou un autre fragment.
Sunny
7
onDetach () est appelé lorsque le fragment n'est plus attaché à son activité. Ceci est appelé après onDestroy (). Bien que vous obteniez ce code lorsque vous appuyez sur le bouton de retour, vous obtiendrez également ce code lorsque vous passez d'un fragment à l'autre. Vous pourriez faire quelque chose de astucieux pour que cela fonctionne ...
apmartin1991
Fonctionne bien pour un DialogFragment.
CoolMind
2
N'oubliez pas d'ajouter isRemoving()comme décrit dans stackoverflow.com/a/27103891/2914140 .
CoolMind
1
c'est faux. Il peut être appelé pour plusieurs raisons
Bali
40

Si vous utilisez androidx.appcompat:appcompat:1.1.0ou au-dessus, vous pouvez ajouter un OnBackPressedCallbackà votre fragment comme suit

requireActivity()
    .onBackPressedDispatcher
    .addCallback(this, object : OnBackPressedCallback(true) {
        override fun handleOnBackPressed() {
            Log.d(TAG, "Fragment back pressed invoked")
            // Do custom work here    

            // if you want onBackPressed() to be called as normal afterwards
            if (isEnabled) {
                isEnabled = false
                requireActivity().onBackPressed()
            }
        }
    }
)

Voir https://developer.android.com/guide/navigation/navigation-custom-back

aaronmarino
la source
Si vous utilisez androidx-core-ktx, vous pouvez utiliserrequireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner) { /* code to be executed when back is pressed */ }
Max
Il est important de noter que le LifecycleOwnerparamètre doit être ajouté comme dans cet exemple. Sans cela, tous les fragments commencés par la suite seront appelés handleBackPressed()si le bouton de retour est enfoncé.
Eric B.
cette méthode est-elle indispensable avec la bibliothèque de navigation?
Silence
25

Ajoutez simplement addToBackStackpendant la transition entre vos fragments comme ci-dessous:

fragmentManager.beginTransaction().replace(R.id.content_frame,fragment).addToBackStack("tag").commit();

si vous écrivez addToBackStack(null), il le traitera de lui-même mais si vous donnez une balise, vous devez la gérer manuellement.

Rudi
la source
Y a-t-il autre chose à faire ou l'ajout de la méthode addToBackStack est-il suffisant?
Sreecharan Desabattula
1
si vous ajoutez simplement que cela devrait fonctionner, s'il ne fonctionne toujours pas, il devrait y avoir quelque chose dans votre code. Laissez le code quelque part pour voir s'il vous plaît @SreecharanDesabattula
Rudi
@Rudi pouvez-vous dire à stackoverflow.com/questions/32132623/…
Aditya Vyas-Lakhan
20

puisque cette question et certaines des réponses ont plus de cinq ans, permettez-moi de partager ma solution. Ceci est un suivi et une modernisation de la réponse de @oyenigun

MISE À JOUR: À Au bas de cet article, j'ai ajouté une implémentation alternative utilisant une extension de fragment abstraite qui n'impliquera pas du tout l'activité, ce qui serait utile pour toute personne ayant une hiérarchie de fragments plus complexe impliquant des fragments imbriqués qui nécessitent un comportement arrière différent.

J'avais besoin de l'implémenter car certains des fragments que j'utilise ont des vues plus petites que je voudrais ignorer avec le bouton de retour, telles que les petites vues d'informations qui apparaissent, etc., mais cela est bon pour tous ceux qui ont besoin de remplacer le comportement de le bouton de retour à l'intérieur des fragments.

Tout d'abord, définissez une interface

public interface Backable {
    boolean onBackPressed();
}

Cette interface, que j'appelle Backable(je suis un adepte des conventions de dénomination), a une seule méthode onBackPressed()qui doit retourner une booleanvaleur. Nous devons appliquer une valeur booléenne, car nous devrons savoir si la pression sur le bouton de retour a «absorbé» l'événement de retour. Renvoyer truesignifie que c'est le cas, et qu'aucune action supplémentaire n'est nécessaire, sinon, falseindique que l'action de retour par défaut doit toujours avoir lieu. Cette interface doit être son propre fichier (de préférence dans un package distinct nommé interfaces). N'oubliez pas que la séparation de vos classes en packages est une bonne pratique.

Deuxièmement, trouvez le fragment supérieur

J'ai créé une méthode qui renvoie le dernier Fragmentobjet de la pile arrière. J'utilise des balises ... si vous utilisez des identifiants, apportez les modifications nécessaires. J'ai cette méthode statique dans une classe utilitaire qui traite des états de navigation, etc ... mais bien sûr, mettez-la là où elle vous convient le mieux. Pour l'édification, j'ai mis le mien dans une classe appelée 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;
}

Assurez-vous que le nombre de piles de retour est supérieur à 0, sinon un ArrayOutOfBoundsExceptionpourrait être lancé lors de l'exécution. S'il n'est pas supérieur à 0, retournez null. Nous vérifierons une valeur nulle plus tard ...

Troisièmement, implémentez dans un fragment

Implémentez l' Backableinterface dans le fragment dans lequel vous devez remplacer le comportement du bouton de retour. Ajoutez la méthode d'implémentation.

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

...

    @Override
    public boolean onBackPressed() {

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

}

Dans le onBackPressed()remplacement, mettez la logique dont vous avez besoin. Si vous souhaitez que le bouton Précédent ne fasse pas apparaître la pile arrière (comportement par défaut), retournez true , que votre événement de retour a été absorbé. Sinon, retournez false.

Enfin, dans votre activité ...

Redéfinissez la onBackPressed()méthode et ajoutez-y cette logique:

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

Nous obtenons le fragment actuel dans la pile arrière, puis nous effectuons une vérification nulle et déterminons s'il implémente notre Backableinterface. Si c'est le cas, déterminez si l'événement a été absorbé. Si oui, nous en avons fini aveconBackPressed() et pouvons revenir. Sinon, traitez-le comme une contre-pression normale et appelez la super méthode.

Deuxième option pour ne pas impliquer l'activité

Parfois, vous ne voulez pas que l'activité gère cela du tout, et vous devez le gérer directement dans le fragment. Mais qui dit que tu ne peux pas avoir de fragments avec une API de contre-presse? Étendez simplement votre fragment à une nouvelle classe.

Créez une classe abstraite qui étend Fragment et implémente l' View.OnKeyListnerinterface ...

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

Comme vous pouvez le voir, tout fragment qui se prolonge BackableFragmentcapturera automatiquement les clics en arrière en utilisant l' View.OnKeyListenerinterface. Appelez simplement la onBackButtonPressed()méthode abstraite à partir de la onKey()méthode implémentée en utilisant la logique standard pour discerner une pression sur le bouton de retour. Si vous devez enregistrer des clics sur les touches autres que le bouton Précédent, assurez-vous simplement d'appeler la superméthode lors de la substitutiononKey() dans votre fragment, sinon vous remplacerez le comportement dans l'abstraction.

Simple à utiliser, il suffit d'étendre et de mettre en œuvre:

public class FragmentChannels extends BackableFragment {

    ...

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

    ...
}

Puisque la onBackButtonPressed()méthode dans la super classe est abstraite, une fois que vous étendez, vous devez implémenter onBackButtonPressed(). Il revient voidcar il a juste besoin d'effectuer une action au sein de la classe de fragments et n'a pas besoin de relayer l'absorption de la presse à l'activité. Assurez- vous d'appeler la onBackPressed()méthode Activity si tout ce que vous faites avec le bouton de retour ne nécessite pas de manipulation, sinon le bouton de retour sera désactivé ... et vous ne le voulez pas!

Avertissements Comme vous pouvez le voir, cela définit l'écouteur clé sur la vue racine du fragment, et nous devons le concentrer. S'il y a des textes d'édition impliqués (ou toute autre vue volant le focus) dans votre fragment qui étend cette classe, (ou d'autres fragments internes ou vues qui ont la même), vous devrez gérer cela séparément. Il y a un bon article sur l' extension d'un EditText pour perdre le focus sur une presse arrière.

J'espère que quelqu'un trouve cela utile. Codage heureux.

mwieczorek
la source
Merci, c'est une bonne solution. Pouvez-vous dire, pourquoi appelez-vous super.onBackPressed();deux fois?
CoolMind
Il n'est appelé qu'une seule fois par scénario concernant l'état nul de currentFragment. Si le fragment n'est pas nul et que le fragment implémente l' Backableinterface, aucune action n'est effectuée. Si le fragment n'est pas nul et qu'il n'implémente PAS Backable, nous appelons la super méthode. Si le fragment est nul, il passe au dernier super appel.
mwieczorek
Désolé, je n'ai pas compris. Dans le cas où a currentFragmentn'est pas nul et est une instance de Backable et qu'il est détaché (nous avons appuyé backsur le bouton et fermé le fragment), la première occurrence de super.onBackPressed();est appelée, puis la seconde.
CoolMind
Excellente solution
David Toledo
Peut-être que "Closeable" sonne un peu mieux que "Backable"
pumnao
15

La solution est simple:

1) Si vous avez une classe de fragments de base que tous les fragments étendent, ajoutez ce code à sa classe, sinon créez une telle classe de fragments de base

 /* 
 * called when on back pressed to the current fragment that is returned
 */
 public void onBackPressed()
 {
    // add code in super class when override
 }

2) Dans votre classe Activity, remplacez onBackPressed comme suit:

private BaseFragment _currentFragment;

@Override
public void onBackPressed()
{
      super.onBackPressed();
     _currentFragment.onBackPressed();
}

3) Dans votre classe Fragment, ajoutez le code souhaité:

 @Override
 public void onBackPressed()
 {
    setUpTitle();
 }
IdoT
la source
9

onBackPressed() amener le Fragment à se détacher de l'Activité.

Selon @Sterling Diaz, je pense qu'il a raison. MAIS une situation sera fausse. (ex. Rotation de l'écran)

Donc, je pense que nous pourrions détecter si isRemoving() faut atteindre les objectifs.

Vous pouvez l'écrire sur onDetach()ou onDestroyView(). C'est du travail.

@Override
public void onDetach() {
    super.onDetach();
    if(isRemoving()){
        // onBackPressed()
    }
}

@Override
public void onDestroyView() {
    super.onDestroyView();
    if(isRemoving()){
        // onBackPressed()
    }
}
林奕 忠
la source
8

Eh bien, je l'ai fait comme ça, et ça marche pour moi

Interface simple

FragmentOnBackClickInterface.java

public interface FragmentOnBackClickInterface {
    void onClick();
}

Exemple d'implémentation

MyFragment.java

public class MyFragment extends Fragment implements FragmentOnBackClickInterface {

// other stuff

public void onClick() {
       // what you want to call onBackPressed?
}

puis juste remplacer onBackPressed dans l'activité

    @Override
public void onBackPressed() {
    int count = getSupportFragmentManager().getBackStackEntryCount();
    List<Fragment> frags = getSupportFragmentManager().getFragments();
    Fragment lastFrag = getLastNotNull(frags);
    //nothing else in back stack || nothing in back stack is instance of our interface
    if (count == 0 || !(lastFrag instanceof FragmentOnBackClickInterface)) {
        super.onBackPressed();
    } else {
        ((FragmentOnBackClickInterface) lastFrag).onClick();
    }
}

private Fragment getLastNotNull(List<Fragment> list){
    for (int i= list.size()-1;i>=0;i--){
        Fragment frag = list.get(i);
        if (frag != null){
            return frag;
        }
    }
    return null;
}
Błażej
la source
Ca ne fonctionne pas ! super.onbackpressed () est toujours appelé :(
Animesh Mangla
Comment passer cet événement Fragment intérieur. J'ai ViewPager en activité et ce ViewPager a des fragments, maintenant tous ces fragments ont un fragment enfant. Comment renvoyer l'événement de bouton à ces fragments enfants.
Kishan Vaghela
@KishanVaghela et bien je n'ai pas touché à android depuis, mais j'ai une idée. Donnez-moi le 24 pour mettre à jour ma réponse.
Błażej
Ok pas de problème, une option est que je dois passer un événement d'écoute à chaque fragment enfant du fragment parent. mais ce n'est pas un moyen idéal car je dois le faire pour chaque fragment.
Kishan Vaghela
@KishanVaghela Je pensais au manager en activité. Vous pouvez ajouter / supprimer des fragments avec cette interface. Mais avec cette approche, vous devez implémenter le gestionnaire dans chaque fragment qui a des sous-fragments. Alors peut-être que la meilleure solution sera de créer un gestionnaire en tant que singleton. Ensuite, vous devrez ajouter / supprimer des fragments / objets et dans «onBackPressed», vous appelez la méthode «onBackPressed» de ce gestionnaire. Cette méthode appellera la méthode «onClick» dans chaque objet ajouté au gestionnaire. Est-ce plus ou moins clair pour vous?
Błażej
8

Vous devez ajouter une interface à votre projet comme ci-dessous;

public interface OnBackPressed {

     void onBackPressed();
}

Et puis, vous devez implémenter cette interface sur votre fragment;

public class SampleFragment extends Fragment implements OnBackPressed {

    @Override
    public void onBackPressed() {
        //on Back Pressed
    }

}

Et vous pouvez déclencher cet événement onBackPressed sous vos activités onBackPressed comme ci-dessous;

public class MainActivity extends AppCompatActivity {
       @Override
        public void onBackPressed() {
                Fragment currentFragment = getSupportFragmentManager().getFragments().get(getSupportFragmentManager().getBackStackEntryCount() - 1);
                if (currentFragment instanceof OnBackPressed) {  
                    ((OnBackPressed) currentFragment).onBackPressed();
                }
                super.onBackPressed();
        }
}
oyenigun
la source
C'est une bonne méthode, mais attention, n'appelez pas getActivity().onBackPressed();car cela invoquera l'exception: "java.lang.StackOverflowError: stack size 8MB".
CoolMind
8

Ce n'est qu'un petit code qui fera l'affaire:

 getActivity().onBackPressed();

J'espère que cela aide quelqu'un :)

Shahid Sarwar
la source
4
Il a demandé à outrepasser le comportement, pas simplement à invoquer la méthode d'activité.
mwieczorek
2
Je l'ai lu attentivement, merci. Votre solution relaie simplement la méthode onBackPressed vers l'activité, lorsqu'il lui a demandé comment remplacer.
mwieczorek
7

Si vous utilisez EventBus, c'est probablement une solution beaucoup plus simple:

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

et dans votre classe d'activité, vous pouvez définir:

@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 n'est qu'un objet POJO

C'est super propre et il n'y a aucun problème d'interface / implémentation.

Akshat
la source
7

c'est ma solution:

dans MyActivity.java:

public interface OnBackClickListener {
        boolean onBackClick();
    }

    private OnBackClickListener onBackClickListener;

public void setOnBackClickListener(OnBackClickListener onBackClickListener) {
        this.onBackClickListener = onBackClickListener;
    }

@Override
    public void onBackPressed() {
        if (onBackClickListener != null && onBackClickListener.onBackClick()) {
            return;
        }
        super.onBackPressed();
    }

et en fragment:

((MyActivity) getActivity()).setOnBackClickListener(new MyActivity.OnBackClickListener() {
    @Override
    public boolean onBackClick() {
        if (condition) {
            return false;
        }

        // some codes

        return true;
    }
});
Hamidreza Samadi
la source
6

En utilisant le composant de navigation, vous pouvez le faire comme ceci :

Java

public class MyFragment extends Fragment {

@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // This callback will only be called when MyFragment is at least Started.
    OnBackPressedCallback callback = new OnBackPressedCallback(true /* enabled by default */) {
        @Override
        public void handleOnBackPressed() {
            // Handle the back button event
        }
    });
    requireActivity().getOnBackPressedDispatcher().addCallback(this, callback);

    // The callback can be enabled or disabled here or in handleOnBackPressed()
}
...
}

Kotlin

class MyFragment : Fragment() {

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    // This callback will only be called when MyFragment is at least Started.
    val callback = requireActivity().onBackPressedDispatcher.addCallback(this) {
        // Handle the back button event
    }

    // The callback can be enabled or disabled here or in the lambda
}
...
}
B-GangsteR
la source
4

Que diriez-vous d'utiliser onDestroyView ()?

@Override
public void onDestroyView() {
    super.onDestroyView();
}
Le meilleur artiste
la source
5
que diriez-vous d'aller à un autre fragment du fragment réel, que se passera-t-il en utilisant votre méthode? :)
Choletski
Réponse la plus inutile. C'est la même chose que l'écriture i = iou if (1 < 0) {}.
CoolMind
4
@Override
public void onResume() {
    super.onResume();

    getView().setFocusableInTouchMode(true);
    getView().requestFocus();
    getView().setOnKeyListener(new View.OnKeyListener() {
        @Override
        public boolean onKey(View v, int keyCode, KeyEvent event) {
            if (event.getAction() == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) {
                // handle back button
                replaceFragmentToBackStack(getActivity(), WelcomeFragment.newInstance(bundle), tags);

                return true;
            }

            return false;
        }
    });
}
Gururaj Hosamani
la source
Seulement si les fragments n'ont pas une vue focalisable (telle que l'édition de texte), laissez les fragments gérer le bouton de retour lui-même comme cette réponse le fait. Sinon, laissez l'activité contenant des fragments le faire par OnBackPress () lors de l'écriture de la première réponse; car la vue focalisable volera la focalisation du fragment. Cependant, nous pouvons retrouver le focus pour le fragment en mettant clairement au point toutes les vues pouvant être mises au point par la méthode clearFocus () et re-focus pour fragmenter.
Nguyen Tan Dat
4
public class MyActivity extends Activity {

    protected OnBackPressedListener onBackPressedListener;

    public interface OnBackPressedListener {
        void doBack();
    }

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

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

    @Override
    protected void onDestroy() {
        onBackPressedListener = null;
        super.onDestroy();
    }
}

dans votre fragment, ajoutez ce qui suit, n'oubliez pas d'implémenter l'interface de mainactivity.

public class MyFragment extends Framgent implements MyActivity.OnBackPressedListener {
    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
         ((MyActivity) getActivity()).setOnBackPressedListener(this);
    }

@Override
public void doBack() {
    //BackPressed in activity will call this;
}

}
joelvarma
la source
4

Dans ma solution (Kotlin);

J'utilise la fonction onBackAlternative comme paramètre sur BaseActivity .

BaseActivity

abstract class BaseActivity {

    var onBackPressAlternative: (() -> Unit)? = null

    override fun onBackPressed() {
        if (onBackPressAlternative != null) {
            onBackPressAlternative!!()
        } else {
            super.onBackPressed()
        }
    }
}

J'ai une fonction pour définir onBackPressAlternative sur BaseFragment .

BaseFragment

abstract class BaseFragment {

     override fun onStart() {
        super.onStart()
        ...
        setOnBackPressed(null) // Add this
     }

      //Method must be declared as open, for overriding in child class
     open fun setOnBackPressed(onBackAlternative: (() -> Unit)?) {
         (activity as BaseActivity<*, *>).onBackPressAlternative = onBackAlternative
     }
}

Ensuite, mon onBackPressAlternative est prêt à être utilisé sur des fragments.

Sous-fragments

override fun setOnBackPressed(onBackAlternative: (() -> Unit)?) {
    (activity as BaseActivity<*, *>).onBackPressAlternative = {
        // TODO Your own custom onback function 
    }
}
Infliger
la source
3

N'implémentez pas la méthode ft.addToBackStack () pour que lorsque vous appuyez sur le bouton de retour, votre activité soit terminée.

proAddAccount = new ProfileAddAccount();
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.fragment_container, proAddAccount);
//fragmentTransaction.addToBackStack(null);
fragmentTransaction.commit();
Muhammad Aamir Ali
la source
dites-moi stackoverflow.com/questions/32132623/…
Aditya Vyas-Lakhan
3

Suivez simplement ces étapes:

Toujours lors de l'ajout d'un fragment,

fragmentTransaction.add(R.id.fragment_container, detail_fragment, "Fragment_tag").addToBackStack(null).commit();

Ensuite, dans l'activité principale, remplacez onBackPressed()

if (getSupportFragmentManager().getBackStackEntryCount() > 0) {
    getSupportFragmentManager().popBackStack();
} else {
    finish();
}

Pour gérer le bouton de retour dans votre application,

Fragment f = getActivity().getSupportFragmentManager().findFragmentByTag("Fragment_tag");
if (f instanceof FragmentName) {
    if (f != null) 
        getActivity().getSupportFragmentManager().beginTransaction().remove(f).commit()               
}

C'est ça!

Divya
la source
3

Dans le cycle de vie des activités, toujours le bouton de retour Android traite les transactions FragmentManager lorsque nous utilisons FragmentActivity ou AppCompatActivity.

Pour gérer le backstack, nous n'avons pas besoin de gérer son nombre de backstack ou de marquer quoi que ce soit, mais nous devons garder le focus lors de l'ajout ou du remplacement d'un fragment. Veuillez trouver les extraits suivants pour gérer les boîtiers de bouton arrière,

    public void replaceFragment(Fragment fragment) {

        FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
        if (!(fragment instanceof HomeFragment)) {
            transaction.addToBackStack(null);
        }
        transaction.replace(R.id.activity_menu_fragment_container, fragment).commit();
    }

Ici, je n'ajouterai pas de pile de retour pour mon fragment d'accueil car c'est la page d'accueil de mon application. Si vous ajoutez addToBackStack à HomeFragment, l'application attendra pour supprimer tous les problèmes d'acactivité, puis nous obtiendrons un écran vide, donc je garde la condition suivante,

if (!(fragment instanceof HomeFragment)) {
            transaction.addToBackStack(null);
}

Maintenant, vous pouvez voir le fragment précédemment ajouté sur acitvity et l'application se fermera lorsque vous atteindrez HomeFragment. vous pouvez également consulter les extraits suivants.

@Override
public void onBackPressed() {

    if (mDrawerLayout.isDrawerOpen(Gravity.LEFT)) {
        closeDrawer();
    } else {
        super.onBackPressed();
    }
}
Deva
la source
3

Fragment: créez un BaseFragment en plaçant une méthode:

 public boolean onBackPressed();

Activité:

@Override
public void onBackPressed() {
    List<Fragment> fragments = getSupportFragmentManager().getFragments();
    if (fragments != null) {
        for (Fragment fragment : fragments) {
            if (!fragment.isVisible()) continue;

            if (fragment instanceof BaseFragment && ((BaseFragment) fragment).onBackPressed()) {
                return;
            }
        }
    }

    super.onBackPressed();
}

Votre activité se déroulera sur les fragments attachés et visibles et appellera onBackPressed () sur chacun d'entre eux et abandonnera si l'un d'eux retourne `` vrai '' (ce qui signifie qu'il a été géré, donc aucune autre action).

Alécio Carvalho
la source
Monsieur, celui-ci est la manière la plus élégante et fonctionne parfaitement!
asozcan
3

Selon les notes de version AndroidX , androidx.activity 1.0.0-alpha01est publié et introduit ComponentActivityune nouvelle classe de base de l'existant FragmentActivityet AppCompatActivity. Et cette version nous apporte une nouvelle fonctionnalité:

Vous pouvez désormais enregistrer un OnBackPressedCallbackvia addOnBackPressedCallbackpour recevoir des onBackPressed()rappels sans avoir à remplacer la méthode dans votre activité.

TonnyL
la source
Pouvez-vous en montrer un exemple? Je n'arrive pas à résoudre cette méthode.
Bryan Bryce
1
@BryanBryce Je n'ai pas trouvé d'exemple mais le doc officiel: developer.android.com/reference/androidx/activity/…
TonnyL
La méthode ne résoudra pas b / c AppCompatActivity s'étend en fait à partir de androidx.core.app.ComponentActivity au lieu de androidx.activity.ComponentActivity.
wooldridgetm
3

Vous pouvez enregistrer un fragment en activité pour gérer la presse arrière:

interface BackPressRegistrar {
    fun registerHandler(handler: BackPressHandler)
    fun unregisterHandler(handler: BackPressHandler)
}

interface BackPressHandler {
    fun onBackPressed(): Boolean
}

usage:

En fragment:

private val backPressHandler = object : BackPressHandler {
    override fun onBackPressed(): Boolean {
        showClosingWarning()
        return false
    }
}

override fun onResume() {
    super.onResume()
    (activity as? BackPressRegistrar)?.registerHandler(backPressHandler)
}

override fun onStop() {
    (activity as? BackPressRegistrar)?.unregisterHandler(backPressHandler)
    super.onStop()
}

En activité:

class MainActivity : AppCompatActivity(), BackPressRegistrar {


    private var registeredHandler: BackPressHandler? = null
    override fun registerHandler(handler: BackPressHandler) { registeredHandler = handler }
    override fun unregisterHandler(handler: BackPressHandler) { registeredHandler = null }

    override fun onBackPressed() {
        if (registeredHandler?.onBackPressed() != false) super.onBackPressed()
    }
}
Francis
la source
3

Il est désormais plus facile de fournir une navigation arrière personnalisée en gérant onBackPressed avec des rappels à l'intérieur du fragment.

class MyFragment : Fragment() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val onBackPressedCallback = object : OnBackPressedCallback(true) {
            override fun handleOnBackPressed() {
                if (true == conditionForCustomAction) {
                    myCustomActionHere()
                } else  NavHostFragment.findNavController(this@MyFragment).navigateUp();    
        }
    }
    requireActivity().onBackPressedDispatcher.addCallback(
        this, onBackPressedCallback
    )
    ...
}

Si vous souhaitez que l'action de retour par défaut soit basée sur une condition, vous pouvez utiliser:

 NavHostFragment.findNavController(this@MyFragment).navigateUp();
krishnakumarcn
la source
3

À l'intérieur de la méthode onCreate du fragment, ajoutez ce qui suit:

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    OnBackPressedCallback callback = new OnBackPressedCallback(true) {
        @Override
        public void handleOnBackPressed() {
            //Handle the back pressed
        }
    };
    requireActivity().getOnBackPressedDispatcher().addCallback(this, callback);
}
David Elmasllari
la source
après avoir ajouté ce backpress myFragment fonctionne mais maintenant le backpress d'activité n'a pas fonctionné
Sunil Chaudhary
2

Meilleure solution,

import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v7.app.AppCompatActivity;

public class BaseActivity extends AppCompatActivity {
    @Override
    public void onBackPressed() {

        FragmentManager fm = getSupportFragmentManager();
        for (Fragment frag : fm.getFragments()) {
            if (frag == null) {
                super.onBackPressed();
                finish();
                return;
            }
            if (frag.isVisible()) {
                FragmentManager childFm = frag.getChildFragmentManager();
                if (childFm.getFragments() == null) {
                    super.onBackPressed();
                    finish();
                    return;
                }
                if (childFm.getBackStackEntryCount() > 0) {
                    childFm.popBackStack();
                    return;
                }
                else {

                    fm.popBackStack();
                    if (fm.getFragments().size() <= 1) {
                        finish();
                    }
                    return;
                }

            }
        }
    }
}
Shervin Gharib
la source