Exemple de travail complet du scénario d'animation en trois fragments de Gmail?


128

TL; DR: Je recherche un exemple de travail complet de ce que j'appellerai le scénario "l'animation à trois fragments de Gmail". Plus précisément, nous voulons commencer par deux fragments, comme ceci:

deux fragments

Lors d'un événement d'interface utilisateur (par exemple, en tapant sur quelque chose dans le fragment B), nous voulons:

  • Fragment A pour glisser hors de l'écran vers la gauche
  • Fragment B pour glisser vers le bord gauche de l'écran et rétrécir pour reprendre la place libérée par le fragment A
  • Fragment C à glisser depuis le côté droit de l'écran et à occuper la place libérée par le Fragment B

Et, en appuyant sur le bouton BACK, nous voulons que cet ensemble d'opérations soit inversé.

Maintenant, j'ai vu beaucoup d'implémentations partielles; J'en passerai en revue quatre ci-dessous. En plus d'être incomplets, ils ont tous leurs problèmes.


@Reto Meier a contribué cette réponse populaire à la même question de base, indiquant que vous utiliseriez setCustomAnimations()avec un FragmentTransaction. Pour un scénario à deux fragments (par exemple, vous ne voyez que le fragment A au départ et que vous voulez le remplacer par un nouveau fragment B utilisant des effets animés), je suis entièrement d'accord. Toutefois:

  • Étant donné que vous ne pouvez spécifier qu'une seule animation "d'entrée" et une "sortie", je ne vois pas comment vous géreriez toutes les différentes animations requises pour le scénario à trois fragments
  • Le <objectAnimator>dans son exemple de code utilise des positions câblées en pixels, ce qui semblerait peu pratique étant donné les différentes tailles d'écran, mais setCustomAnimations()nécessite des ressources d'animation, excluant la possibilité de définir ces choses en Java
  • Je ne sais pas comment les animateurs d'objets pour l'échelle sont liés à des choses comme android:layout_weightdans une LinearLayoutallocation d'espace en pourcentage
  • Je suis à une perte sur la façon dont le fragment C est traitée au départ ( GONE? android:layout_weightDe 0? Pré-animation à une échelle de 0? Autre chose?)

@Roman Nurik souligne que vous pouvez animer n'importe quelle propriété , y compris celles que vous définissez vous-même. Cela peut aider à résoudre le problème des positions câblées, au prix de l'invention de votre propre sous-classe de gestionnaire de mise en page personnalisée. Cela aide certains, mais je suis toujours déconcerté par le reste de la solution de Reto.


L'auteur de cette entrée pastebin montre un pseudo-code alléchant, disant essentiellement que les trois fragments résideraient initialement dans le conteneur, le fragment C étant caché au départ via une hide()opération de transaction. Nous avons ensuite show()C et hide()A lorsque l'événement UI se produit. Cependant, je ne vois pas comment cela gère le fait que B change de taille. Cela repose également sur le fait que vous pouvez apparemment ajouter plusieurs fragments au même conteneur, et je ne suis pas sûr que ce comportement soit fiable ou non sur le long terme (pour ne pas mentionner que cela devrait casser findFragmentById(), même si je peux vivre avec cela).


L'auteur de ce billet de blog indique que Gmail n'utilise pas setCustomAnimations()du tout, mais utilise directement des animateurs d'objets ("il suffit de changer la marge gauche de la vue racine + de changer la largeur de la vue droite"). Cependant, il s'agit toujours d'une solution AFAICT à deux fragments, et la mise en œuvre a montré une fois de plus les dimensions des fils durs en pixels.


Je vais continuer à me débrouiller à ce sujet, donc je finirai peut-être par y répondre moi-même un jour, mais j'espère vraiment que quelqu'un a élaboré la solution à trois fragments pour ce scénario d'animation et pourra publier le code (ou un lien vers celui-ci). Les animations sous Android me donnent envie de m'arracher les cheveux, et ceux d'entre vous qui m'ont vu savent que c'est une entreprise largement infructueuse.


