web-dev-qa-db-fra.com

Les transitions d'éléments partagés de fragments ne fonctionnent pas avec ViewPager

Mon application contient une vue qui consiste en un ViewPager composé d'une poignée de fragments. Lorsque vous cliquez sur un élément dans l'un de ces fragments, le comportement attendu est que l'élément partagé (dans ce cas, une image) passe au fragment qui affiche plus d'informations sur le contenu cliqué.

Voici une vidéo très simple de ce à quoi cela devrait ressembler:

https://dl.dropboxusercontent.com/u/97787025/device-2015-06-03-114842.mp4

Il s'agit simplement d'utiliser une transition Fragment-> Fragment.

Le problème se pose lorsque vous placez le fragment de départ dans un ViewPager. Je soupçonne que cela est dû au fait que le ViewPager utilise le gestionnaire de fragments enfants de son fragment parent, qui est différent du gestionnaire de fragments de l'activité, qui gère la transaction de fragment. Voici une vidéo de ce qui se passe:

https://dl.dropboxusercontent.com/u/97787025/device-2015-06-03-120029.mp4

Je suis presque certain que le problème ici, comme je l'ai expliqué ci-dessus, est le gestionnaire de fragments enfant vs le gestionnaire de fragments de l'activité. Voici comment je fais la transition:

SimpleFragment fragment = new SimpleFragment();

FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.am_list_pane, fragment, fragment.getClass().getSimpleName());

if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.Lollipop) {
    TransitionSet enterTransition = new TransitionSet();
    enterTransition.addTransition(new ChangeBounds());
    enterTransition.addTransition(new ChangeClipBounds());
    enterTransition.addTransition(new ChangeImageTransform());
    enterTransition.addTransition(new ChangeTransform());

    TransitionSet returnTransition = new TransitionSet();
    returnTransition.addTransition(new ChangeBounds());
    returnTransition.addTransition(new ChangeClipBounds());
    returnTransition.addTransition(new ChangeImageTransform());
    returnTransition.addTransition(new ChangeTransform());

    fragment.setSharedElementEnterTransition(enterTransition);
    fragment.setSharedElementReturnTransition(returnTransition);

    transaction.addSharedElement(iv, iv.getTransitionName());
}

transaction.addToBackStack(fragment.getClass().getName());

transaction.commit();

Cela fonctionne bien lorsque les deux fragments sont gérés par le gestionnaire de fragments de l'activité, mais lorsque je charge un ViewPager comme celui-ci:

ViewPager pager = (ViewPager) view.findViewById(R.id.pager);
pager.setAdapter(new Adapter(getChildFragmentManager()));

Les enfants du ViewPager ne sont pas gérés par l'activité et cela ne fonctionne plus.

Est-ce un oubli de l'équipe Android? Y a-t-il un moyen de résoudre cela? Merci.

52
JMRboosties

Vous avez probablement déjà trouvé une réponse à cela, mais au cas où vous ne l'auriez pas fait, voici ce que j'ai fait pour y remédier après quelques heures à me gratter la tête.

Je pense que le problème est une combinaison de deux facteurs:

  • La charge de Fragments dans ViewPager avec un retard, ce qui signifie que l'activité retourne beaucoup plus rapidement que ses fragments qui sont contenus à l'intérieur de la ViewPager

  • Si vous êtes comme moi, les fragments enfants de votre ViewPager sont probablement du même type. Cela signifie qu'ils partagent tous le même nom de transition (si vous les avez définis dans votre présentation xml), sauf si vous les définissez dans le code et que vous ne le définissez qu'une seule fois, sur le fragment visible .

Pour résoudre ces deux problèmes, voici ce que j'ai fait:

1. Correction du problème de chargement retardé:

Dans votre activité (celle qui contient le ViewPager), ajoutez cette ligne après super.onCreate() et avant setContentView():

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

    ActivityCompat.postponeEnterTransition(this); // This is the line you need to add

    setContentView(R.layout.feeds_content_list_activity);
    ...
}

2. Correction du problème avec plusieurs fragments avec le même nom de transition:

Maintenant, il y a plusieurs façons de procéder, mais c'est ce que je me suis retrouvé dans mon activité "détail", c'est-à-dire l'activité qui contient le ViewPager (dans la onCreate() mais vous pouvez le faire n'importe où vraiment):

_viewPager.setAdapter(_sectionsPagerAdapter);
_viewPager.setCurrentItem(position);
...
...
_pagerAdapter.getItem(position).setTransitionName(getResources().getString(R.string.transition_contenet_topic));

