¿Muestra de trabajo completa del escenario de animación de tres fragmentos de Gmail?


128

TL; DR: Estoy buscando una muestra de trabajo completa de lo que denominaré "el escenario de animación de tres fragmentos de Gmail". Específicamente, queremos comenzar con dos fragmentos, como este:

dos fragmentos

En algún evento de IU (por ejemplo, tocando algo en el Fragmento B), queremos:

  • Fragmento A para deslizarse fuera de la pantalla hacia la izquierda
  • Fragmento B para deslizarse hacia el borde izquierdo de la pantalla y reducir para ocupar el lugar desocupado por el Fragmento A
  • Fragmento C para deslizarse desde el lado derecho de la pantalla y ocupar el lugar desocupado por el Fragmento B

Y, al presionar el botón ATRÁS, queremos que se revierta ese conjunto de operaciones.

Ahora, he visto muchas implementaciones parciales; Revisaré cuatro de ellos a continuación. Más allá de estar incompletos, todos tienen sus problemas.


@Reto Meier contribuyó con esta respuesta popular a la misma pregunta básica, indicando que usaría setCustomAnimations()con a FragmentTransaction. Para un escenario de dos fragmentos (por ejemplo, solo ve el Fragmento A inicialmente y desea reemplazarlo por un nuevo Fragmento B que usa efectos animados), estoy totalmente de acuerdo. Sin embargo:

  • Dado que solo puede especificar una animación de "entrada" y una de "salida", no puedo ver cómo manejaría todas las animaciones diferentes requeridas para el escenario de tres fragmentos
  • El <objectAnimator>código en su muestra utiliza posiciones cableadas en píxeles, y eso parecería poco práctico dados los diferentes tamaños de pantalla, pero setCustomAnimations()requiere recursos de animación, lo que impide la posibilidad de definir estas cosas en Java
  • Estoy en una pérdida en cuanto a cómo los animadores de objeto para lazo escala en con cosas como android:layout_weighten una LinearLayoutde asignación de espacio en una base porcentual
  • Estoy en una pérdida en cuanto a cómo Fragmento C se maneja desde el principio ( GONE? android:layout_weightDe 0? Pre-animado a una escala de 0? Otra cosa?)

@Roman Nurik señala que puede animar cualquier propiedad , incluidas las que usted mismo defina. Eso puede ayudar a resolver el problema de las posiciones cableadas, a costa de inventar su propia subclase de administrador de diseño personalizado. Eso ayuda a algunos, pero todavía estoy desconcertado por el resto de la solución de Reto.


El autor de esta entrada de Pastebin muestra un pseudocódigo tentador, básicamente diciendo que los tres fragmentos residirían inicialmente en el contenedor, con el Fragmento C oculto al principio a través de una hide()operación de transacción. Luego, show()C y hide()A cuando ocurre el evento UI. Sin embargo, no veo cómo eso maneja el hecho de que B cambia de tamaño. También se basa en el hecho de que aparentemente puede agregar múltiples fragmentos al mismo contenedor, y no estoy seguro de si ese es un comportamiento confiable a largo plazo (sin mencionar que debería romperse findFragmentById(), aunque puedo vivir con eso).


El autor de esta publicación de blog indica que Gmail no está utilizando setCustomAnimations()en absoluto, sino que utiliza directamente animadores de objetos ("simplemente cambia el margen izquierdo de la vista raíz + cambia el ancho de la vista derecha"). Sin embargo, esta sigue siendo una solución AFAICT de dos fragmentos, y la implementación muestra una vez más las dimensiones de los cables duros en píxeles.


Continuaré desconectando de esto, así que quizás termine respondiendo esto algún día, pero realmente espero que alguien haya resuelto la solución de tres fragmentos para este escenario de animación y pueda publicar el código (o un enlace al mismo). Las animaciones en Android me dan ganas de arrancarme el pelo, y aquellos de ustedes que me han visto saben que este es un esfuerzo en gran medida infructuoso.


2
¿No es algo como esto publicado por Romain Guy? curious-creature.org/2011/02/22/…
Fernando Gallego