2
N'est-ce pas quelque chose comme ça posté par Romain Guy? curious-creature.org/2011/02/22/…
Fernando Gallego

1
@ ferdy182: Il n'utilise pas de fragments AFAICT. Visuellement, c'est le bon effet de base, et je peux peut-être l'utiliser comme un autre point de données pour essayer de créer une réponse basée sur des fragments. Merci!
CommonsWare

1
Juste une pensée: l'application de messagerie standard a un comportement similaire (mais pas complètement identique) sur mon Nexus 7 - qui devrait être open source.
Christopher Orr

4
L'application Email utilise un «ThreePaneLayout» personnalisé qui anime la position de la vue de gauche et les largeurs / visibilités des deux autres vues, chacune contenant le fragment correspondant.
Christopher Orr

2
hey @CommonsWare Je ne dérangerai pas les gens avec ma réponse complète ici, mais il y avait une question similaire à la vôtre dans laquelle j'ai donné une réponse très satisfaisante. Alors, suggérez-vous simplement de le vérifier (vérifiez également les commentaires): stackoverflow.com/a/14155204/906362
Budius

Réponses:


67

J'ai téléchargé ma proposition sur github (fonctionne avec toutes les versions d'Android bien que l'accélération matérielle de la vue soit fortement recommandée pour ce type d'animations. Pour les appareils non accélérés par le matériel, une mise en cache bitmap devrait mieux convenir)

La vidéo de démonstration avec l'animation est arrivée (la fréquence d'images lente est la cause de la diffusion d'écran. Les performances réelles sont très rapides)


Usage:

layout = new ThreeLayout(this, 3);
layout.setAnimationDuration(1000);
setContentView(layout);
layout.getLeftView();   //<---inflate FragmentA here
layout.getMiddleView(); //<---inflate FragmentB here
layout.getRightView();  //<---inflate FragmentC here

//Left Animation set
layout.startLeftAnimation();

//Right Animation set
layout.startRightAnimation();

//You can even set interpolators

Explication :

Création d'un nouveau RelativeLayout personnalisé (ThreeLayout) et de 2 animations personnalisées (MyScalAnimation, MyTranslateAnimation)

ThreeLayoutobtient le poids du volet gauche comme paramètre, en supposant que l'autre vue visible a weight=1.

Crée donc new ThreeLayout(context,3)une nouvelle vue avec 3 enfants et le volet gauche avec 1/3 de l'écran total. L'autre vue occupe tout l'espace disponible.

Il calcule la largeur à l'exécution, une implémentation plus sûre est que les dimensions sont calculées pour la première fois dans draw (). au lieu de dans post ()

Les animations de mise à l'échelle et de traduction redimensionnent et déplacent la vue et non pseudo- [redimensionner, déplacer]. Notez que ce fillAfter(true)n'est utilisé nulle part.

View2 est à droite de View1

et

View3 est à droite_of View2

Après avoir défini ces règles, RelativeLayout s'occupe de tout le reste. Les animations modifient margins(en mouvement) et [width,height]à l'échelle

