¿Qué es un fragmento?
La necesidad de usar fragmentos nace con la versión 3.0 (API 11) de Android debido a los múltiples tamaños de pantalla que estaban apareciendo en el mercado y a la capacidad de orientación de la interfaz ( Landscape y Portrait). Estas características necesitaban dotar a las aplicaciones Android de la capacidad para adaptarse y responder a la interfaz de usuario sin importar el dispositivo.
Un fragmento es una sección "modular" de interfaz de usuario embebida dentro de una actividad anfritriona, el cual permite versatilidad y optimización de diseño. Se trata de miniactividades contenidas dentro de una actividad anfitriona, manejando su propio diseño (un recurso layout propio) y ciclo de vida.
Estas nuevas entidades permiten reusar código y ahorrar tiempo de diseño a la hora de desarrollar una aplicación. Los fragmentos facilitan el despliegue de tus aplicaciones en cualquier tipo de tamaño de pantalla y orientación.
Otra ventaja de usarlos es que permiten crear diseños de interfaces de usuario de múltiples vistas. ¿Que quiere decir eso?, que los fragmentos son imprescindibles para generar actividades con diseños dinámicos, como por ejemplo el uso de pestañas de navegación, expand and collapse, stacking, etc.
Ciclo de vida de un fragmento
Espero haya quedado claro el concepto de fragmento en la sección anterior, ya que ahora seguimos con la explicación de su ciclo de vida.
Como habíamos mencionado, los fragmentos tienen un ciclo de vida que los asemejan a las actividades. La diferencia esta en que el ciclo de vida de un fragmento depende del ciclo de vida de una actividad y además un fragmento posee algunas características extras, ya que es muy común alterarlos en tiempo de ejecución.
Cuando una actividad esta en su estado de reanudación, todos los fragmentos que esta contiene pueden actuar independientemente y pasar a sus otros estados sin ningún problema. Pero si en algún momento la actividad pasa a pausa, entonces todos los fragmentos siguen su comportamiento, al igual que si pasa a detención o se destruye.
Veamos a continuación una ilustración que muestra el ciclo de vida de un fragmento:
El ciclo de un fragmento tiene unos cuantos métodos callback adicionales al ciclo de vida de una actividad. Averigüemos un poco sobre ellos:
onAttach: Es invocado cuando el fragmento ha sido asociado a la actividad anfitriona.onActiviyCreated: Se ejecuta cuando la actividad anfitriona ya ha terminado la ejecución de su metodo onCreate().onCreate: Este método es llamado cuando el fragmento se esta creando. En el puedes inicializar todos los componentes que deseas guardar si el fragmento fue pausado o detenido.onCreateView: Se llama cuando el fragmento será dibujado por primera vez en la interfaz de usuario. En este método crearemos el view que representa al fragmento para retornarlo hacia la actividad.onStart: Se llama cuando el fragmento esta visible ante el usuario. Obviamente depende del método onStart() de la actividad para saber si la actividad se esta mostrando.onResume: Es ejecutado cuando el fragmento esta activo e interactuando con el usuario. Esta situacion depende de la que actividad anfitriona este primero en su estado Resumed.onStop: Se llama cuando un fragmento ya no es visible para el usuario debido a que la actividad anfitriona esta detenida o porque dentro de la actividad se esta gestionando una operación de fragmentos.onPause: Al igual que las actividades, onPause se ejecuta cuando se detecta que el usuario dirigió el foco por fuera del fragmento.onDestroyView: Este método es llamado cuando la jerarquía de views a la cual ha sido asociado el fragmento ha sido destruida.onDetach: Se llama cuando el fragmento ya no esta asociado a la actividad anfitriona.Creando un fragmento embebido
Los fragmentos son representados en el API de Android por la clase Fragment, por lo que cada vez que vayamos a crear un fragmento personalizado debemos crear una nueva clase que herede su propiedades y comportamientos. Cabe aclarar que también podemos usar subclases de fragmentos creadas previamente en el API de Android como por ejemplo el ListFragment, DialogFragment, PreferenceFragment, etc. Estudiaremos sus utilidades en otros artículos.
Luego de ello debes sobrescribir el método onCreateView() para retornar el View del fragmento hacia la jerarquia de la actividad anfitriona. Para eso debemos inflar el código java desde el layout personalizado del fragmento.
Veamos un ejemplo básicopublic static class FragmentoEjemplo extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragmento_ejemplo, container, false);
}
}
El método onCreateView() recibe como parámetro un objeto de tipo LayoutInflater, el cual es proveído por la clase anfitriona.
Esta clase es la encargada de inflar el código Java a través del método inflate(). Este recibe como parámetros el recurso xml del layout, el contenedor(en este caso el view de la actividad anfitriona) donde será insertado y una bandera indicando si el view que se producirá debe adherirse al contenedor.
Como se ve, hemos usado al archivo fragmento_ejemplo.xml como recurso, el parámetro container que apunta a la actividad anfitriona y false para indicar que no indicar que no deseamos comprometer el view del fragmento.¿Como añadir el fragmento a la actividad?
Una vez creada la clase y el layout que representa nuestro fragmento debemos añadirlo a la actividad. Existen dos formas para realizar este proceso.
La primera es a través del archivo layout de la actividad. Donde incluiremos un componente XML de equivalencia para fragmentos llamado <Fragment>, el cual referenciará a la clase especifica extendida del fragmento para crear la jerarquía.
Veamos un ejemplo:<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment android:name="com.herprogramacion.fragmentos.FragmentoEjemplo"
android:id="@+id/ejemplo"
android:layout_weight="1"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
El código anterior muestra un nodo <fragment> embebido como hijo en el LinearLayout de nuestra actividad. Es importante usar un atributo id para el fragmento, ya que esto permitirá diferenciarlo de otros fragmentos a la hora de hacer la búsqueda de alguno en especial. De esa forma al ejecutar el proyecto el fragmento aparecerá automáticamente.
La segunda forma se trata de insertar el fragmento en tiempo de ejecución. Lo que quiere decir que no usaremos el nodo <fragment>, si no que usaremos el estilo programático para añadir por código Java el fragmento a la actividad.Embeber un fragmento dentro de una actividad
Android Studio nos facilita la generación del código necesario para añadir un fragmento en el layout de una actividad. A continuación crearemos una aplicación con un fragmento basada en el ejemplo del articulo anterior. La idea es convertir nuestra actitividad principal en un fragmento.Pasos para embeber un fragmento en Android Studio
Paso 1: Creación de fragmento
Dirígete a tu paquete Java en la ruta "src/main/java". Luego presiona click derecho en ella y elige "New". Esto significa que deseamos crear un nuevo archivo dentro de la carpeta java. Ahora elige la categoría "Fragment" y luego la opción "Fragment(Blank)", es decir, un fragmento vacio o en blanco.
Una vez hecho esto, veremos una ventana que permite configurar la nueva creación del fragmento dentro del proyecto.
En ella se puede elegir el nombre de la clase que heredará de la clase Fragment con "Fragment Name". En mi caso asigne el nombre de "CountFragment" en relación a la función de la aplicación que es contar los carácteres de un texto.
También podemos especificar si deseamos crear automáticamente un layout relacionado al fragmento con "Create layout XML?". Por supuesto que marcaremos esta opción
Debajo de está la opción "Fragment Layout Name" donde podemos especificar el nombre del archivo XML del layout. En mi caso se autogeneró el nombre de "fragment_count".
Luego se encuentra la opción "Include fragment factory methods"que especifica si queremos incluir en el código de la clase los métodos de fabricación para la clase CountFragment. Por el momento esta opción no la usaremos.
Y finalmente veremos la opción "Include interface callbacks?" que se refiere a si deseamos incluir el código para sobreescribir los métodos mas comunes del ciclo de vida del fragmento. Por supuesto que la marcaremos.
Al presionar en "Finish" en el editor aparecerá la clase CountFragment preparada para funcionar. Si todo salió bien tendrías un código con la siguiente forma:package TUPAQUETE.APLICACION;
import android.os.Bundle;
import android.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
*
* A simple {@link Fragment} subclass.
*
public class CountFragment extends Fragment {
public CountFragment() {
// Required empty public constructor
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_count, container, false);
}
}
Como ves, se ha creado el método onCreateView() de forma automática asignando como recurso al layout fragment_count.xml que se autogeneró en nuestro proyecto.
Ahora acede a este archivo y dirigete a la pestaña de diseño. En la previsualizacion veremos que tiene un componente TextView con la cadena "Hello blank fragment".
Y su contenido xml sería el siguiente:<FrameLayout 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"
tools:context="TUPAQUETE.APLICACION.CountFragment">
<!-- TODO: Update blank fragment layout -->
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="@string/hello_blank_fragment" />
</FrameLayout>
Paso 2: Mover todo el código de la actividad al fragmento
Ahora vamos a mover todos los componentes del layout de la actividad "MyActivity.java" al layout del fragmento. Solo corta y pega todos los elementos hijos del RelativeLayout de la actividad y reemplazalos por el componente <TextView> del fragmento.
Luego cambia la etiqueta FrameLayout por RelativeLayout para que los componentes puedan reacomodarse. Con estas modificaciones el código de fragment_count.xml tendría la siguiente forma:<RelativeLayout 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"
tools:context="TUPAQUETE.APLICACION.CountFragment">
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:inputType="textMultiLine"
android:ems="10"
android:id="@+id/main_editText"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:hint="@string/hint" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/main_button"
android:layout_below="@+id/main_editText"
android:layout_centerHorizontal="true"
android:layout_marginTop="41dp"
android:text="@string/button" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
android:id="@+id/main_textview"
android:layout_centerVertical="true"
android:layout_centerHorizontal="true"
android:layout_below="@+id/main_button"
android:layout_marginTop="41dp" />
</RelativeLayout>
Ahora cortaremos todo el código Java del metodo onCreate() de la actividad al método onCreateView() del fragmento. Al pegar el código, Android Studio te permitirá importar automáticamente todas las dependencia necesarias para mantener la funcionalidad.
¿Viste el error en el siguiente código?boton = (Button)findViewById(R.id.main_button);
edit = (EditText)findViewById(R.id.main_editText);
text = (TextView)findViewById(R.id.main_textview);
Muy bien, al parecer nuestro método findViewById() no funciona en el contexto del fragmento. Esto se debe a que el único que puede referenciar a todos los Views de un un fragmento es el View padre del fragmento. Dicho view es obtenido cuando se infla el layout.
Para tener una referencia a este simplemente crearemos una instancia de tipo View y asignaremos el resultado al inflar el código XML. Luego invocaremos el método findViewById() desde la nueva instancia. Veamos// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_count, container, false);
boton = (Button)view.findViewById(R.id.main_button);
edit = (EditText)view.findViewById(R.id.main_editText);
text = (TextView)view.findViewById(R.id.main_textview);
De este modo ya podemos obtener los views que hay en el layout del fragmento y retornar la variable view al final del método onCreateView(). El código completo del fragmento sería este:package TUPAQUETE.APLICACION;
import android.app.Activity;
import android.os.Bundle;
import android.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
*
* A simple {@link Fragment} subclass.
*
public class CountFragment extends Fragment {
private Button boton;
private EditText edit;
private TextView text;
public CountFragment() {
// Required empty public constructor
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_count, container, false);
boton = (Button)view.findViewById(R.id.main_button);
edit = (EditText)view.findViewById(R.id.main_editText);
text = (TextView)view.findViewById(R.id.main_textview);
boton.setOnClickListener( new View.OnClickListener() {
public void onClick(View view){
String mensaje = edit.getText().toString();
text.setText(String.valueOf(mensaje.length()));
}
});
return view;
}
}
Paso 3: Insertar el fragmento en el layout de la actividad
El ultimo paso es embeber un elemento <fragment> en el layout de la actividad. Aunque podríamos escribir el código XML y parecer inteligentes, a mi me gusta mas bien hacerlo con el método perezoso. Fijate:
Ve a la pestaña "Design" del archivo layout de nuestra actividad. Luego busca un elemento de la categoría en la paleta de Views llamada "Custom" y presiona el elemento "fragment":
A continuación te aparecerá un dialogo que te pide seleccionar el tipo de Fragmento que deseas insertar en el layout de la actividad. Si te fijas aparecen los tipos predefinidos en el API de Android y los definidos por nosotros. En este caso elegiremos nuestro CountFragment y confirmamos.
Cuando selecciones, el editor de diseño habilita el puntero para que podamos arrastrar el fragmento a la posición que deseemos en el layout de la actividad. Así que centra horizontalmente y alinea a la parte superior:
El error de renderizado que acaba de presentarse se debe a que aun nuestro nodo <fragment> no tiene asignado su layout a través del atributo "tools:layout".
Para ello presionaremos en la referencia del error, el enlace "Use layout/fragment_count". Esta acción incluirá rápidamente a nuestro archivo XML la definición del atributo "tools:layout". Una vez hecho esto se mostrará el fragmento incrustado dentro del layout de la actividad como si fuese un view corriente. Si ves el Component Tree verás al fragmento representado como hijo del RelativeLayout de nuestra actividad:
El layout de nuestra actividad final tendría el siguiente aspecto:<RelativeLayout 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:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
tools:context=".MyActivity">
<fragment
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:name="herprogramacion.test.CountFragment"
android:id="@+id/fragment"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
class="herprogramacion.test.CountFragment"
tools:layout="@layout/fragment_count" />
</RelativeLayout>
Si observas bien, el atributo "tools:layout" ya tiene asignado al layout fragment_count.
Al igual que los demás views, un fragmento debe tener definido un identificador id para diferenciarse de los demás y poder encontrarlo a través de búsquedas directas. En este caso el fragmento esta denominado por el identificador "fragment".Paso 4: Ejecuta y comprueba
Ejecutamos el proyecto para comprobar la funcionalidad del fragmento.
¿Qué chevere cierto?, nuestro fragmento funciona a la perfección.
Esta vía para agregar fragmentos es muy útil cuando los fragmentos de las actividades están predefinidos y serán estáticos. Pero... ¿qué sucedería si deseamos añadir un fragmento en tiempo de ejecución?, la siguiente sección nos mostrará como hacerlo.Creando un fragmento en tiempo de ejecución
Existen casos de fragmentos que no pueden agregarse al layout de una actividad hasta que la aplicación este corriendo, bien sea por que necesitan la información de la base de datos para sus views o por que todavía el usuario no ha solicitado ver esa parte de la interfaz. En estos casos no es posible agregar los fragmentos como nodos <fragment> en el layout de la actividad, ya que no sabemos nada sobre sus atributos.
Para crear un fragmento dinámicamente en tiempo de ejecución haremos exactamente lo mismo que hicimos anteriormente. Declaramos la clase del fragmento y generamos su layout. Pero en vez de agregarlo al layout de la actividad, instanciaremos un elemento del tipo fragmento directamente en el código Java de nuestra actividad. Lo que quiere decir que usaremos el diseño programático en vez del declarativo.
Como ejemplo, añadiremos el fragmento anterior. Para ello primero debes eliminar el nodo <fragment> del layout de nuestra actividad y luego añadir un nuevo botón a la actividad con el id "add_fragment" y el texto "Añadir Fragmento":
El propósito de este botón es educativo. Su función es añadir el fragmento en tiempo de ejecución una vez se ha presionado.
Ahora solo queda añadir las instrucciones necesarias dentro de la escuha OnClickListener del botón, para añadir nuestro fragmento al pulsarse.¡Show me the code!
¡Con calma mi amigo!, primero entenderemos que factores intervienen.
En primera instancia es importante entender que un fragmento puede ser añadido, reemplazado y eliminado en tiempo de ejecución. A esas tres operaciones es les llama Transacciones y en Java una transacción es representada con la clase FragmentTransaction.
Otra situación relevante es que existe una entidad administradora de framentos llamada FragmentManager. Esta entidad es la que permite coordinar las transacciones de los fragmentos, por lo tanto cuando añadamos nuestro fragmento debemos referirnos a él.Pasos para añadir un fragmento en tiempo de ejecución
Paso 1: Obtener una instancia del FragmentManager
Este paso es sencillo. Declararemos un objeto de tipo FragmentManager en el método onCreate() de la actividad y luego le asignaremos la instancia con el método getFragmentManager() de la clase Activity, el cual retorna en una referencia al FragmentManager//Paso 1: Obtener la instancia del administrador de fragmentos
FragmentManager fragmentManager = getFragmentManager();
Paso 2: Crear la instancia de la transacción
Ahora crearemos una nueva transacción usando una instancia de la clase FragmentTransaction y el método beginTransaction() del administrador de fragmentos://Paso 2: Crear una nueva transacción
FragmentTransaction transaction = fragmentManager.beginTransaction();
Paso 3: Crear un nuevo fragmento y añadirlo a la actividad
Instancia un nuevo fragmento de la clase CountFragment y luego añadelo al layout de la actividad a través del método add() de la transacción.//Paso 3: Crear un nuevo fragmento y añadirlo
CountFragment fragment = new CountFragment();
transaction.add(R.id.contenedor, fragment);
Como ves, el método add() recibe dos parámetros. El primero es el identificador del contenedor donde insertaremos el fragmento. En este caso es el RelativeLayout de nuestra actividad, al cual le asigné el id "contenedor".
El segundo parámetro es la instancia del fragmento que queremos añadir.
Paso 4: Confirmar los cambios
Por ultimo ordenamos que los cambios surtan efecto mediante el método commit() de la clase FragmentTransaction. Si estas familiarizado con transacciones en SQL, puedes relacionarlo con la instrucción COMMIT, ambas tienen un propósito muy similar.//Paso 4: Confirmar el cambio
transaction.commit();
El código final de nuestra actividad tendría la siguiente estructura:package herprogramacion.test;
import android.app.Activity;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
public class MyActivity extends Activity {
private Button add_fragment ;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_my);
//Obteniendo la instancia del botón add_fragment
add_fragment = (Button)findViewById(R.id.add_fragment);
//Seteando la escucha del botón
add_fragment.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//Paso 1: Obtener la instancia del administrador de fragmentos
FragmentManager fragmentManager = getFragmentManager();
//Paso 2: Crear una nueva transacción
FragmentTransaction transaction = fragmentManager.beginTransaction();
//Paso 3: Crear un nuevo fragmento y añadirlo
CountFragment fragment = new CountFragment();
transaction.add(R.id.contenedor, fragment);
//Paso 4: Confirmar el cambio
transaction.commit();
}
});
}
}
Pero aun falta un detalle adicional. Si ejecutásemos la aplicación de la forma en que se encuentra, el fragmento aparecerá sin alineación establecida con respecto al botón. Tal vez aparezca superpuesto, perdiendo todo el sentido de la estética. Por esa razón, añadiremos los siguientes parámetros programáticamente al View del fragmento:
Su borde superior este por debajo del borde inferior del botón.
Su margen superior tiene 41dp con respecto a los otros elementos del RelativeLayout.
Para materializar estas condiciones, insertaremos el siguiente código, después de haber inflado el layout del fragmento://Nuevos parametros para el view del fragmento
RelativeLayout.LayoutParams params =
new RelativeLayout.LayoutParams(
RelativeLayout.LayoutParams.MATCH_PARENT,
RelativeLayout.LayoutParams.MATCH_PARENT);
//Nueva Regla: EL fragmento estara debajo del boton add_fragment
params.addRule(RelativeLayout.BELOW, R.id.add_fragment);
//Margenes: top:41dp
params.setMargins(0,41,0,0);
//Setear los parametros al view
view.setLayoutParams(params);
Lo primero que debemos tener en cuenta es que los atributos de un view dentro de un RelativeLayout son representados por la clase LayoutParams. Si tuviésemos un ViewGroup distinto a RelativeLayout también se usaría la misma clase LayoutParams en su paquete correspondiente.
Si nos ubicamos en la primera instrucción veremos una nueva instancia que recibe en su constructor el ancho y el alto del view. Para ello usamos las constantes MATCH_PARENT para indicar un autoajuste al tamaño del padre.
Luego de ello añadiremos una nueva regla de alineación.
Se le llama reglas a todas las condiciones referentes a la ubicación de los views que debe interpretar un RelativeLayout. Para a