Programación Android, Cómo usar múltiples Fragments

Share if you like...Share on Facebook0Tweet about this on TwitterShare on Google+0Share on LinkedIn3
Cómo usar múltiples fragments
Cómo usar múltiples fragments

Aprovechando que volvemos a tener tiempo libre en esta ocasión vamos a escribir un tutorial que hacía tiempo que queríamos hacer para explicar cómo usar múltiples fragments en una misma pantalla. En su día ya explicamos cómo usar los fragments, pero utilizando tabs, o pestañas, para mostrarlos, en esta ocasión explicaremos cómo tener el control del estado de estos para poder mostrar los datos en pantalla de forma correcta. Es decir, imaginemos que tenemos la pantalla dividida en 3 columnas diferentes, cada una de estas columnas es un área para mostrar fragments, y la última columna la dividimos en 2 filas, de manera que tendríamos un total de 4 áreas para fragments. Estando en landscape (dispositivo en horizontal) tenemos espacio de sobra para mostrar toda la información, sin embargo si giramos a portrait (vertical) estas áreas serán demasiado pequeñas como para mostrar todo lo que queremos, por lo que deberíamos mostrar el último detalle añadido, es decir, la última de las columnas que contengan datos. Sin embargo, al volver a landscape tendríamos que volver a mostrar todas las áreas de nuevo.

Por otra parte, si estamos mostrando información en la última columna, y decidimos cargar otra información distinta posteriormente, tendríamos que mantener la lógica de la cola de fragments que vayamos creando, para que al darle al botón atrás mantengamos una navegación lógica para el usuario, por lo tanto tenemos que crear una lógica que nos permita controlar distintos niveles, para añadir y eliminar fragments donde lo necesitemos.

Para entender esto último, que puede parecer muy abstracto, pensemos en la siguiente situación: En la primera columna tenemos la posibilidad de abrir 2 fragments distintos, que se mostrarán en la segunda columna, que a su vez contendrá la posibilidad de abrir otros 2 fragments en la tercera columna. En la primera columna abrimos la primera de las opciones, se muestra en la columna central y a su vez elegimos abrir el primer fragment en la tercera columna. Después de esto seleccionamos en la primera columna abrir el segundo fragment, que se mostrará en la columna central. En este punto, si no hemos controlado la cola de fragments para mantener una lógica, en la tercera columna tendremos aún la vista del detalle que correspondía al primer fragment central, que ya no está visible, lo que no es lógico para un usuario. Pero además, si pulsamos el botón atrás, desaparecerá el fragment de la columna central, mostrándose el que había previamente, y aún mantendremos la vista de la última columna, que correspondería a un detalle. Claramente un comportamiento como este no es el que espera un usuario, sino que lo que espera es que cuando se abra una nueva sección desaparezca todo lo referente a la sección anterior.

Para un ejemplo como este tendríamos 2 niveles distintos, uno es la columna central, y en este nivel añadiríamos todos los fragments que se vayan a mostrar en esta columna, y el segundo para la tercera columna, que contendrá todos los fragments que vayan a esta columna. Al mantener este tipo de lógica, como veremos más adelante en el ejemplo, podremos tomar decisiones sobre a partir de qué niveles eliminar todos los fragments que se hayan cargado en pantalla.

Cómo usar múltiples fragments

Ahora que tenemos una pequeña idea inicial del problema que nos podemos encontrar, vamos, por fin, a empezar con un proyecto que nos sirva de ejemplo para terminar de explicar el resto de puntos que pueden resultar extraños o difíciles de entender en la explicación. En primer lugar vamos a crear un layout para la activity principal que va a tener 4 áreas diferentes, con 3 columnas y la última columna dividida en 2 filas. El primer área tendrá un fragment fijo, y los demás podrán variar su contenido, por lo tanto para el primer área podremos usar en el xml un contenedor de tipo fragment, para el resto utilizaremos framelayout:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              xmlns:tools="http://schemas.android.com/tools"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:orientation="horizontal"
              tools:context=".MainActivity">

    <fragment
    class="com.example.igor.fragments.fragments.MainFragment"
    android:id="@+id/mainFragment"
    android:layout_width="0dp"
    android:layout_height="match_parent"
    android:layout_weight="1"/>

    <FrameLayout
        android:id="@+id/flCentral"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"/>

    <LinearLayout
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:orientation="vertical">

        <FrameLayout
            android:id="@+id/flDetailTop"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"/>

        <FrameLayout
            android:id="@+id/flDetailBottom"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"/>

    </LinearLayout>