Pour accéder à chaque enfant (afin que vous puissiez le gonfler avec votre Fragment, vous pouvez appeler

public FrameLayout getLeftLayout() {}

public FrameLayout getMiddleLayout() {}

public FrameLayout getRightLayout() {}

Ci-dessous sont présentés les 2 animations


Étape 1

--- Écran IN ----------! ----- OUT ----

[Vue1] [_____ Vue2 _____] [_____ Vue3_____]

Étape 2

--OUT -! -------- Écran IN ------

[Vue1] [Vue2] [_____ Vue3_____]


1
Bien que je n’ai pas encore implémenté d’animateur d’échelle, je crains que cela fonctionne bien pour les champs de couleur solides et moins bien sur, dirons-nous, du contenu «réel». Les résultats du redimensionnement du fragment B pour s'adapter à l'espace du fragment A ne devraient pas être différents de ceux du fragment B qui y avait été placé à l'origine. Par exemple, AFAIK, un animateur d'échelle redimensionnera la taille de la police (en dessinant tout dans l'animation Viewplus petite), mais dans Gmail / Email, nous n'obtenons pas de polices plus petites, juste moins de mots.
CommonsWare

1
@CommonsWare scaleAnimation n'est qu'un nom abstrait. En fait, aucune mise à l'échelle n'a lieu. Il redimensionne en fait la largeur de la vue. Vérifiez la source. LayoutResizer pourrait être un meilleur nom pour cela.
lowwire

1
Ce n'est pas mon interprétation de la source autour de la scaleXvaleur View, bien que je puisse mal interpréter les choses.
CommonsWare

1
@CommonsWare consultez ce github.com/weakwire/3PaneLayout/blob/master/src/com/example/… out. La seule raison pour laquelle j'étends l'animation est d'avoir accès à interpolatedTime. p.width = largeur (int); fait toute la magie et ce n'est pas scaleX. Ce sont les dimensions réelles de la vue qui changent pendant le temps spécifié.
lowwire

2
Votre vidéo montre un bon framerate, mais les vues utilisées dans votre exemple sont très simples. Faire un requestLayout () pendant une animation est très coûteux. Surtout si les enfants sont complexes (un ListView par exemple). Cela entraînera probablement une animation saccadée. L'application GMail n'appelle pas requestLayout. Au lieu de cela, une autre vue plus petite est placée dans le panneau du milieu juste avant le début de l'animation.
Thierry-Dimitri Roy

31

OK, voici ma propre solution, dérivée de l'application Email AOSP, selon la suggestion de @ Christopher dans les commentaires de la question.

https://github.com/commonsguy/cw-omnibus/tree/master/Animation/ThreePane

La solution de @ lowwire rappelle la mienne, bien qu'il utilise des classiques Animationplutôt que des animateurs, et qu'il utilise des RelativeLayoutrègles pour appliquer le positionnement. Du point de vue de la prime, il obtiendra probablement la prime, à moins que quelqu'un d'autre avec une solution plus astucieuse ne publie encore une réponse.


En un mot, le ThreePaneLayoutdans ce projet est une LinearLayoutsous - classe, conçue pour travailler en paysage avec trois enfants. Les largeurs de ces enfants peuvent être définies dans le format XML de mise en page, par tous les moyens souhaités - je montre en utilisant des poids, mais vous pouvez avoir des largeurs spécifiques définies par des ressources de dimension ou autre. Le troisième enfant - Fragment C dans la question - doit avoir une largeur de zéro.

package com.commonsware.android.anim.threepane;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.widget.LinearLayout;

public class ThreePaneLayout extends LinearLayout {
  private static final int ANIM_DURATION=500;
  private View left=null;
  private View middle=null;
  private View right=null;
  private int leftWidth=-1;
  private int middleWidthNormal=-1;

  public ThreePaneLayout(Context context, AttributeSet attrs) {
    super(context, attrs);
    initSelf();
  }

  void initSelf() {
    setOrientation(HORIZONTAL);
  }

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

    left=getChildAt(0);
    middle=getChildAt(1);
    right=getChildAt(2);
  }

  public View getLeftView() {
    return(left);
  }

  public View getMiddleView() {
    return(middle);
  }

  public View getRightView() {
    return(right);
  }

  public void hideLeft() {
    if (leftWidth == -1) {
      leftWidth=left.getWidth();
      middleWidthNormal=middle.getWidth();
      resetWidget(left, leftWidth);
      resetWidget(middle, middleWidthNormal);
      resetWidget(right, middleWidthNormal);
      requestLayout();
    }

    translateWidgets(-1 * leftWidth, left, middle, right);

    ObjectAnimator.ofInt(this, "middleWidth", middleWidthNormal,
                         leftWidth).setDuration(ANIM_DURATION).start();
  }

  public void showLeft() {
    translateWidgets(leftWidth, left, middle, right);

    ObjectAnimator.ofInt(this, "middleWidth", leftWidth,
                         middleWidthNormal).setDuration(ANIM_DURATION)
                  .start();
  }

  public void setMiddleWidth(int value) {
    middle.getLayoutParams().width=value;
    requestLayout();
  }

  private void translateWidgets(int deltaX, View... views) {
    for (final View v : views) {
      v.setLayerType(View.LAYER_TYPE_HARDWARE, null);

      v.animate().translationXBy(deltaX).setDuration(ANIM_DURATION)
       .setListener(new AnimatorListenerAdapter() {
         @Override
         public void onAnimationEnd(Animator animation) {
           v.setLayerType(View.LAYER_TYPE_NONE, null);
         }
       });
    }
  }

  private void resetWidget(View v, int width) {
    LinearLayout.LayoutParams p=
        (LinearLayout.LayoutParams)v.getLayoutParams();

    p.width=width;
    p.weight=0;
  }
}

Cependant, au moment de l'exécution, quelle que soit la configuration initiale des largeurs, la gestion de la largeur est prise en charge ThreePaneLayoutla première fois que vous utilisez hideLeft()pour passer de l'affichage de ce que la question appelle les fragments A et B aux fragments B et C. Dans la terminologie de ThreePaneLayout- qui n'a pas de lien spécifique à des fragments - les trois pièces sont left, middleet right. Au moment où vous appelez hideLeft(), nous enregistrons les tailles leftet middleet nous mettons à zéro tous les poids qui ont été utilisés sur l'un des trois, afin que nous puissions contrôler complètement les tailles. Au moment de hideLeft(), nous définissons la taille de rightcomme étant la taille d'origine de middle.

Les animations sont doubles:

  • Utilisez a ViewPropertyAnimatorpour effectuer une translation des trois widgets vers la gauche de la largeur de left, en utilisant une couche matérielle
  • Utilisez une ObjectAnimatorsur une pseudo-propriété personnalisée de middleWidthpour changer la middlelargeur de ce avec quoi il a commencé à la largeur d'origine deleft

(il est possible que ce soit une meilleure idée d'utiliser un AnimatorSetet ObjectAnimatorspour tous ceux-ci, bien que cela fonctionne pour le moment)

(il est également possible que le middleWidth ObjectAnimatornie la valeur de la couche matérielle, car cela nécessite une invalidation assez continue)

(il est certainement possible que j'aie encore des lacunes dans ma compréhension d'animation et que j'aime les déclarations entre parenthèses)

L'effet net est que leftglisse hors de l'écran, middleglisse à la position et à la taille d'origine leftet se righttraduit juste derrière middle.

showLeft() inverse simplement le processus, avec le même mélange d'animateurs, juste avec les directions inversées.

L'activité utilise a ThreePaneLayoutpour contenir une paire de ListFragmentwidgets et a Button. Sélectionner quelque chose dans le fragment de gauche ajoute (ou met à jour le contenu) du fragment du milieu. Sélectionner quelque chose dans le fragment du milieu définit la légende du Button, plus s'exécute hideLeft()sur le ThreePaneLayout. Appuyer sur BACK, si nous avons caché le côté gauche, exécutera showLeft(); sinon, BACK quitte l'activité. Puisque cela ne sert pas FragmentTransactionsà affecter les animations, nous sommes bloqués à gérer nous-mêmes cette «pile arrière».

Le projet lié ci-dessus utilise des fragments natifs et le framework d'animation natif. J'ai une autre version du même projet qui utilise le backport des fragments de support Android et NineOldAndroids pour l'animation:

https://github.com/commonsguy/cw-omnibus/tree/master/Animation/ThreePaneBC

Le backport fonctionne bien sur un Kindle Fire de 1ère génération, bien que l'animation soit un peu saccadée étant donné les spécifications matérielles inférieures et le manque de prise en charge de l'accélération matérielle. Les deux implémentations semblent fluides sur un Nexus 7 et d'autres tablettes de la génération actuelle.

Je suis certainement ouvert aux idées sur la façon d'améliorer cette solution, ou d'autres solutions qui offrent des avantages évidents par rapport à ce que j'ai fait ici (ou à ce que @weakwire a utilisé).

Merci encore à tous ceux qui ont contribué!


1
Salut! Merci pour la solution, mais je pense que l'ajout de v.setLayerType () rend parfois les choses un peu plus lentes. Supprimez simplement ces lignes (et Listener aussi) C'est au moins vrai pour les appareils Android 3+ que je viens d'essayer sur Galaxy Nexus et sans ces lignes, cela fonctionne beaucoup mieux
Evgeny Nacu

1
"Les deux implémentations semblent fluides sur un Nexus 7 et d'autres tablettes de la génération actuelle." Je te crois; mais je teste votre implémentation avec ObjectAnimator natif sur un Nexus 7 et il est un peu en retard. Quelles seraient les causes les plus probables? J'ai déjà hardwareAccelerated: true dans AndroidMAnifest. Je ne sais vraiment pas quel pourrait être le problème. J'ai également essayé la variante de DanielGrech et elle est à la traîne. Je peux voir des différences dans l'animation (son animation est basée sur des poids), mais je ne vois pas pourquoi elle serait en retard dans les deux cas.
Bogdan Zurac

1
@Andrew: "ça traîne un peu" - Je ne sais pas comment vous utilisez le verbe "lag" ici. Pour moi, le «retard» implique une cible concrète de comparaison. Ainsi, par exemple, une animation liée à un mouvement de balayage peut être en retard sur le mouvement du doigt. Dans ce cas, tout est déclenché par des robinets, et je ne sais donc pas ce que l'animation pourrait être en retard. En supposant que peut-être «retarde un peu» signifie simplement «se sent lent», je n'ai pas vu cela, mais je ne prétends pas avoir un sens esthétique fort, et donc ce qui pourrait être une animation acceptable pour moi pourrait être inacceptable pour vous.
CommonsWare

2
@CommonsWare: J'ai compilé votre code avec 4.2, excellent travail. Lorsque j'ai tapoté sur le ListViewbouton du milieu et appuyé rapidement sur le bouton arrière et répété, toute la mise en page a dérivé vers la droite. Il a commencé à afficher une colonne d'espace supplémentaire à gauche. Ce comportement a été introduit parce que je ne laisserais pas la première animation (commencée par un appui sur le milieu ListView) se terminer et j'ai appuyé sur le bouton de retour. Je l'ai corrigé en remplaçant translationXBypar translationXin translateWidgetsmethod et translateWidgets(leftWidth, left, middle, right);par translateWidgets(0, left, middle, right);in showLeft()method.
M-WaJeEh

2
J'ai également remplacé requestLayout();par middle.requestLayout();dans la setMiddleWidth(int value);méthode. Cela peut ne pas affecter les performances car je suppose que le GPU fera toujours une mise en page complète, des commentaires?
M-WaJeEh

13

Nous avons construit une bibliothèque appelée PanesLibrary qui résout ce problème. C'est encore plus flexible que ce qui était proposé auparavant car:

  • Chaque volet peut être dimensionné dynamiquement
  • Il permet un nombre illimité de volets (pas seulement 2 ou 3)
  • Les fragments à l'intérieur des volets sont correctement conservés lors des changements d'orientation.

Vous pouvez le vérifier ici: https://github.com/Mapsaurus/Android-PanesLibrary

Voici une démo: http://www.youtube.com/watch?v=UA-lAGVXoLU&feature=youtu.be

Il vous permet essentiellement d'ajouter facilement n'importe quel nombre de volets de taille dynamique et d'attacher des fragments à ces volets. J'espère que vous le trouverez utile! :)


6

En vous basant sur l'un des exemples auxquels vous avez lié ( http://android.amberfog.com/?p=758 ), que diriez-vous d'animer la layout_weightpropriété? De cette façon, vous pouvez animer le changement de poids des 3 fragments ensemble, ET vous obtenez le bonus qu'ils glissent tous bien ensemble:

Commencez par une mise en page simple. Puisque nous allons animer layout_weight, nous avons besoin d'une LinearLayoutvue racine pour les 3 panneaux:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/container"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">

<LinearLayout
    android:id="@+id/panel1"
    android:layout_width="0dip"
    android:layout_weight="1"
    android:layout_height="match_parent"/>

<LinearLayout
    android:id="@+id/panel2"
    android:layout_width="0dip"
    android:layout_weight="2"
    android:layout_height="match_parent"/>

<LinearLayout
    android:id="@+id/panel3"
    android:layout_width="0dip"
    android:layout_weight="0"
    android:layout_height="match_parent"/>
</LinearLayout>

Puis le cours de démonstration:

public class DemoActivity extends Activity implements View.OnClickListener {
    public static final int ANIM_DURATION = 500;
    private static final Interpolator interpolator = new DecelerateInterpolator();

    boolean isCollapsed = false;

    private Fragment frag1, frag2, frag3;
    private ViewGroup panel1, panel2, panel3;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        panel1 = (ViewGroup) findViewById(R.id.panel1);
        panel2 = (ViewGroup) findViewById(R.id.panel2);
        panel3 = (ViewGroup) findViewById(R.id.panel3);

        frag1 = new ColorFrag(Color.BLUE);
        frag2 = new InfoFrag();
        frag3 = new ColorFrag(Color.RED);

        final FragmentManager fm = getFragmentManager();
        final FragmentTransaction trans = fm.beginTransaction();

        trans.replace(R.id.panel1, frag1);
        trans.replace(R.id.panel2, frag2);
        trans.replace(R.id.panel3, frag3);

        trans.commit();
    }

    @Override
    public void onClick(View view) {
        toggleCollapseState();
    }

    private void toggleCollapseState() {
        //Most of the magic here can be attributed to: http://android.amberfog.com/?p=758

        if (isCollapsed) {
            PropertyValuesHolder[] arrayOfPropertyValuesHolder = new PropertyValuesHolder[3];
            arrayOfPropertyValuesHolder[0] = PropertyValuesHolder.ofFloat("Panel1Weight", 0.0f, 1.0f);
            arrayOfPropertyValuesHolder[1] = PropertyValuesHolder.ofFloat("Panel2Weight", 1.0f, 2.0f);
            arrayOfPropertyValuesHolder[2] = PropertyValuesHolder.ofFloat("Panel3Weight", 2.0f, 0.0f);
            ObjectAnimator localObjectAnimator = ObjectAnimator.ofPropertyValuesHolder(this, arrayOfPropertyValuesHolder).setDuration(ANIM_DURATION);
            localObjectAnimator.setInterpolator(interpolator);
            localObjectAnimator.start();
        } else {
            PropertyValuesHolder[] arrayOfPropertyValuesHolder = new PropertyValuesHolder[3];
            arrayOfPropertyValuesHolder[0] = PropertyValuesHolder.ofFloat("Panel1Weight", 1.0f, 0.0f);
            arrayOfPropertyValuesHolder[1] = PropertyValuesHolder.ofFloat("Panel2Weight", 2.0f, 1.0f);
            arrayOfPropertyValuesHolder[2] = PropertyValuesHolder.ofFloat("Panel3Weight", 0.0f, 2.0f);
            ObjectAnimator localObjectAnimator = ObjectAnimator.ofPropertyValuesHolder(this, arrayOfPropertyValuesHolder).setDuration(ANIM_DURATION);
            localObjectAnimator.setInterpolator(interpolator);
            localObjectAnimator.start();
        }
        isCollapsed = !isCollapsed;
    }

    @Override
    public void onBackPressed() {
        //TODO: Very basic stack handling. Would probably want to do something relating to fragments here..
        if(isCollapsed) {
            toggleCollapseState();
        } else {
            super.onBackPressed();
        }
    }

    /*
     * Our magic getters/setters below!
     */

    public float getPanel1Weight() {
        LinearLayout.LayoutParams params = (LinearLayout.LayoutParams)     panel1.getLayoutParams();
        return params.weight;
    }

    public void setPanel1Weight(float newWeight) {
        LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) panel1.getLayoutParams();
        params.weight = newWeight;
        panel1.setLayoutParams(params);
    }

    public float getPanel2Weight() {
        LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) panel2.getLayoutParams();
        return params.weight;
    }

    public void setPanel2Weight(float newWeight) {
        LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) panel2.getLayoutParams();
        params.weight = newWeight;
        panel2.setLayoutParams(params);
    }

    public float getPanel3Weight() {
        LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) panel3.getLayoutParams();
        return params.weight;
    }

    public void setPanel3Weight(float newWeight) {
        LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) panel3.getLayoutParams();
        params.weight = newWeight;
        panel3.setLayoutParams(params);
    }


    /**
     * Crappy fragment which displays a toggle button
     */
    public static class InfoFrag extends Fragment {
        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle     savedInstanceState) {
            LinearLayout layout = new LinearLayout(getActivity());
            layout.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
            layout.setBackgroundColor(Color.DKGRAY);

            Button b = new Button(getActivity());
            b.setOnClickListener((DemoActivity) getActivity());
            b.setText("Toggle Me!");

            layout.addView(b);

            return layout;
        }
    }

    /**
     * Crappy fragment which just fills the screen with a color
     */
    public static class ColorFrag extends Fragment {
        private int mColor;

        public ColorFrag() {
            mColor = Color.BLUE; //Default
        }

        public ColorFrag(int color) {
            mColor = color;
        }

        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
            FrameLayout layout = new FrameLayout(getActivity());
            layout.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));

            layout.setBackgroundColor(mColor);
            return layout;
        }
    }
}