1
@ ferdy182: No está usando fragmentos AFAICT. Visualmente, es el efecto básico correcto, y es posible que pueda usarlo como otro punto de datos para intentar crear una respuesta basada en fragmentos. ¡Gracias!
CommonsWare

1
Solo un pensamiento: la aplicación de correo electrónico estándar tiene un comportamiento similar (aunque no completamente idéntico) en mi Nexus 7, que debería ser de código abierto.
Christopher Orr

44
La aplicación de correo electrónico utiliza un "ThreePaneLayout" personalizado que anima la posición de la Vista de la izquierda y los anchos / visibilidades de las otras dos Vistas, cada una de las cuales contiene el Fragmento relevante.
Christopher Orr

2
Hola @CommonsWare No molestaré a las personas con toda mi respuesta aquí, pero hubo una pregunta similar a la tuya en la que di una respuesta muy satisfactoria. Entonces, solo le sugiero que lo revise (también revise los comentarios): stackoverflow.com/a/14155204/906362
Budius

Respuestas:


67

Subí mi propuesta en github (está funcionando con todas las versiones de Android, aunque se recomienda ver la aceleración de hardware para este tipo de animaciones. Para dispositivos sin aceleración de hardware, una implementación de almacenamiento en caché de mapa de bits debería ser mejor)

El video de demostración con la animación está aquí (Causa de velocidad de fotogramas lenta de la transmisión de pantalla. El rendimiento real es muy rápido)


Uso:

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

Explicacion :

Creó un nuevo RelativeLayout personalizado (ThreeLayout) y 2 animaciones personalizadas (MyScalAnimation, MyTranslateAnimation)

ThreeLayoutobtiene el peso del panel izquierdo como parámetro, suponiendo que la otra vista visible lo tenga weight=1.

Entonces new ThreeLayout(context,3)crea una nueva vista con 3 hijos y el panel izquierdo tiene 1/3 de la pantalla total. La otra vista ocupa todo el espacio disponible.

Calcula el ancho en tiempo de ejecución, una implementación más segura es que las dimensiones se calculan la primera vez en draw (). en lugar de en post ()

Las animaciones de Escalar y Traducir en realidad cambian el tamaño y mueven la vista y no pseudo- [escalar, mover]. Tenga en cuenta que fillAfter(true)no se usa en ningún lado.

View2 es right_of View1

y

View3 es right_of View2

Habiendo establecido estas reglas, RelativeLayout se encarga de todo lo demás. Las animaciones alteran margins(en movimiento) y [width,height]en escala

