web-dev-qa-db-fra.com

Pop le backstack fragment sans jouer le Pop-Animation

I Poussez un fragment sur la pile en utilisant le code suivant:

FragmentManager fragmentManager = getActivity().getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_right,
     R.anim.slide_in_left, R.anim.slide_out_left);
fragmentTransaction.replace(getId(), newFragment);
fragmentTransaction.addToBackStack(null);
fragmentTransaction.commit();

De cette façon, lorsque la pile de fragments est éclatée, par ex. en appuyant sur le bouton de retour, une animation de fragment pop est jouée. Cependant, il y a des situations dans lesquelles je voudrais faire apparaître le backstack de fragments sans afficher cette animation, par exemple. parce que je viens de rentrer d’une autre activité et que je veux afficher le fragment précédent à la fois, sans animation.

Un exemple de navigation pourrait ressembler à ceci:

  • L'utilisateur est sur l'écran de démarrage avec le fragment racine
  • Il sélectionne un élément sur le fragment racine qui affiche ensuite un nouveau fragment pour afficher les détails de cet élément. Pour ce faire, il utilise une transaction de fragment qui définit les animations pour les cas Push et Pop (si l'utilisateur appuie sur le bouton Précédent, la transition s'anime)
  • À partir de ce fragment, il commence une activité qui (pour une raison quelconque) supprime l'élément qui vient d'être montré
  • Une fois cette activité terminée, je voudrais revenir au fragment racine sans afficher "l'animation pop" du "fragment de détail".

Y a-t-il un moyen de faire apparaître le backstack du fragment sans jouer l'animation pop spécifiée?

49
ChristophK

Warpzit était donc sur la bonne voie, il n'a tout simplement pas abordé votre problème en particulier. Je suis tombé sur le même problème et voici comment je l'ai résolu.

J'ai d'abord créé une variable booléenne statique (par souci de simplicité, mettons-la dans la classe FragmentUtils) ...

public class FragmentUtils {
    public static boolean sDisableFragmentAnimations = false;
}

Ensuite, dans CHAQUE fragment que vous avez, vous devez redéfinir la méthode onCreateAnimation ...

@Override
public Animation onCreateAnimation(int transit, boolean enter, int nextAnim) {
    if (FragmentUtils.sDisableFragmentAnimations) {
        Animation a = new Animation() {};
        a.setDuration(0);
        return a;
    }
    return super.onCreateAnimation(transit, enter, nextAnim);
}

Ensuite, lorsque vous devez effacer le backstack de votre activité, procédez comme suit:.

public void clearBackStack() {
    FragmentUtils.sDisableFragmentAnimations = true;
    getSupportFragmentManager().popBackStackImmediate(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
    FragmentUtils.sDisableFragmentAnimations = false;
}

Et voilà, un appel à clearBackStack () vous replongera dans le fragment racine sans aucune animation de transition. 

Espérons que le grand G va ajouter une façon moins stupide de le faire à l'avenir.

84
Geoff

Donc pour les travaux suivants de la bibliothèque de support:

Dans le fragment qui devrait avoir une animation pop personnalisée, vous remplacez onCreateAnimation par votre propre animation personnalisée. Vous pouvez l'obtenir et définir une sorte de paramètre en fonction de ce que vous voulez. Il faudra peut-être un peu plus de travail pour le faire fonctionner avec des fragments réguliers. 

Voici l'exemple où je le remplace et modifie la durée définie:

@Override
public Animation onCreateAnimation(int transit, boolean enter, int nextAnim) {
    Animation anim = (Animation) super.onCreateAnimation(transit, enter, nextAnim);
    if(!enter) {
        if(anim != null) {
            anim.setDuration(0); // This doesn't seem to be called.
            return anim;
        } else {
            Animation test = new TestAnimation();
            test.setDuration(0);
            return test;
        }
    }
    return anim;
}

private class TestAnimation extends Animation {

}
6
Warpzit

L'utilisateur est sur l'écran de démarrage avec le fragment racine

Disons que le fragment de racine est contenu dans l'activité A.

Il sélectionne un élément sur le fragment racine qui affiche ensuite un nouveau fragment pour afficher les détails de cet élément. Pour ce faire, il utilise une transaction de fragment qui définit les animations pour les cas Push et Pop (si l'utilisateur appuie sur le bouton Précédent, la transition s'anime)

La transaction est ajoutée à la pile arrière. Ce qui signifie que lorsque vous appuyez sur le bouton Précédent à partir d'un fragment de détail, le processus de déclenchement est animé.

À partir de ce fragment, il commence une activité qui (pour une raison quelconque) supprime l'élément qui vient d'être montré.

Disons que c'est l'activité B

Une fois cette activité terminée, je voudrais revenir au fragment racine sans afficher "l'animation pop" du "fragment de détail".

Une façon d’obtenir ce comportement est de le faire dans l’activité B:

Intent intent = new Intent(this, A.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); 
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
finish();

Ceci démarrera l’activité A en le réinitialisant à son état racine conformément à la documentation . ... ")

Avec cette configuration, l'animation sera présente dans le cas par défaut tandis que dans le cas particulier, vous pouvez contrôler l'animation à l'aide de:

intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);