De plus, cet exemple n'utilise pas FragmentTransactions pour réaliser les animations (il anime plutôt les vues auxquelles les fragments sont attachés), vous devrez donc faire toutes les transactions de backstack / fragment vous-même, mais par rapport à l'effort de faire fonctionner les animations gentiment, cela ne semble pas être un mauvais compromis :)

Horrible vidéo basse résolution en action: http://youtu.be/Zm517j3bFCo


1
Eh bien, Gmail fait définitivement une traduction de ce que je décris comme Fragment A. Je craindrais que réduire son poids à 0 puisse introduire des artefacts visuels (par exemple, TextViewen wrap_contents'étirant verticalement au fur et à mesure de l'animation). Cependant, il se peut que l'animation du poids soit la façon dont ils redimensionnent ce que j'ai appelé Fragment B. Merci!
CommonsWare

1
Pas de soucis! C'est un bon point cependant, cela provoquerait probablement des artefacts visuels en fonction des vues enfants dans le «fragment A». Espérons que quelqu'un trouve un moyen plus solide de le faire
DanielGrech

5

Cela n'utilise pas de fragments ... C'est une mise en page personnalisée avec 3 enfants. Lorsque vous cliquez sur un message, vous décalez les 3 enfants en utilisant offsetLeftAndRight()et un animateur.