</LinearLayout>

Más adelante, cuando hayamos definido el fragment que se mostrará en la primera columna, modificaremos este xml para indicar en el propio xml qué fragment queremos mostrar, y así evitarnos un trabajo innecesario posteriormente en código. El primer paso será crear este primer fragment, crearemos una clase Fragment para manejar la lógica y un archivo xml que represente las vistas para el fragment:

  • XML
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:orientation="vertical"
              android:gravity="center">

    <Button
        android:onClick="showCenterFrag1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="New Button"
        android:id="@+id/button"/>

    <Button
        android:onClick="showCenterFrag2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="New Button"
        android:id="@+id/button2"/>
</LinearLayout>
  • Java
public class MainFragment extends Fragment {

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View v = inflater.inflate(R.layout.mainfragment, container);

        return v;
    }
}

Y ahora que tenemos tanto la vista como el controlador creados, vamos a modificar el primer xml, para que haga la carga automática de este fragment, indicando qué clase usaremos, tanto package como nombre de la clase, modificando lo siguiente:

<fragment
        class="com.example.igor.fragments.fragments.MainFragment"
        android:id="@+id/mainFragment"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"/>

Si ahora ejecutáramos nuestro proyecto veríamos dos botones en el lado izquierdo, sin necesidad de haber añadido ningún fragment desde código. El siguiente paso será crear el resto de fragments, tanto sus controladores como sus vistas, para posteriormente poder trabajar con ellos. Para simplificar un poco el tutorial vamos a crear otros 2 fragments iguales al anterior, cada uno con 2 botones, pero añadiremos un TextView para escribir en él de qué fragment se trata, y además le daremos un color al background, además de dar diferentes nombres a cada uno de los fragments y archivos xml, lógicamente. Por último añadiremos otros 4 fragments, que serán los que mostremos en la tercera columna, para poner en la zona superior e inferior, en función de a qué botón del fragment central pulsemos. Estos fragments tendrán únicamente un background y un TextView que nos permita identificarlos. Los colores, nombres, valores a mostrar en TextViews y métodos a asignar a cada botón quedan a elección de cada uno :p

Para poder seguir el tutorial, en mi caso he llamado de la siguiente manera a los fragments: Para los 2 fragments que irán en la columna central CentralFragment1 y CentralFragment2 (sí, muy original el nombre…) y para los fragments que irán en la tercera columna para la parte superior TopFragment1 y TopFragment2 (seguimos con la originalidad) y por último, como no podía ser de otra manera, los fragments para la parte inferior se llamarán BottomFragment1 y BottomFragment2. No serán nombres muy originales, pero seguro que aclaran dónde se van a mostrar xD

Como ejemplo pondremos el código de uno de los fragments, para que tengamos después claro lo que estamos haciendo:

public class CentralFragment1 extends Fragment {

    /**
     * Método de factoría que nos permite instanciar objetos. Especialmente útil si queremos pasar
     * datos al fragment para poder mostrarlos después en pantalla.
     *
     * @return
     */
    public static CentralFragment1 newInstance() {
        CentralFragment1 frag = new CentralFragment1();

        return frag;
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View v = inflater.inflate(R.layout.centralfragment_1, null);

        return v;
    }
}

En el ejemplo vemos que se ha creado un método estático para crear una instancia de nuestro nuevo fragment, para este ejemplo no es muy interesante utilizarlo, pues no nos ofrece ningún beneficio, pero al final del tutorial explicaré con un pequeño ejemplo por qué debemos hacerlo así.