Qui commence une nouvelle activité sans aucune animation. Si vous voulez une animation, vous pouvez le faire en utilisant la méthode overridePendingTransition.

5
500865

Donc, je voudrais suggérer un petit changement à la réponse de @ Geoff.

Au lieu d'avoir un booléen statique global, je préfère en avoir un local non statique. C'est ce que je suis venu avec.

Créer une interface

public interface TransitionAnimator {
    void disableTransitionAnimation();
    void enableTransitionAnimation();
}

Faites que le fragment implémente cette interface.

public class MyFragment extends Fragment implements TransitionAnimator {

    private boolean mTransitionAnimation;

    @Override
    public void disableTransitionAnimation() {
        mTransitionAnimation = false;
    }

    @Override
    public void enableTransitionAnimation() {
        mTransitionAnimation = true;
    }

    @Override
    public Animation onCreateAnimation(int transit, boolean enter, int nextAnim) {
        Animation result;
        if (!mTransitionAnimation) {
            Animation dummyAnimation = new Animation() {
            };
            dummyAnimation.setDuration(0);
            result = dummyAnimation;
        } else {
            result = super.onCreateAnimation(transit, enter, nextAnim);
        }
        return result;
    }

Et puis, quand vous voulez désactiver les animations de transition pour un fragment, faites juste

if (fragment instanceof TransitionAnimator) {
    ((TransitionAnimator) fragment).disableTransitionAnimation();
}

pour les activer, il suffit de faire

if (fragment instanceof TransitionAnimator) {
    ((TransitionAnimator) fragment).enableTransitionAnimation();
}

Si vous voulez faire la même chose pour tous les fragments du gestionnaire de fragments, faites simplement

List<Fragment> fragments = getSupportFragmentManager().getFragments();
for (Fragment fragment : fragments) {
    if (fragment instanceof TransitionAnimator) {
        // disable animations
        ((TransitionAnimator) fragment).disableTransitionAnimation();
    }
}

Très similaire, mais sans champs statiques.

1
Joao Sousa

Android dispose actuellement d'un moyen de le faire sans que le travail autour de @Geoff ne soit répondu.

Pour éviter que l'animation ne s'exécute sur popBackStack(), lorsque vous gonflez votre fragments, ajoutez .setReorderingAllowed(true) à votre fragmentTransaction.

Donc par exemple:

supportFragmentTransaction.beginTransaction()
    .setReorderingAllowed(true)
    .addToBackStack(null)
    .setCustomAnimations(
        Android.R.anim.fade_in,
        Android.R.anim.fade_out,
        Android.R.anim.fade_in,
        Android.R.anim.fade_out
    )
    .replace(yourContainer.id, yourFragment)
    .commit()

Vous remarquerez que si vous définissez setReorderingAllowed(true), l'animation pop ne serait plus lue. Les résultats sont en fait similaires au résultat de la réponse de @ Geoff.

0

C’est une suite de l’excellente réponse de @ Geoff, mais qui convient à un scénario plus dynamique et plus réaliste.

J'imaginais que c'était un joli petit billet, mais je réalise maintenant que ça a un peu dérapé. Cependant, tout le code est là et je le trouve vraiment utile, même s'il couvre bien plus que la simple désactivation des animations de transition.

D'habitude, lorsque je travaille avec des fragments, j'aime bien avoir un BaseFragment qui s'attache à un BaseActivityCallback. Mes Fragments peuvent utiliser BaseActivityCallback pour ajouter un nouveau fragment sur lui-même, ou même pour faire apparaître des fragments en dessous, d'où le désir de désactiver les animations pop - ou pop en silence :

interface BaseActivityCallback
{
    void addFragment ( BaseFragment f, int containerResId );
    void popFragment ( boolean silently );
}

class BaseActivity extends Android.support.v4.app.FragmentActivity implements BaseActivityCallback
{
    public void addFragment ( BaseFragment f, int containerResId )
    {
        FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
        ft.setCustomAnimations(R.anim.enter, R.anim.exit, R.anim.enter, R.anim.pop_exit); // http://stackoverflow.com/a/17488542/2412477
        ft.addToBackStack(DEFAULT_FRAGMENT_STACK_NAME);
        ft.replace(containerResId, fragment);
        ft.commitAllowingStateLoss();
    }        

    public void popFragment ( boolean silently )
    {
        FragmentManager fm = getSupportFragmentManager();                
        if ( silently ) {
            int count = fm.getFragments().size();
            BaseFragment f = (BaseFragment)fm.getFragments().get(count-1);
            f.setDisableTransitionAnimations(true);
        }
        fm.popBackStackImmediate();
    }
}

public abstract class BaseFragment extends Android.support.v4.app.Fragment
{
    private static final String TAG = "BaseFragment";
    private final String STATE_DISABLE_TRANSITION_ANIMATIONS = TAG+".stateDisableTransitionAnimations";