Para acceder a cada niño (para que pueda inflarlo con su Fragmento, puede llamar

public FrameLayout getLeftLayout() {}

public FrameLayout getMiddleLayout() {}

public FrameLayout getRightLayout() {}

A continuación se muestran las 2 animaciones.


Nivel 1

--- Pantalla de ENTRADA ----------! ----- FUERA ----

[Vista1] [_____ Vista2 _____] [_____ Vista3_____]

Etapa 2

--OUT -! -------- IN Screen ------

[Vista1] [Vista2] [_____ Vista3_____]


1
Si bien aún no he implementado un animador de escala, me preocupa que funcione bien para campos sólidos de color y menos bien, digamos, contenido "real". Los resultados del Fragmento B se redimensionan para adaptarse al espacio del Fragmento A no deberían ser diferentes de si el Fragmento B se hubiera colocado allí originalmente. Por ejemplo, AFAIK, un animador de escala escalará el tamaño de la fuente (dibujando todo en la animación Viewmás pequeña), pero en Gmail / Email, no obtenemos fuentes más pequeñas, solo menos palabras.
CommonsWare

1
@CommonsWare scaleAnimation es solo un nombre abstracto. De hecho, no se realiza escalamiento. Realmente redimensiona el ancho de la vista. Comprueba la fuente. LayoutResizer podría ser un mejor nombre para ello.
Debilwire

1
Esa no es mi interpretación de la fuente en torno al scaleXvalor View, aunque puedo estar malinterpretando las cosas.
CommonsWare

1
@CommonsWare revisa este github.com/weakwire/3PaneLayout/blob/master/src/com/example/… out. La única razón por la que extiendo Animación es para tener acceso al Tiempo interpolado. p.width = (int) ancho; hace toda la magia y no es scaleX. Son las dimensiones reales de la vista las que cambian durante el tiempo especificado.
2012

2
Su video muestra una buena velocidad de fotogramas, pero las vistas utilizadas en su ejemplo son muy simples. Hacer una requestLayout () durante una animación es muy costoso. Especialmente si los hijos son complejos (un ListView por ejemplo). Esto probablemente dará como resultado una animación entrecortada. La aplicación GMail no llama a requestLayout. En cambio, otra vista más pequeña se coloca en el panel central justo antes de que comience la animación.
Thierry-Dimitri Roy

31

OK, aquí está mi propia solución, derivada de la aplicación Email AOSP, según la sugerencia de @ Christopher en los comentarios de la pregunta.

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

La solución de @ weakwire recuerda a la mía, aunque usa el clásico en Animationlugar de los animadores, y usa RelativeLayoutreglas para imponer el posicionamiento. Desde el punto de vista de la recompensa, probablemente obtendrá la recompensa, a menos que alguien más con una solución más ingeniosa aún publique una respuesta.


En pocas palabras, el ThreePaneLayoutproyecto en ese es una LinearLayoutsubclase, diseñada para trabajar en el paisaje con tres hijos. Los anchos de esos niños se pueden establecer en el diseño XML, a través de los medios deseados: muestro el uso de pesos, pero podría tener anchos específicos establecidos por recursos de dimensión o lo que sea. El tercer hijo (Fragmento C en la pregunta) debe tener un ancho de cero.

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

Sin embargo, en tiempo de ejecución, no importa cómo configuró originalmente los anchos, la administración del ancho se hace cargo de ThreePaneLayoutla primera vez que usa hideLeft()para pasar de mostrar lo que la pregunta denominada Fragmentos A y B a Fragmentos B y C. En la terminología de ThreePaneLayout- que no tiene lazos específicas a fragmentos - las tres piezas son left, middle, y right. En el momento de llamar hideLeft(), registramos los tamaños de lefty middley cero fuera de cualquier peso que se utilizaron en cualquiera de los tres, así que podemos controlar por completo los tamaños. En el momento de hideLeft()establecer el tamaño de rightser el tamaño original de middle.

Las animaciones son dobles:

  • Use a ViewPropertyAnimatorpara realizar una traducción de los tres widgets a la izquierda por el ancho de left, usando una capa de hardware
  • Use una ObjectAnimatorpseudo-propiedad personalizada de middleWidthpara cambiar el middleancho de lo que sea que comience al ancho original deleft

(es posible que sea una mejor idea usar un AnimatorSety ObjectAnimatorspara todos estos, aunque esto funciona por ahora)

(también es posible que middleWidth ObjectAnimatorniegue el valor de la capa de hardware, ya que eso requiere una invalidación bastante continua)

( Definitivamente es posible que todavía tenga lagunas en mi comprensión de animación y que me gusten las declaraciones entre paréntesis)

El efecto neto es que se leftdesliza fuera de la pantalla, se middledesliza a la posición y tamaño originales lefty se righttraduce justo detrás middle.

showLeft() simplemente invierte el proceso, con la misma combinación de animadores, solo con las direcciones invertidas.

La actividad utiliza a ThreePaneLayoutpara contener un par de ListFragmentwidgets y a Button. Al seleccionar algo en el fragmento izquierdo, se agrega (o actualiza el contenido) el fragmento del medio. Al seleccionar algo en el fragmento del medio, se establece el subtítulo de Button, además, se ejecuta hideLeft()en el ThreePaneLayout. Al presionar ATRÁS, si ocultamos el lado izquierdo, se ejecutará showLeft(); de lo contrario, BACK sale de la actividad. Como esto no se usa FragmentTransactionspara afectar las animaciones, estamos atascados manejando esa "pila de fichas" nosotros mismos.

El proyecto vinculado anteriormente utiliza fragmentos nativos y el marco de animación nativo. Tengo otra versión del mismo proyecto que utiliza el backport de fragmentos de soporte de Android y NineOldAndroids para la animación:

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

El backport funciona bien en un Kindle Fire de primera generación, aunque la animación es un poco irregular debido a las especificaciones de hardware más bajas y la falta de soporte de aceleración de hardware. Ambas implementaciones parecen fluidas en un Nexus 7 y otras tabletas de generación actual.

Ciertamente estoy abierto a ideas sobre cómo mejorar esta solución u otras soluciones que ofrecen claras ventajas sobre lo que hice aquí (o lo que usó @weakwire).

Gracias de nuevo a todos los que han contribuido!


1
¡Hola! Gracias por la solución, pero creo que agregar v.setLayerType () a veces hace las cosas un poco más lentas. Simplemente elimine estas líneas (y Listener también) Esto es al menos cierto para los dispositivos Android 3+ que acabo de probar en Galaxy Nexus y sin estas líneas funciona mucho mejor
Evgeny Nacu

1
"Ambas implementaciones parecen fluidas en un Nexus 7 y otras tabletas de generación actual". Te creo; pero estoy probando su implementación con ObjectAnimator nativo en un Nexus 7 y se retrasa un poco. ¿Cuáles serían las causas más probables? Ya tengo hardware Acelerado: cierto en AndroidMAnifest. Realmente no sé cuál podría ser el problema. También probé la variante de DanielGrech y es la misma. Puedo ver diferencias en la animación (su animación se basa en pesos), pero no puedo ver por qué se retrasaría en ambos casos.
Bogdan Zurac

1
@ Andrew: "se retrasa un poco" - No estoy seguro de cómo estás usando el verbo "lag" aquí. Para mí, "retraso" implica un objetivo concreto para la comparación. Entonces, por ejemplo, una animación vinculada a un movimiento de deslizamiento podría retrasarse detrás del movimiento del dedo. En este caso, todo se desencadena por toques, por lo que no estoy seguro de qué podría estar quedando atrás la animación. Adivinando que tal vez "se retrase un poco" simplemente significa "se siente lento", no lo he visto, pero no pretendo tener un gran sentido estético, por lo que lo que podría ser una animación aceptable para mí podría ser inaceptable para usted.
CommonsWare

2
@CommonsWare: compilé su código con 4.2, gran trabajo. Cuando toqué el centro ListViewy presioné el botón de retroceso rápidamente y repetí, todo el diseño se desvió hacia la derecha. Comenzó a mostrar espacio extra en la columna de la izquierda. Este comportamiento se introdujo porque no dejaría que se completara la primera animación (comenzada tocando en el medio ListView) y presioné el botón Atrás. Lo arreglé reemplazando translationXBycon translationXin translateWidgetsmethod y translateWidgets(leftWidth, left, middle, right);con translateWidgets(0, left, middle, right);in showLeft()method.
M-WaJeEh

2
También lo reemplacé requestLayout();con middle.requestLayout();in setMiddleWidth(int value);method. Es posible que no afecte el rendimiento, ya que supongo que la GPU seguirá haciendo un pase de diseño completo, ¿algún comentario?
M-WaJeEh

13

Creamos una biblioteca llamada PanesLibrary que resuelve este problema. Es aún más flexible de lo que se ofreció anteriormente porque:

  • Cada panel puede tener un tamaño dinámico
  • Permite cualquier cantidad de paneles (no solo 2 o 3)
  • Los fragmentos dentro de los paneles se retienen correctamente en los cambios de orientación.

Puede consultarlo aquí: https://github.com/Mapsaurus/Android-PanesLibrary

Aquí hay una demostración: http://www.youtube.com/watch?v=UA-lAGVXoLU&feature=youtu.be

Básicamente, le permite agregar fácilmente cualquier número de paneles de tamaño dinámico y adjuntar fragmentos a esos paneles. ¡Esperamos que te sea útil! :)