Dans JellyBeanvous pouvez activer "Afficher les limites de mise en page" dans les paramètres "Options du développeur". Lorsque l'animation de la diapositive est terminée, vous pouvez toujours voir que le menu de gauche est toujours là, mais sous le panneau du milieu.

C'est similaire au menu de l'application Fly-in de Cyril Mottier , mais avec 3 éléments au lieu de 2.

De plus, le ViewPagerdes troisièmes enfants est une autre indication de ce comportement: ViewPagerutilise généralement des fragments (je sais qu'ils ne sont pas obligés, mais je n'ai jamais vu d'implémentation autre que Fragment), et puisque vous ne pouvez pas utiliser Fragmentsdans un autre Fragmentles 3 enfants ne sont probablement pas des fragments ...


Je n'ai jamais utilisé "Afficher les limites de mise en page" auparavant - c'est très utile pour les applications à source fermée comme celle-ci (merci!). Il semble que ce que j'ai décrit comme le fragment A se traduit hors écran (vous pouvez voir son bord droit glisser de droite à gauche), avant de revenir sous ce que j'ai décrit comme le fragment B.Cela ressemble à un TranslateAnimation, même si je suis sûr qu'il existe des moyens d'utiliser des animateurs pour accomplir les mêmes fins. Je vais devoir faire quelques expériences là-dessus. Merci!
CommonsWare

2

J'essaie actuellement de faire quelque chose comme ça, sauf que le fragment B est mis à l'échelle pour prendre l'espace disponible et que le volet 3 peut être ouvert en même temps s'il y a assez de place. Voici ma solution jusqu'à présent, mais je ne suis pas sûr si je vais m'en tenir à elle. J'espère que quelqu'un fournira une réponse montrant la bonne voie.

Au lieu d'utiliser un LinearLayout et d'animer le poids, j'utilise un RelativeLayout et j'anime les marges. Je ne suis pas sûr que ce soit le meilleur moyen car il nécessite un appel à requestLayout () à chaque mise à jour. C'est fluide sur tous mes appareils.

Donc, j'anime la mise en page, je n'utilise pas de transaction de fragments. Je gère le bouton de retour manuellement pour fermer le fragment C s'il est ouvert.

FragmentB utilise layout_toLeftOf / ToRightOf pour le maintenir aligné sur les fragments A et C.

Lorsque mon application déclenche un événement pour afficher le fragment C, je glisse le fragment C et je glisse le fragment A en même temps. (2 animations séparées). Inversement, lorsque le Fragment A s'ouvre, je ferme C en même temps.

En mode portrait ou sur un écran plus petit, j'utilise une disposition légèrement différente et je fais glisser le fragment C sur l'écran.

Pour utiliser le pourcentage pour la largeur des fragments A et C, je pense que vous devriez le calculer au moment de l'exécution ... (?)

Voici la mise en page de l'activité:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/rootpane"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">

    <!-- FRAGMENT A -->
    <fragment
        android:id="@+id/fragment_A"
        android:layout_width="300dp"
        android:layout_height="fill_parent"
        class="com.xyz.fragA" />

    <!-- FRAGMENT C -->
    <fragment
        android:id="@+id/fragment_C"
        android:layout_width="600dp"
        android:layout_height="match_parent"
        android:layout_alignParentRight="true"
        class="com.xyz.fragC"/>

    <!-- FRAGMENT B -->
    <fragment
        android:id="@+id/fragment_B"
        android:layout_width="match_parent"
        android:layout_height="fill_parent"
        android:layout_marginLeft="0dip"
        android:layout_marginRight="0dip"
        android:layout_toLeftOf="@id/fragment_C"
        android:layout_toRightOf="@id/fragment_A"
        class="com.xyz.fragB" />

</RelativeLayout>

L'animation pour faire glisser FragmentC vers l'intérieur ou l'extérieur:

private ValueAnimator createFragmentCAnimation(final View fragmentCRootView, boolean slideIn) {

    ValueAnimator anim = null;

    final RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) fragmentCRootView.getLayoutParams();
    if (slideIn) {
        // set the rightMargin so the view is just outside the right edge of the screen.
        lp.rightMargin = -(lp.width);
        // the view's visibility was GONE, make it VISIBLE
        fragmentCRootView.setVisibility(View.VISIBLE);
        anim = ValueAnimator.ofInt(lp.rightMargin, 0);
    } else
        // slide out: animate rightMargin until the view is outside the screen
        anim = ValueAnimator.ofInt(0, -(lp.width));

    anim.setInterpolator(new DecelerateInterpolator(5));
    anim.setDuration(300);
    anim.addUpdateListener(new AnimatorUpdateListener() {

        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            Integer rightMargin = (Integer) animation.getAnimatedValue();
            lp.rightMargin = rightMargin;
            fragmentCRootView.requestLayout();
        }
    });

    if (!slideIn) {
        // if the view was sliding out, set visibility to GONE once the animation is done
        anim.addListener(new AnimatorListenerAdapter() {

            @Override
            public void onAnimationEnd(Animator animation) {
                fragmentCRootView.setVisibility(View.GONE);
            }
        });
    }
    return anim;
}
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.