El siguiente paso que tenemos que dar es empezar a crear los métodos que se lanzan al pulsar los 2 botones de la primera columna, definidos en xml. Para esto vamos a necesitar un objeto de la clase FragmentManager, que será el encargado de gestionar la pila de fragments que hagamos y de mostrarlos en las áreas que decidamos. Este objeto será global a toda la Activity, y debemos inicializarlo en el método onCreate de nuestra Activity. También crearemos instancias globales de nuestros Fragments, que inicializaremos más adelante en los métodos que se encargarán de mostrarlos en pantalla. Por último, también vamos a crear un mapa que se va a encargar de almacenar los commits que hagamos de los fragments (el commit se realiza en el momento en que lo vamos a mostrar en pantalla) y a qué nivel lo vamos a hacer, por lo quedefiniremos también los diferentes niveles en los que se pueden mostrar:

public class MainActivity extends Activity {
    /**
     * Primer nivel de commits, en este nivel guardaremos los commits que se realicen para mostrar
     * el fragment en la columna central.
     */
    private final static int FIRST_COMMIT = 1;
    /**
     * Segundo nivel de commits, en este nivel guardaremos los commits que se realicen para mostrar
     * el fragment en la columna de la derecha.
     */
    private final static int SECOND_COMMIT = 2;

    /**
     * Mapa en el que vamos a guardar la referencia del nivel en el que se realiza un commit, y qué
     * commit se ha realizado.
     */
    private HashMap<Integer, Integer> commits;

    private FragmentManager fragManager;
    private CentralFragment1 fragCentral1;
    private CentralFragment2 fragCentral2;
    private TopFragment1 fragTop1;
    private TopFragment2 fragTop2;
    private BottomFragment1 fragBottom1;
    private BottomFragment2 fragBottom2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        fragManager = getFragmentManager();

        commits = new HashMap<Integer, Integer>();
    }
}

Y ahora sí, definamos en la Activity los métodos onClick de nuestros botones:

public void showCenterFrag1(View v) {
    // Comprobar que no se encuentra ya en pantalla el fragment.
    if(fragCentral1 == null || !fragCentral1.isVisible()) {
        // Iniciamos una nueva transacción
        FragmentTransaction transaction = fragManager.beginTransaction();

        // Inicializamos el fragment
        fragCentral1 = CentralFragment1.newInstance();

        // Indicamos dónde queremos mostrar el fragment, y qué fragment es.
        transaction.replace(R.id.flCentral, fragCentral1);

        // Añadimos la operación a la cola de fragments para que mantengan la
        // lógica al pulsar el botón atrás. De no hacer esta llamada no se
        // guardará en la cola, por lo que al pulsar el botón atrás no podremos
        // recuperar este estado.
        transaction.addToBackStack(null);

        // Realizamos el commit de la transacción y lo almacenamos en el mapa.
        commits.put(FIRST_COMMIT, transaction.commit());

        // Ejectuamos las tareas pendientes, para que finalmente se muestren en pantalla.
        fragManager.executePendingTransactions();
    }
}

public void showCenterFrag2(View v) {
    if(fragCentral2 == null || !fragCentral2.isVisible()) {
        FragmentTransaction transaction = fragManager.beginTransaction();

        fragCentral2 = CentralFragment2.newInstance();

        transaction.replace(R.id.flCentral, fragCentral2);

        transaction.addToBackStack(null);

        commits.put(FIRST_COMMIT, transaction.commit());

        fragManager.executePendingTransactions();
    }
}

Y con esto tendríamos toda la lógica creada para mantener una navegación controlada por nosotros mismos, sin embargo quedaría un detalle pendiente: si pulsaramos el botón atrás del terminal eliminaríamos de la pila el último commit, y desaparecería de la pantalla el último fragment que se haya mostrado, sin embargo en nuestro mapa no tendríamos actualizado este dato. Esto no dará ninguna excepción en la aplicación, sin embargo en el próximo tutorial veremos algunos casos en los que sí nos podría dar problemas. Además, también veremos cómo mostrar sólo un area al poner el dispositivo en modo portrait, y de nuevo volver a mostrarlo todo al volver a poner el dispositivo en landscape.

Share if you like...Share on Facebook0Tweet about this on TwitterShare on Google+0Share on LinkedIn3
The following two tabs change content below.
Reborn as IT Developer. Desarrollador Android y fundador de Proyecto Simio. "En realidad, yo no puedo enseñar nada a nadie, sólo puedo hacerles pensar." - Sócrates.

Deja un comentario