6

Partiendo de uno de los ejemplos a los que se vinculó ( http://android.amberfog.com/?p=758 ), ¿qué le parece animar la layout_weightpropiedad? De esta manera, puede animar el cambio en el peso de los 3 fragmentos juntos, Y obtiene la ventaja de que todos se deslizan juntos:

Comience con un diseño simple. Como vamos a animar layout_weight, necesitamos una LinearLayoutvista de raíz para los 3 paneles:

<?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>

Luego la clase de demostración:

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

Además, este ejemplo no usa FragmentTransactions para lograr las animaciones (más bien, anima las vistas a las que están unidos los fragmentos), por lo que necesitaría hacer todas las transacciones de backstack / fragment usted mismo, pero en comparación con el esfuerzo de hacer que las animaciones funcionen bien, esto no parece una mala compensación :)

Horrible video de baja resolución en acción: http://youtu.be/Zm517j3bFCo


1
Bueno, Gmail definitivamente hace una traducción de lo que yo describo como Fragmento A. Me preocuparía que la reducción de su peso a 0 podría introducir artefactos visuales (por ejemplo, TextViewcon wrap_contentel estiramiento en sí verticalmente a medida que avanza la animación). Sin embargo, puede ser que animando el peso es cómo cambian el tamaño de lo que denominé Fragmento B. ¡Gracias!
CommonsWare

