124 votes

Toutes les travaillant échantillon du Gmail trois-Fragment Animation scénario ?

TL;DR: je suis à la recherche d'un travail complet de l'échantillon de ce que j'appellerai "l'Gmail trois-fragment d'animation" scénario. Plus précisément, nous voulons commencer avec deux fragments, comme ceci:

two fragments

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

  • Fragment d'Un glisser hors de l'écran vers la gauche
  • Fragment B de glisser vers le bord gauche de l'écran et rétrécir à prendre la place vacante par le Fragment d'Un
  • Le Fragment C de glisser le bord droit de l'écran et de prendre la place vacante par le Fragment B

Et, sur un bouton de RETOUR de la presse, nous voulons que l'ensemble des opérations devant être inversé.

Maintenant, j'ai vu beaucoup de partielle implémentations, je vais examiner quatre d'entre eux ci-dessous. Au-delà d'être incomplètes, elles ont toutes leurs questions.


@Reto Meier ont contribué à cette populaire réponse à la même question de base, indiquant que vous pouvez utiliser setCustomAnimations() avec un FragmentTransaction. Pour un deux-fragment de scénario (par exemple, vous ne voyez qu'Un Fragment d'abord, et que vous voulez le remplacer par un nouveau Fragment B à l'aide d'effets d'animation), je suis tout à fait d'accord. Cependant:

  • Puisque vous ne pouvez spécifier qu'un "in" et un "dehors" de l'animation, je ne vois pas comment vous pourriez faire face à toutes les animations nécessaires pour les trois-fragment de scénario
  • L' <objectAnimator> dans son exemple de code utilise câblé positions en pixels, et qui semble pas pratique, compte tenu de diverses tailles d'écran, pourtant, setCustomAnimations() nécessite d'animation de ressources, ce qui exclut la possibilité de définir ces choses en Java
  • Je suis à une perte quant à la façon dont l'objet de l'animation pour l'échelle de la cravate avec des choses comme android:layout_weight en LinearLayout pour allouer de l'espace sur la base d'un pourcentage
  • Je suis à une perte quant à la façon dont le Fragment C est gérée au départ (GONE? android:layout_weight de 0? pré-animation d'une échelle de 0? quelque chose d'autre?)

@Roman Nurik points que vous pouvez animer toute propriété, y compris ceux que vous définissez vous-même. Qui peut aider à résoudre le problème de la filaire de positions, au prix d'inventer votre propre mise en page personnalisée gestionnaire de sous-classe. Qui aide à certains, mais je suis toujours sidéré par le reste de Reto de la solution.


L'auteur de ce pastebin entrée montre certaines alléchantes pseudo-code, ce qui revient à dire que tous les trois fragments réside dans le conteneur d'abord, avec le Fragment C cachés au départ par l'intermédiaire d'un hide() opération de transaction. Nous avons ensuite show() C et hide() lorsque l'INTERFACE utilisateur de l'événement se produit. Cependant, je ne vois pas comment il gère le fait que B des changements de taille. Il s'appuie également sur le fait que, apparemment, vous avez peut ajouter plusieurs fragments de la même conteneur, et je ne suis pas sûr de savoir si ou non la fiabilité de comportement sur le long terme (pour ne pas mentionner il devrait casser findFragmentById(), mais je peux vivre avec ça).


L'auteur de ce blog indique que Gmail n'est pas à l'aide de setCustomAnimations() , mais plutôt directement utilise l'objet d'animateurs ("vous venez de changer de marge de gauche de la vue racine + changement de la largeur de la vue de droite"). Cependant, c'est toujours un deux-fragment solution AFAICT, et la mise en œuvre une fois de plus montré dur-fils dimensions en pixels.


Je vais continuer à bosser à cette, afin que je puisse la liquidation de répondre à cette question à moi-même un jour, mais je suis vraiment en espérant que quelqu'un a travaillé sur les trois-fragment de solution pour cette animation scénario et peux poster le code (ou un lien de celui-ci). Animations dans Android me faire tirer mes cheveux, et ceux d'entre vous qui m'ont vu savent que c'est une grande partie stériles effort.

Merci à l'avance!

66voto

weakwire Points 6263

Téléchargé ma proposition à github (Fonctionne avec toutes les versions d'android mais vue de l'accélération matérielle est fortement recommandé pour ce genre d'animations. Pour les non accélération matérielle des périphériques une mise en cache des bitmaps de la mise en œuvre devrait mieux)

Vidéo de démonstration de l'animation Ici (ralentissement de la cadence cause de l'écran de fonte. La performance réelle est très rapide)


Utilisation:

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

En expliquant:

Créé une nouvelle custom RelativeLayout(ThreeLayout) et 2 Animations personnalisées(MyScalAnimation,MyTranslateAnimation)

ThreeLayout obtient le poids du volet de gauche comme param ,en supposant que les autres d'affichage visible a weight=1.

Donc, new ThreeLayout(context,3) crée un nouveau point de vue avec les 3 enfants et le volet de gauche ont 1/3 de l'écran total. L'autre point de vue, occupe tout l'espace disponible.

Il calcule la largeur lors de l'exécution,la sécurité de la mise en œuvre est que les dimensions sont calculées première fois dans draw(). au lieu de post()

Échelle et translation des animations redimensionnez et déplacez le point de vue et non de pseudo-[échelle,déplacer] .Notez que fillAfter(vrai) n'est pas utilisé n'importe où.

Vue2 est right_of Vue1

et

Vue3 est right_of Vue2

Après avoir défini ces règles RelativeLayout prend soin de tout le reste. Animations modifier l' margins (sur le coup) et [width,height] sur l'échelle

Pour accéder à chaque enfant (de sorte que vous pouvez le gonfler avec votre Fragment que vous pouvez appeler

public FrameLayout getLeftLayout() {}

public FrameLayout getMiddleLayout() {}

public FrameLayout getRightLayout() {}

Ci-dessous sont démontrés les 2 animations


Stage1

---DANS l'Écran----------!---------

[Vue1][__Vue2__][__Vue3__]

Stage2

--OUT-!--------DANS l'Écran------

[Vue1][Vue2][__Vue3__]

31voto

CommonsWare Points 402670

OK, voici ma solution, dérivé de l'e-Mail PSBA application, par @Christophe suggestion dans la question de commentaires.

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

@weakwire la solution est pas sans rappeler la mienne, bien qu'il utilise le classique Animation plutôt que des animateurs, et il utilise RelativeLayout règles à appliquer de positionnement. À partir de la bounty point de vue, il sera probablement obtenir la prime, à moins que quelqu'un d'autre avec un ciré solution encore des postes une réponse.


En un mot, l' ThreePaneLayout dans ce projet est un LinearLayout sous-classe, conçu pour fonctionner en mode paysage avec trois enfants. Ces enfants largeurs peuvent être définies dans le schéma XML, via n'importe quel désirée -- je montre à l'aide de poids, mais vous pourriez avoir largeurs spécifiques fixés par la dimension des ressources ou quoi que ce soit. Le troisième enfant, le Fragment C de 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, peu importe comment vous avez créée à l'origine pour la largeur, la largeur de la gestion est prise en charge par ThreePaneLayout la première fois que vous utilisez hideLeft() pour passer de montrer que la question posée comme des Fragments A et B à des Fragments B et C. Dans la terminologie de l' ThreePaneLayout -- qui n'a pas des relations spécifiques avec des fragments -- les trois morceaux sont left, middle, et right. Au moment de l'appel hideLeft(), nous enregistrons les tailles d' left et middle et zéro tous les poids qui ont été utilisés sur l'un des trois, de sorte que nous pouvons contrôler complètement les tailles. Au moment de l' hideLeft(), nous avons fixé la taille de l' right à la taille d'origine de l' middle.

Les animations sont de deux ordres:

  • Utiliser un ViewPropertyAnimator pour effectuer une traduction des trois widgets vers la gauche par la largeur de l' left, à l'aide d'une couche matérielle
  • Utiliser un ObjectAnimator sur un custom pseudo-propriété d' middleWidth pour changer l' middle de la largeur de ce qu'il a commencé avec de la largeur d'origine de l' left

(il est possible que c'est une meilleure idée d'utiliser un AnimatorSet et ObjectAnimators pour l'ensemble de ces, si cela fonctionne pour l'instant)

(il est également possible que l' middleWidth ObjectAnimator nie la valeur de la couche matérielle, puisque ça demande assez continue d'invalidation)

(il est certainement possible que j'ai encore des lacunes dans mon animation à la compréhension, et que j'aime entre parenthèses états)

L'effet net est que l' left glisse hors de l'écran, middle des diapositives à la position d'origine et la taille de l' left, et right se traduit en droit derrière, middle.

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

L'activité utilise un ThreePaneLayout pour tenir une paire de ListFragment widgets et un Button. Choisir quelque chose dans la gauche fragment ajoute (ou met à jour le contenu de) le milieu fragment. La sélection de quelque chose dans le milieu fragment définit la légende de l' Button, plus exécute hideLeft() sur le ThreePaneLayout. En appuyant sur le DOS, si nous avons caché le côté gauche, exécute showLeft(); sinon, les sorties de l'activité. Ce n'est pas utiliser FragmentTransactions pour affecter les animations, nous sommes coincés de la gestion de cette "pile de retour" de nous-mêmes.

Le projet lié à ci-dessus utilisant les fragments et les natifs de l'animateur-cadre. J'ai une autre version du même projet qui utilise le Soutien Android fragments de backport et NineOldAndroids pour l'animation:

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

Le port fonctionne très bien sur une 1ère génération de Kindle Fire, si l'animation est un peu saccadé compte tenu de la baisse des spécifications matérielles et le manque de matériel de support de l'accélération. Les deux implémentations semble lisse sur une Nexus 7 et d'autres en cours de génération de tablettes.

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

Merci encore à tous ceux qui ont contribué!

13voto

coda Points 185

Nous avons construit une bibliothèque appelée PanesLibrary qui résout ce problème. C'est même plus souple que ce qui a été précédemment proposé en raison de:

  • Chaque volet peut être dynamiquement la taille d'
  • Il permet à n'importe quel nombre de volets (et pas seulement 2 ou 3)
  • Les Fragments à l'intérieur de panneaux sont correctement retenus sur les 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

Essentiellement, il vous permet de facilement ajouter n'importe quel nombre de dynamiquement la taille des volets et de fixer des fragments de ces volets. J'espère que vous trouverez utile! :)

6voto

DanielGrech Points 1205

S'appuyant sur l'un des exemples que vous avez associé (http://android.amberfog.com/?p=758), comment sur l'animation de la layout_weight de la propriété? De cette façon, vous pouvez animer le changement de poids de la 3 fragments, ET vous obtenez le bonus qu'ils ont tous diapositive bien ensemble:

Commencez avec une mise en page simple. Depuis nous allons animer layout_weight, nous avons besoin d'un LinearLayout à la racine de vue pour le 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 la démo de classe:

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

Aussi cet exemple n'utilise pas FragmentTransactions pour atteindre les animations (plutôt, il anime les points de vue les fragments sont rattachés), de sorte que vous devez faire tous les backstack/fragment opérations vous-même, mais par rapport à l'effort d'obtenir les animations fonctionne parfaitement, ce qui ne marche pas ressembler à un mauvais compromis :)

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

5voto

Ce n'est pas à l'aide de fragments.... C'est une mise en page personnalisée avec 3 enfants. Lorsque vous cliquez sur un message, vous compensez les 3 enfants à l'aide de offsetLeftAndRight() et un animateur.

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

Il est similaire à Cyril Mottier Mouche-dans le menu app, mais avec 3 éléments au lieu de 2.

Par ailleurs, la ViewPager de la troisième enfants, est une autre indication de ce comportement: ViewPager utilise généralement des Fragments (je sais qu'ils n'ont pas à, mais je n'ai jamais vu une mise en œuvre autres que Fragment), et puisque vous ne pouvez pas utilise des Fragments à l'intérieur d'un autre Fragment de l'3 enfants sont sans doute pas des fragments....

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X