    protected BaseActivityCallback baseActivityCallback;
    private boolean disableTransitionAnimations;    

    @Override
    public void onCreate ( @Nullable Bundle savedInstanceState )
    {
        super.onCreate(savedInstanceState);
        disableTransitionAnimations = (savedInstanceState==null ? false : savedInstanceState.getBoolean(STATE_DISABLE_TRANSITION_ANIMATIONS, false));
    }

    @Override
    public void onAttach ( Context context )
    {
        super.onAttach(context);
        baseActivityCallback = (BaseActivityCallback)context;
    }

    @Override
    public void onSaveInstanceState ( Bundle outState )
    {
        super.onSaveInstanceState(outState);
        outState.putBoolean(STATE_DISABLE_TRANSITION_ANIMATIONS, disableTransitionAnimations);
    }

    @Override
    public Animation onCreateAnimation ( int transit, boolean enter, int nextAnim )
    {
        if ( disableTransitionAnimations ) {
            Animation nop = new Animation(){};
            nop.setDuration(0);
            return nop;
        }
        return super.onCreateAnimation(transit, enter, nextAnim);
    }

    public void setDisableTransitionAnimations ( boolean disableTransitionAnimations )
    {
        this.disableTransitionAnimations = disableTransitionAnimations; // http://stackoverflow.com/a/11253987/2412477
    }
}

Maintenant, vous pouvez créer votre MainActivity et lui montrer un Fragment1 qui peut ajouter un autre Fragment2 qui peut à son tour faire apparaître Fragment1 silencieusement :

public class MainActivity extends BaseActivity
{
    protected void onCreate ( Bundle savedInstanceState )
    {
        setContentView(R.layout.main_activity);
        ...
        if ( getSupportFragmentManager().getFragments() != null && !getSupportFragmentManager().getFragments().isEmpty() ) {

            addFragment( FragmentA.newInstance(), R.id.main_activity_fragment_container );
        }
    }
    ...
}

public class FragmentA extends BaseFragment
{
    public View onCreateView ( LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState )
    {
        ViewGroup root = (ViewGroup)inflater.inflate(R.layout.fragment_a, container, false);
        ...
        root.findViewById(R.id.fragment_a_next_button)
            .setOnClickListener( new View.OnClickListener() {
                 public void onClick ( View v ) {
                      baseActivityCallback.addFragment( FragmentB.newInstance(), R.id.main_activity_fragment_container );
                 }
             });
    }
}

public class FragmentB extends BaseFragment
{
    public View onCreateView ( LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState )
    {
        ViewGroup root = (ViewGroup)inflater.inflate(R.layout.fragment_b, container, false);
        ...
        root.findViewById(R.id.fragment_b_pop_silently_button)
            .setOnClickListener( new View.OnClickListener() {
                 public void onClick ( View v ) {
                      baseActivityCallback.popFragment( true );
                 }
             });
    }
}
0
Stephan Henningsen

Remplacez ceci dans le fragment que vous voulez faire apparaître sans animation et conservez l'animation quand vous entrez

@Override
public Animation onCreateAnimation(int transit, boolean enter, int nextAnim) {
    if(!enter){
        Animation a = new Animation() {};
        a.setDuration(0);
        return a;
    }
    return super.onCreateAnimation(transit, enter, nextAnim);
}
0
Linh

Utilisez simplement une autre méthode surchargée de setCustomAnimation() et dans laquelle ne définissez pas le paramètre R.anim.slide_out Cela résoudra le problème

À votre santé :)

0
Deepak Goel

Solution plus facile:

for (fragment in supportFragmentManager.fragments) {
    removeFragment(fragment)
}
if (supportFragmentManager.backStackEntryCount > 0) {
    supportFragmentManager.popBackStackImmediate(null, FragmentManager.POP_BACK_STACK_INCLUSIVE)
}
0
Javier Antón

Ceci est assez facile à réaliser avec overridePendingTransition(int enterAnim, int exitAnim) avec les deux 0 sans animation.

FragmentManager fm = getSupportFragmentManager();
if (fm.getBackStackEntryCount() > 0) {
    fm.popBackStack();
    overridePendingTransition(0, 0);
}
0
Markus Rubey

Avant de répondre à votre question, je dois poser une question moi-même.

Dans la méthode onBackPressed () de la deuxième activité, pouvez-vous accéder au backstack de la première activité?

Si c'est le cas, vous pouvez appeler popBackStackImmediate (String trnaisiotnName, int inclus) et cela supprimera la transition de fragment du backstack, sans que vous ayez à vous soucier des animations.

Je suppose que vous pouvez accéder au backstack de l'activité précédente, sinon cela ne fonctionnera pas

0
ufdeveloper