Vous devez être prudent car le Activity n'est peut-être pas encore attaché à votre fragment ViewPager, il est donc plus facile de simplement passer le nom de transition de l'activité au fragment si vous le chargez depuis une ressource

L'implémentation réelle est aussi simple que vous attendez:

public void setTransitionName(String transitionName) {
    _transitionName = transitionName;
}

Puis dans la onViewCreated() de votre fragment, ajoutez cette ligne:

public void onViewCreated(View view, Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    ...
    if (_transitionName != null && Android.os.Build.VERSION.SDK_INT >= Android.os.Build.VERSION_CODES.Lollipop) {
        setTransitionNameLollipop();
    }
    ...
}

@TargetApi(Build.VERSION_CODES.Lollipop)
private void setTransitionNamesLollipop() {
    _imgTopic.setTransitionName(_transitionName);
}

La dernière pièce du puzzle consiste à savoir quand votre fragment est complètement chargé, puis à appeler ActivityCompat.startPostponedEnterTransition(getActivity());.

Dans mon cas, mes fragments n'ont été entièrement chargés que plus tard, car je charge la plupart des choses hors du thread d'interface utilisateur, ce qui signifie que je devais trouver un moyen d'appeler cela lorsque tout était chargé, mais si ce n'est pas votre cas, vous pouvez appeler cela juste après avoir appelé setTransitionNameLollipop().

Le problème avec cette approche est que la transition de sortie peut ne pas fonctionner sauf si vous êtes très prudent et réinitialisez le nom de la transition sur le fragment "visible" juste avant de exit l'activité pour revenir en arrière. Cela peut facilement se faire comme suit:

  1. Écoutez le changement de page sur votre ViewPager
  2. Supprimez le (s) nom (s) de transition dès que votre fragment est hors de vue
  3. Définissez le (s) nom (s) de transition sur le fragment visible.
  4. Au lieu d'appeler finish(), appelez ActivityCompat.finishAfterTransition(activity);

Cela peut devenir très complexe très rapidement si vous avez besoin d'une transition de retour vers un RecyclerView ce qui était mon cas. Pour cela, il y a une bien meilleure réponse fournie par @Alex Lockwood ici: ViewPager Fragments - Shared Element Transitions qui a un exemple de code très bien écrit (bien que beaucoup plus compliqué que ce que je viens d'écrire) ici: https://github.com/alexjlockwood/activity-transitions/tree/master/app/src/main/Java/com/alexjlockwood/activity/transitions

Dans mon cas, je n'ai pas eu à aller jusqu'à mettre en œuvre sa solution et la solution ci-dessus que j'ai publiée a fonctionné pour mon cas.

Dans le cas où vous avez plusieurs éléments partagés, je suis sûr que vous pouvez comprendre comment étendre les méthodes afin de les satisfaire.

36
kha

Sur l'activité supportPostponeEnterTransition();

Et lorsque vos fragments sont chargés (essayez de les synchroniser, peut-être avec EventBus ou autre)

startPostponedEnterTransition();

Se référer à cet exemple

http://www.androiddesignpatterns.com/2015/03/activity-postponed-shared-element-transitions-part3b.html

1
florent champigny

Je me suis cogné la tête contre le mur avec celui-ci récemment. Tout ce que je voulais, c'était un fragment dans un ViewPager pour lancer un autre fragment avec une transition d'élément partagé de type carte d'extension Nice. Aucune des suggestions ci-dessus n'a fonctionné pour moi, j'ai donc décidé d'essayer de lancer une activité comme un dialogue:

<style name="AppTheme.CustomDialog" parent="MyTheme">
    <item name="Android:windowIsTranslucent">true</item>
    <item name="Android:windowBackground">@Android:color/transparent</item>
    <item name="Android:windowContentOverlay">@null</item>
    <item name="Android:windowNoTitle">true</item>
    <item name="Android:backgroundDimEnabled">true</item>
</style>

Ensuite, pour lancer l'activité à partir du fragment:

Intent intent = new Intent(getContext(), MyDialogActivity.class);
ActivityOptionsCompat options = ActivityOptionsCompat
                        .makeSceneTransitionAnimation(getActivity(),
                                Pair.create(sharedView, "target_transition"));
ActivityCompat.startActivity(getActivity(), intent, options.toBundle());

Dans la disposition Fragment, mettez Android: transition_name sur le sharedView et dans la disposition Activity, Android: transition_name = "target_transition" (identique au deuxième argument Pair.create ()).

0
Mark