1
¡Sin preocupaciones! Sin embargo, ese es un buen punto, esto probablemente causaría artefactos visuales dependiendo de qué vistas secundarias se encuentren en el 'Fragmento A'. Esperemos que alguien encuentre una manera más sólida de hacerlo
DanielGrech

5

Esto no está usando fragmentos ... Es un diseño personalizado con 3 hijos. Cuando hace clic en un mensaje, compensa los 3 niños que usan offsetLeftAndRight()y un animador.

En JellyBeanpuede habilitar "Mostrar límites de diseño" en la configuración de "Opciones de Desarrollador". Cuando se completa la animación de la diapositiva, aún puede ver que el menú izquierdo todavía está allí, pero debajo del panel central.

Es similar al menú de la aplicación Fly-in de Cyril Mottier , pero con 3 elementos en lugar de 2.

Además, la ViewPagerde los terceros niños es otra indicación de este comportamiento: ViewPagergeneralmente usa Fragmentos (sé que no tienen que hacerlo, pero nunca he visto una implementación que no sea esa Fragment), y como no puedes usar Fragmentsdentro de otro, Fragmentlos 3 niños probablemente no son fragmentos ...


Nunca he usado "Mostrar límites de diseño" antes, eso es muy útil para aplicaciones de código cerrado como esta (¡gracias!). Parece que lo que describí como Fragmento A se está traduciendo fuera de la pantalla (se puede ver su borde derecho deslizarse de derecha a izquierda), antes de aparecer debajo de lo que describí como Fragmento B. Eso se siente como un TranslateAnimation, aunque estoy Seguro que hay formas de usar animadores para lograr los mismos fines. Tendré que hacer algunos experimentos sobre esto. ¡Gracias!
CommonsWare

2

Actualmente estoy tratando de hacer algo así, excepto que la Fragmento B escala para tomar el espacio disponible y que el panel 3 se puede abrir al mismo tiempo si hay suficiente espacio. Aquí está mi solución hasta ahora, pero no estoy seguro de si voy a seguir con ella. Espero que alguien proporcione una respuesta que muestre La forma correcta.

En lugar de usar un LinearLayout y animar el peso, uso un RelativeLayout y animar los márgenes. No estoy seguro de que sea la mejor manera porque requiere una llamada a requestLayout () en cada actualización. Sin embargo, es suave en todos mis dispositivos.

Entonces, animo el diseño, no estoy usando fragmentos de transacción. Manejo el botón de retroceso manualmente para cerrar el fragmento C si está abierto.

FragmentB use layout_toLeftOf / ToRightOf para mantenerlo alineado con los fragmentos A y C.

Cuando mi aplicación activa un evento para mostrar el fragmento C, deslizo el fragmento C y deslizo el fragmento A al mismo tiempo. (2 animaciones separadas). Inversamente, cuando el Fragmento A se abre, cierro C al mismo tiempo.

En modo vertical o en una pantalla más pequeña, uso un diseño ligeramente diferente y deslizo el Fragmento C sobre la pantalla.

Para usar el porcentaje para el ancho del Fragmento A y C, creo que tendría que calcularlo en tiempo de ejecución ... (?)

Aquí está el diseño de la actividad:

<?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>

La animación para deslizar FragmentC dentro o fuera:

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;
}
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.