Las transiciones en Android son animaciones que se manifiestan cuando las actividades son iniciadas o cerradas dentro de una aplicación.
Y es que estos efectos muestran en el Material Design un estilo visual tan poderoso que deleita a la vista.
Además aplican los conceptos de movimiento autentico, donde cada elemento proyecta sus condiciones físicas con tan solo verlo.
Pero… ¿Ya sabes usar este tipo de elementos visuales?
¿No?,
pues bien… en este artículo veremos de qué forma incorporar estas características. Estudiaremos distintos tipos de transiciones entre actividades, como explode, fade y slide.
También verás cómo compartir elementos en común de una actividad a otra, para que se ajusten sus dimensiones de un espacio a otro.
Descargar Proyecto En Android Studio De Cursos Online
Al final crearemos una aplicación que contiene Cursos Online como síntesis de este artículo.
Si deseas desbloquear el link de descarga del código Android, entonces sigue estas instrucciones:
[sociallocker id=»7121″][/sociallocker]
1. Transiciones Entre Actividades
¿Qué es una transición?
Google dice:
«Las transiciones en aplicaciones con Material Design proveen conexiones visuales entre diferentes estados, a través del movimiento y transformación de elementos en común.»
Puedes mostrar transiciones al inicio o terminación (Enter-Exit) de una actividad. Donde los elementos de las actividades saldrán o entrarán con un patrón determinado.
Dicho patrón puede ser definido por tí a través de la clase Transition. O puedes usar aquellos que ya están prefabricados por el framework de Android.
Aunque para nosotros a simple vista hay transiciones de entrada y salida, Android comprende otros dos estados llamados Retorno y Reingreso (Reenter – Return) cuando el back button es presionado.
Por otro lado, dos actividades pueden compartir uno o más views que se mantengan a través de la transición.
Esta convención visual permite mantener una retroalimentación de las acciones del usuario. Lo que incrementa la experiencia de usuario y el significado de las acciones dentro de la app.
La siguiente aplicación muestra como la miniatura de la galería es redimensionada a medida que aparece la actividad de detalle.
Existen tres animaciones predefinidas que puedes usar en tus aplicaciones:
- Explode: Expulsa o Atrae los views desde o hacia el centro de la actividad.
- Slide: Mueve los views en conjunto hacia algún borde de la actividad (superior, inferior, derecho, izquierdo, etc).
- Fade: Desvanece o resalta poco a poco los views dentro de la actividad.
Crear Transiciones En Android
Antes de crear transiciones debes habilitarlas para que funcionen.
Una de las formas de hacerlo es crear un archivo values-v21/styles.xml y añadir el atributo android:windowContentTransitions
con el valor de true como muestra el código de abajo.
<?xml version="1.0" encoding="utf-8"?> <resources> <style name="AppTheme" parent="android:Theme.Material"> <!--Habilitar animaciones en las transiciones --> <item name="android:windowContentTransitions">true</item> <!-- Más definiciones... --> </style> </resources>
Análogamente puedes activar las transiciones de forma programática con el método Window.requestFeature()
a través de la bandera Window.FEATURE_CONTENT_TRANSITIONS
.
// Dentro de tu actividad getWindow().requestFeature(Window.FEATURE_CONTENT_TRANSITIONS);
Las transiciones pueden ser definiciones xml almacenadas en la carpeta /res/transition. Dentro los archivos de transición podemos incluir un nodo del tipo <transitionSet>
para combinar un conjunto de transiciones. O simplemente declarar una transición individual.
Por ejemplo…
El código siguiente declara una transición del tipo Explode con 2 segundos de duración.
explode_transition.xml
<?xml version="1.0" encoding="utf-8"?> <transitionSet xmlns:android="http://schemas.android.com/apk/res/android"> <explode android:duration="2000"/> </transitionSet>
Así mismo puedes usar las clases Slide y Fade para representar los otros tipos de transición.
Luego asignas la animación en los estados correspondientes:
<!-- Estados Entrada-Salida --> <item name="android:windowEnterTransition">@transition/explode_transition</item> <item name="android:windowExitTransition">@transition/explode_transition</item> <!-- Estados Reingreso-Retorno --> <item name="android:windowReenterTransition">@transition/fade_transition</item> <item name="android:windowReturnTransition">@transition/fade_transition</item>
Como ya lo supones, los elementos anteriores se refieren a cada una de las interacciones que vimos al inicio del artículo.
Si no seteas transiciones para el reingreso y retorno, el framework de android reutilizará las de entrada-salida, solo que las reproducirá desde el final hacia el inicio.
Para las transiciones con elementos compartidos es el mismo proceso. Usamos los atributos android:windowSharedElement*
.
<!-- Estados entrada-salida --> <item name="android:windowSharedElementEnterTransition">@transition/shared_explode</item> <item name="android:windowSharedElementExitTransition">@transition/shared_explode</item> <!-- Estados ingreso-retorno --> <item name="android:windowSharedElementReenterTransition">@transition/shared_fade</item> <item name="android:windowSharedElementReturnTransition">@transition/shared_fade</item>
Poner transiciones en el código: A través de los métodos set*Transition()
que proporciona el componente Window puedes cumplir el mismo cometido anterior.
Por ejemplo…
Si quieres añadir una transición Explode
para la salida de la actividad actual usa el método setExitTransition()
.
Explode explode = new Explode(); explode.setDuration(500); // Duración en milisegundos getWindow().setExitTransition(explode);
Si deseas la entrada, entonces usa setEnterTransition()
. Si deseas establecer el retorno, usa setReturnTransition()
y así sucesivamente.
Ahora, para las transiciones compartidas, usa los métodos setSharedElement*Transition()
de la misma forma que los anteriores.
Iniciar una Activity con transiciones: Como ya sabes, una actividad se inicia con el método startActivity()
. No obstante debemos indicar el inicio de la transición con el resultado del método ActivityOptions.makeSceneTransitionAnimation() en el segundo parámetro
.
Intent startIntent = new Intent(this, OtraActividad.class); startActivity(startIntent, ActivityOptions.makeSceneTransitionAnimation(this).toBundle());
Para terminar manualmente una actividad y que esta reproduzca su transición de salida usa el método Activity.finishAfterTransition()
y no el método finish()
.
Superponer las transiciones: Por defecto las transiciones entre actividades se superponen. Esto quiere decir que en medio de la animación, las transiciones de entrada-salida o reingreso-retorno se combinan para producir un efecto de continuidad natural.
Usa el los atributos android:windowAllowEnterTransitionOverlap
, android:windowAllowReturnTransitionOverlap
con el valor de false
, para que una transición no empiece si la otra no ha acabado de reproducirse.
<!-- Deshabilitar sobreposición entre animaciones--> <item name="android:windowAllowEnterTransitionOverlap">false</item> <item name="android:windowAllowReturnTransitionOverlap">false</item>
Para transiciones de elementos compartidos también existe un atributo similar. Pero esta vez, cumple la función de superponer los views compartidos sobre todas las transiciones. Esto evita que no desaparezcan de la escena.
<!-- Habilitar superposición de los elementos compartidos entre transiciones --> <item name="android:windowSharedElementsUseOverlay">true</item>
Usar transiciones definidas en xml desde el código: En ocasiones necesitarás usar dinámicamente una transición personalizada que se encuentra en un archivo xml.
La solución a este problema es usar la clase TransitionInflater
. Este elemento es el encargado de transformar las definiciones de una transición en objetos java.
Veamos un ejemplo…
Transition boomExplode = TransitionInflater.from(this) .inflateTransition(R.transition.boom_explode); window.setEnterTransition(boomExplode);
Obtén su instancia a través del método estático TransitionInflater.from()
y luego infla el recurso xml con TransitionInflater.inflateTransition
.
En seguida puedes setear el resultado en cualquier estado de las transiciones.
Usar Transiciones Con Elementos Compartidos
Ahora verás la lógica para compartir un elemento entre dos actividades de forma que varíe sus características y se ajuste a lo largo de la transición de forma coordinada.
Para ello haz lo siguiente:
- Marca el view en ambas actividades con el atributo
android:transitionName
. Este será un nombre único que mantendrá la relación del elemento. - Setea una escucha
OnClickListener
al view que recibe el evento para activar la transición. El mejor caso, es que el view que reciba el click sea el que se compartirá. Por otro lado, puede que el elemento esté contenido dentro de otro view ó que sea el elemento de unViewGroup
como una lista. - Inicia la transición con el método
makeSceneTransitionAnimation()
enviando como segundo parámetro el view compartido y la marca en el tercer parámetro.
Por ejemplo… el siguiente código obtiene un ImageView
que al ser presionado es compartido entre dos actividades:
userImage = findViewById(R.id.user_avatar); userImage.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent i = new Intent(MainActivity.this, DetailActivity.class); ActivityOptions options = ActivityOptions .makeSceneTransitionAnimation(MainActivity.this, v, v.getTransitionName()); startActivity(i, options.toBundle()); } });
Como ves, se usa como segundo parámetro el view que ingresa en onClick()
, ya que se refiere a sí mismo. Y el nombre de transición puede obtenerse a través del método getTransitionName()
.
¿Cómo empezar una actividad con múltiples views compartidos?
Luego de marcar cada view en su layout, define pares de elementos view-nombreDeTransición a través de la clase Pair y setealos secuencialmente dentro de makeSceneTransitionAnimation().
ActivityOptions options = ActivityOptions.makeSceneTransitionAnimation(this, Pair.create(view1, view1.getTransitionName()), Pair.create(view2, view2.getTransitionNane());
Puedes usar la cantidad de elementos que necesites, solo crea pares nuevos con Pair.create()
. El primer parámetro será el view a compartir y el segundo su nombre de transición.
Manejar Eventos De Una Transición
Gestiona los eventos de una transición con la escucha OnTransitionListener
. Esta clase proporciona controladores para dictaminar acciones en caso de que una transición inicie, termine, se cancele, etc.
Un buen ejemplo de utilización de la escucha OnTransitionListener se ve en el repositorio Material Animations. Donde luego de terminar una transición de elementos compartidos, se inicia un efecto de revelación cicular.
Añade la escucha a una transición a través del método Transition.addListener()
y luego crea una clase anónima para sobrescribir los controladores.
Fade fade = new Fade(); fade.addListener( new Transition.TransitionListener() { @Override public void onTransitionStart(Transition transition) { } @Override public void onTransitionEnd(Transition transition) { } @Override public void onTransitionCancel(Transition transition) { } @Override public void onTransitionPause(Transition transition) { } @Override public void onTransitionResume(Transition transition) { } } );
Con ello tendrás la capacidad de dirigir el flujo de la transición y mejorar tus efectos visuales.
Ejemplo De Transiciones En Android
La aplicación a desarrollar como ejemplo se llama «Cursos Point». Su objetivo es mostrar una lista de cursos online en una actividad principal, los cuales al ser presionados abrirán una actividad de detalle.
La idea es que al pasar entre actividades, se activen diferentes transiciones que te permitan conocer su comportamiento. Esto nos dará la oportunidad de aplicar los temas que vimos en el artículo.
¡Empezemos!
Paso 1. Abre Android Studio y crea un nuevo proyecto llamado «Cursos Point». Cuando se te pregunte por qué tipo de actividad deseas agregar para comenzar, selecciona Blank Activity.
Usa el nombre de CourseActivity.java para la clase java y activity_course.xml para el layout.
Paso 2. Ve al archivo build.gradle y añade las dependencias del código de abajo.
build.gradle
dependencies { ... compile 'com.android.support:appcompat-v7:22.2.0' compile 'com.android.support:recyclerview-v7:22.2.0' compile 'com.android.support:design:22.2.0' compile 'com.github.bumptech.glide:glide:3.6.1' compile 'com.android.support:support-v4:22.2.0' compile 'com.android.support:cardview-v7:22.2.0' }
Las librerías reflejan varios aspectos importantes de nuestro proyecto.
- Se debe crear una lista a través de un recycler view.
- Cargaremos imágenes en segundo plano con Glide.
- Tendremos cards en la interfaz.
Paso 3. Crea el archivo /res/values/colors.xml para especificar la paleta de colores que usaremos en el estilo Material Design.
colors.xml
<?xml version="1.0" encoding="utf-8"?> <resources> <color name="window_background">#EEEEEE</color> <color name="primaryDarkColor">#00796B</color> <color name="primaryColor">#009688</color> <color name="accentColor">#FFC107</color> </resources>
Paso 4. Lo siguiente es modificar el archivo /res/values/styles.xml. Dentro de él, asigna los colores a un tema base llamado «CursosPoint.Base». Luego aplica herencia de estilos para definir el tema de la aplicación.
<resources> <style name="CursosPoint" parent="CursosPoint.Base"/> <style name="CursosPoint.Base" parent="Theme.AppCompat.Light.NoActionBar"> <item name="android:windowBackground">@color/window_background</item> <item name="colorPrimaryDark">@color/primaryDarkColor</item> <item name="colorPrimary">@color/primaryColor</item> <item name="colorAccent">@color/accentColor</item> </style> </resources>
Este recurso aplicará para versiones anteriores de android.
Para las versiones mayores a 21, crea una nueva carpeta llamada /res/values-21. Luego crea un archivo styles.xml y habilita las transiciones.
values-21/styles.xml
<?xml version="1.0" encoding="utf-8"?> <resources> <style name="CursosPoint" parent="CursosPoint.Base"> <!--Habilitar animaciones en las transiciones --> <item name="android:windowContentTransitions">true</item> <!-- Deshabilitar sobreposición entre animaciones--> <item name="android:windowAllowEnterTransitionOverlap">false</item> <item name="android:windowAllowReturnTransitionOverlap">false</item> <!-- Transiciones para entrada, salida y elementos compartidos...--> <!-- Habilitar superposición de los elementos compartidos entre transiciones --> <item name="android:windowSharedElementsUseOverlay">true</item> </style> </resources>
Como ves, el estilo hereda del tema base para mantener los colores que declaramos, solo que esta vez añadimos las características de transiciones que vimos en los apartado anteriores.
Paso 5. Declara las siguientes dimensiones dentro del archivo /res/values/dimens.xml.
<resources> <!-- Default screen margins, per the Android Design guidelines. --> <dimen name="activity_horizontal_margin">16dp</dimen> <dimen name="activity_vertical_margin">16dp</dimen> <dimen name="size_fab">56dp</dimen> <dimen name="fab_margin">16dp</dimen> <dimen name="card_margin">16dp</dimen> </resources>
Paso 6. Descarga los drawables que usaremos dentro de la aplicación. En este caso usaremos imágenes para los elementos de la lista y para el detalle. La imagen será el elemento que compartamos entre las transiciones.
Crear Una Lista Con RecyclerView
Paso 7. Crea una nueva clase java con el nombre de Course.java. Este archivo representa la fuente de datos que usaremos como unidad de la lista. Dentro de esta clase añadiremos campos populares referentes a un curso online.
Course.java
/** * POJO para los cursos */ public class Course { private String name; private String description; private String author; private float rating; private int students; private float price; private int idImage; public Course(String name, String description, String author, float rating, int students, float price, int idImage) { this.name = name; this.description = description; this.author = author; this.rating = rating; this.students = students; this.price = price; this.idImage = idImage; } public String getName() { return name; } public String getDescription() { return description; } public String getAuthor() { return author; } public float getRating() { return rating; } public int getStudents() { return students; } public float getPrice() { return price; } public int getIdImage() { return idImage; } }
Paso 8. Lo siguiente es crear datos de prueba para poblar la lista. Esto requiere que crees una nueva clase llamada Courses.java que represente el modelo de datos de la aplicación.
import java.util.Arrays; import java.util.List; /** * Creado por Hermosa Programación. */ public class Courses { private static Course[] courses = { new Course("Curso Online De Seo Para Principiantes", "Aprende prácticas para optimizar tanto internos como externos de tu sitio " + "web con el fin de recibir mas tráfico desde los motores de búsquedan" + "n" + "Más de 30 clases n" + "12 horas de contenido", "Carlos Villa", 3f, 4340, 22, R.drawable.curso_online_seo), new Course("La ciencia del Marketing Online", "Obtén este curso y aprende paso a paso como crear un negocio que genere" + " ingresos constantes lo más pronto posible.n" + "n" + "20 excelentes clasesn" + "n" + "Plantillas para inexpertosn", "Elena Maiguel", 5f, 220, 24, R.drawable.curso_online_marketing), new Course("Publicidad Rápida y Furiosa", "Con este curso obtendrás todos los secretos para generar campañas " + "publicitarias que tus clientes no puedan resistirse.n" + "n" + "45 clases didácticasn" + "n" + "Desarrolla tu creatividad y asertividad comercialn", "PricePart", 4.4f, 34235, 32, R.drawable.curso_online_publicidad), new Course("Aumentando el control de mis finanzas", "Curso introductorio sobre finanzas personales. " + "Aprende a gestionar tus recursos financieros a " + "través de una estrategia de planificación sencilla y probada.n" + "n" + "¡Más de 13 clases y 20 horas de contenido!n" + "n" + "Satisfacción garantizada", "Academia Money", 3.4f, 11245, 35, R.drawable.curson_online_finanzas), new Course("Coaching Extremo", "Aprende a conseguir resultados, alcanzar metas, cooperar " + "con otras personas y a motivar su entorno.n" + "n" + "23 clases dividas en 10 horas n" + "Ejemplo prácticos", "Internaut Perri", 4.0f, 122, 45, R.drawable.curso_online_coaching), new Course("¿Cómo sacar máximo provecho a las redes sociales?", "Aprende a gestionar y manejar eficazmente las comunidades " + "sociales. Automatiza tareas, crea contenidos " + "interesantes y saca el mejor provecho de las análiticas.n" + "n" + "Plantillas descargables para planificación.n" + "21 Infografías potentes para simplificar tu acción", "Milo Alino", 3.2f, 2503, 34, R.drawable.curso_online_community_manager), }; /** * Obtiene como lista todos los cursos de prueba * * @return Lista de cursos */ public static List<Course> getCourses() { return Arrays.asList(courses); } /** * Obtiene un curso basado en la posición del array * * @param position Posición en el array * @return Curso seleccioando */ public static Course getCourseByPosition(int position) { return courses[position]; } }
Paso 9. Ve a /res/layout y añade un nuevo layout llamado list_item.xml. El propósito de este archivo es proveer el diseño para los ítems de la lista.
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:foreground="?attr/selectableItemBackground" android:minHeight="?android:attr/listPreferredItemHeight"> <TextView android:id="@+id/name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentTop="true" android:layout_marginLeft="16dp" android:layout_toRightOf="@+id/image" android:text="Nombre" android:textAppearance="?android:attr/textAppearanceMedium" android:textColor="@android:color/black" android:textSize="15sp" /> <TextView android:id="@+id/author" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignLeft="@+id/name" android:layout_below="@+id/name" android:text="Autor" /> <ImageView android:id="@+id/image" android:layout_width="120dp" android:layout_height="120dp" android:layout_alignParentLeft="true" android:layout_alignParentTop="true" android:src="@drawable/curso_online_seo" android:transitionName="shared_image" /> <TextView android:id="@+id/price" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_alignParentRight="true" android:padding="16dp" android:text="Precio" android:textAppearance="?android:attr/textAppearanceMedium" android:textColor="?colorPrimary" /> <TextView android:id="@+id/students" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignBottom="@+id/image" android:layout_toRightOf="@+id/image" android:padding="16dp" android:text="Estudiantes" android:textAppearance="?android:attr/textAppearanceSmall" /> <RatingBar android:id="@+id/rating" style="?android:attr/ratingBarStyleSmall" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignLeft="@+id/author" android:layout_below="@+id/author" android:layout_toRightOf="@+id/image" android:isIndicator="true" android:paddingBottom="8dp" android:paddingTop="8dp" android:progressTint="#FDDB39" android:rating="3" android:secondaryProgressTint="#FDDB39" /> </RelativeLayout>
¿Has visto el atributo android:transitionName en la image view?
Usamos el identificador «shared_image» como marca.
Paso 10. Ahora es el turno de crear el RecyclerView.Adapter personalizado para inflar la lista.
CourseAdapter.java
import android.app.Activity; import android.content.Context; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.RatingBar; import android.widget.TextView; import com.bumptech.glide.Glide; import java.util.List; /** * {@link android.support.v7.widget.RecyclerView.Adapter} para la lista de elementos */ public class CourseAdapter extends RecyclerView.Adapter<CourseAdapter.CourseViewHolder> implements ItemClickListener { private final Context context; private List<Course> items; public CourseAdapter(Context context, List<Course> items) { this.context = context; this.items = items; } @Override public int getItemCount() { return items.size(); } @Override public CourseViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) { View v = LayoutInflater.from(viewGroup.getContext()) .inflate(R.layout.list_item, viewGroup, false); return new CourseViewHolder(v, this); } @Override public void onBindViewHolder(CourseViewHolder viewHolder, int i) { // Item procesado actualmente Course currentItem = items.get(i); viewHolder.name.setText(currentItem.getName()); viewHolder.author.setText(currentItem.getAuthor()); viewHolder.price.setText("$" + currentItem.getPrice()); viewHolder.rating.setRating(currentItem.getRating()); viewHolder.students.setText(currentItem.getStudents() + " Estudiantes"); // Cargar imagen Glide.with(context) .load(currentItem.getIdImage()) .into(viewHolder.image); } @Override public void onItemClick(View view, int position) { // Imagen a compartir entre transiciones View sharedImage = view.findViewById(R.id.image); DetailActivity.launch( (Activity) context, position, sharedImage); } /** * View holder para reciclar elementos */ public static class CourseViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener { // Views para un curso public final TextView name; public final TextView author; public final TextView price; public final RatingBar rating; public final TextView students; public final ImageView image; // Interfaz de comunicación public ItemClickListener listener; public CourseViewHolder(View v, ItemClickListener listener) { super(v); name = (TextView) v.findViewById(R.id.name); author = (TextView) v.findViewById(R.id.author); price = (TextView) v.findViewById(R.id.price); rating = (RatingBar) v.findViewById(R.id.rating); students = (TextView) v.findViewById(R.id.students); image = (ImageView) v.findViewById(R.id.image); v.setOnClickListener(this); this.listener = listener; } @Override public void onClick(View v) { listener.onItemClick(v, getAdapterPosition()); } } } interface ItemClickListener { void onItemClick(View view, int position); }
Algo importante a resaltar en el código anterior es la creación de una interfaz de comunicación entre el adaptador y el view holder llamada ItemClickListener.
Esta clase permite que se añadan escuchas OnClickListener a los elementos del view holder, de tal forma que se relacionen con la posición dentro del adaptador.
El adaptador debe implementar esta interfaz y sobrescribir el controlador onItemClick(). Si te fijas bien, dentro de este se llama al método estático DetailActivity.launch().
El tercer parámetro recibe la imagen que compartiremos entre actividades.
Crear Actividad De Detalle
Paso 11. Ve a File > New > Activity > Blank Activity para crear una nueva actividad. Ponle el nombre de DetailActivity.java y conviértela en hija de CourseActivity.java.
Paso 12. Revisa que el archivo AndroidManifest.xml tenga la dependencia entre ambas actividades como se ve en el siguiente código.
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.herprogramacion.cursospoint" > <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:theme="@style/CursosPoint" > <activity android:name=".CourseActivity" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:name=".DetailActivity" android:label="@string/title_activity_detail" android:parentActivityName=".CourseActivity" > <meta-data android:name="android.support.PARENT_ACTIVITY" android:value="com.herprogramacion.cursospoint.CourseActivity" /> </activity> </application> </manifest>
Paso 13. Modifica el layout de la actividad de detalle con el siguiente diseño:
activity_detail.xml
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/coordinator" android:layout_width="match_parent" android:layout_height="match_parent"> <android.support.v4.widget.NestedScrollView android:id="@+id/scroll" android:layout_width="match_parent" android:layout_height="match_parent" app:layout_behavior="@string/appbar_scrolling_view_behavior"> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin"> <TextView android:id="@+id/detail_name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Nombre" android:textAppearance="?android:attr/textAppearanceMedium" /> <TextView android:id="@+id/detail_price" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Precio" android:textColor="?colorPrimary" android:textStyle="bold" /> <TextView android:id="@+id/tag_description" android:layout_width="wrap_content" android:layout_height="wrap_content" android:paddingTop="16dp" android:text="DESCRIPCIÓN" android:textColor="?colorPrimary" /> <TextView android:id="@+id/detail_description" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Descripción" /> <RatingBar android:id="@+id/detail_rating" style="?android:attr/ratingBarStyleSmall" android:layout_width="wrap_content" android:layout_height="wrap_content" android:clickable="true" android:isIndicator="true" android:paddingTop="8dp" android:progressTint="#FDDB39" android:rating="3" android:secondaryProgressTint="#FDDB39" /> <TextView android:id="@+id/detail_author" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Autor" /> </LinearLayout> </android.support.v4.widget.NestedScrollView> <!-- App Bar --> <android.support.design.widget.AppBarLayout android:id="@+id/app_bar" android:layout_width="match_parent" android:layout_height="256dp" android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"> <!-- Toolbar --> <android.support.v7.widget.Toolbar xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" app:popupTheme="@style/ThemeOverlay.AppCompat.Light" app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" /> <!-- Imagen del detalle --> <ImageView android:id="@+id/detail_image" android:layout_width="match_parent" android:layout_height="match_parent" android:transitionName="shared_image" /> </android.support.design.widget.AppBarLayout> <!-- FAB --> <android.support.design.widget.FloatingActionButton android:id="@+id/fab" android:layout_width="@dimen/size_fab" android:layout_height="@dimen/size_fab" android:layout_margin="@dimen/fab_margin" android:src="@drawable/ic_basket" app:borderWidth="0dp" app:elevation="@dimen/fab_elevation" app:layout_anchor="@id/app_bar" app:layout_anchorGravity="bottom|right|end" /> </android.support.design.widget.CoordinatorLayout>
El diseño requiere del uso de la App Bar para añadir la imagen de cabecera. Además debes recubrir los componentes con un CoordinatorLayout
para permitir la ubicación del floating action button.
Importante… ¿viste la marca de la imagen de cabecera?
Tiene el mismo valor: "shared_image"
. Esto asegura que la imagen del item sea redimensionada hasta la posición de la imagen de cabecera.
Paso 14. Crea la carpeta /res/transition-v21 e incluye una nueva transición llamada detail_enter_transition.xml. Esta transición será inflada en la actividad de detalle para una de las muestras que usaremos.
detail_enter_transition.xml
<?xml version="1.0" encoding="utf-8"?> <transitionSet xmlns:android="http://schemas.android.com/apk/res/android" android:duration="500" android:transitionOrdering="together"> <slide android:slideEdge="right"> <targets> <target android:targetId="@android:id/navigationBarBackground" /> </targets> </slide> <slide android:slideEdge="left"> <targets> <target android:targetId="@android:id/statusBarBackground" /> </targets> </slide> <slide android:slideEdge="top"> <targets> <target android:targetId="@+id/fab" /> </targets> </slide> </transitionSet>
Dentro de este archivo es posible definir como se comportarán los elementos de la interfaz. Para referirte a un elemento específico usa la etiqueta <targets>
y dentro de ella usa <target>
.
<target>
tiene un atributo llamado android:targetId
, que te permite referenciar por el indentificador al view que desees.
En nuestro caso, se aplica un slide a la barra de navegación. Con el atributo android:slideEdge="right"
indicamos que sería a la derecha.
Luego usamos un slide hacia el borde izquierdo para la status bar.
Y por último un slide hacia el borde superior para el floating action button.
Adicionalmente se indicó al elemento <transitionSet>
una duración de 500ms (android:duration
) y que las transiciones se reproduzcan al mismo tiempo(android:transitionOrdering="together"
).
Paso 15. Abre la actividad DetailActivity.java y añade la siguiente implementación.
import android.app.Activity; import android.app.ActivityOptions; import android.app.TaskStackBuilder; import android.content.Intent; import android.os.Build; import android.os.Bundle; import android.support.design.widget.Snackbar; import android.support.v4.app.NavUtils; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; import android.transition.Explode; import android.transition.Fade; import android.transition.Slide; import android.transition.Transition; import android.transition.TransitionInflater; import android.view.Gravity; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.Window; import android.widget.ImageView; import android.widget.RatingBar; import android.widget.TextView; import com.bumptech.glide.Glide; public class DetailActivity extends AppCompatActivity { private static final String EXTRA_POSITION = "com.herprogramacion.cursospoint.extra.POSITION"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_detail); setToolbar(); // Reemplazar la action bar // Se obtiene la posición del item seleccionado int position = getIntent().getIntExtra(EXTRA_POSITION, -1); // Carga los datos en la vista setupViews(position); Window window = getWindow(); // Elegir transiciones switch (position) { // EXPLODE case 0: Explode t0 = new Explode(); window.setEnterTransition(t0); break; // SLIDE case 1: Slide t1 = new Slide(); t1.setSlideEdge(Gravity.END); window.setEnterTransition(t1); break; // FADE case 2: Fade t2 = new Fade(); window.setEnterTransition(t2); break; // PERSONALIZADA case 3: Transition t3 = TransitionInflater.from(this) .inflateTransition(R.transition.detail_enter_trasition); window.setEnterTransition(t3); break; // EVENTOS DE TRANSICIÓN case 4: Fade t4 = new Fade(); t4.addListener( new Transition.TransitionListener() { @Override public void onTransitionStart(Transition transition) { } @Override public void onTransitionEnd(Transition transition) { Snackbar.make( findViewById(R.id.coordinator), "Terminó la transición", Snackbar.LENGTH_SHORT) .show(); } @Override public void onTransitionCancel(Transition transition) { } @Override public void onTransitionPause(Transition transition) { } @Override public void onTransitionResume(Transition transition) { } } ); window.setEnterTransition(t4); break; // POR DEFECTO case 5: window.setEnterTransition(null); break; } } private void setupViews(int position) { TextView name = (TextView) findViewById(R.id.detail_name); TextView description = (TextView) findViewById(R.id.detail_description); TextView author = (TextView) findViewById(R.id.detail_author); TextView price = (TextView) findViewById(R.id.detail_price); RatingBar rating = (RatingBar) findViewById(R.id.detail_rating); ImageView image = (ImageView) findViewById(R.id.detail_image); Course detailCourse = Courses.getCourseByPosition(position); name.setText(detailCourse.getName()); description.setText(detailCourse.getDescription()); author.setText("Creado Por:" + detailCourse.getAuthor()); price.setText("$" + detailCourse.getPrice()); rating.setRating(detailCourse.getRating()); Glide.with(this).load(detailCourse.getIdImage()).into(image); } private void setToolbar() { Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); if (getSupportActionBar() != null)// Habilitar Up Button getSupportActionBar().setDisplayHomeAsUpEnabled(true); } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { default: return super.onOptionsItemSelected(item); case android.R.id.home: // Obtener intent de la actividad padre Intent upIntent = NavUtils.getParentActivityIntent(this); upIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); // Comprobar si DetailActivity no se creó desde CourseActivity if (NavUtils.shouldUpRecreateTask(this, upIntent) || this.isTaskRoot()) { // Construir de nuevo la tarea para ligar ambas actividades TaskStackBuilder.create(this) .addNextIntentWithParentStack(upIntent) .startActivities(); } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { // Terminar con el método correspondiente para Android 5.x this.finishAfterTransition(); return true; } // Dejar que el sistema maneje el comportamiento del up button return false; } } @Override public boolean onCreateOptionsMenu(Menu menu) { return super.onCreateOptionsMenu(menu); } public static void launch(Activity context, int position, View sharedView) { Intent intent = new Intent(context, DetailActivity.class); intent.putExtra(EXTRA_POSITION, position); // Los elementos 4, 5 y 6 usan elementos compartidos, if (position >= 3) { ActivityOptions options0 = ActivityOptions .makeSceneTransitionAnimation(context, sharedView, sharedView.getTransitionName()); context.startActivity(intent, options0.toBundle()); } else { ActivityOptions options0 = ActivityOptions.makeSceneTransitionAnimation(context); context.startActivity(intent, options0.toBundle()); } } }
Veamos un poco de que van las piezas de código que tenemos.
setToolbar()
: Reemplaza la action bar por la toolbar que tenemos definida en el layout. Además habilita el up button para la navegación hacia atrás.setupViews()
: Obtiene las instancias de los views del layout y luego setea los valores correspondientes según el curso obtenido con la posición que entró como parámetro.launch()
: Inicia una instancia prefabricada deDetailActivity
. Recibe como parámetros la actividad desde donde se inicia, la posición del ítem de la lista y el view que se compartirá en la transición. Para propósitos educativos, solo los ítems 4, 5 y 6 podrán compartir la imagen
Superimportante resaltar que estamos usando un switch
en onCreate()
, para determinar que transiciones usaremos de acuerdo a la posición obtenida desde el valor extra del intent comunicado con launch()
.
El número de posición reproduce las siguientes variaciones:
- Cero: Transición tipo
Explode
. - Uno: Transición tipo
Slide
con movimiento de izquierda a derecha (Gravitiy.END
). - Dos: Transición tipo
Fade
. - Tres: Transición personalizada.
- Cuatro: Uso de eventos de transición para desplegar una
SnackBar
al terminar la animación. - Cinco: Transición por defecto del sistema.
Paso 16. Abre el layout de la actividad principal para añadir la toolbar y el recycler view que la compondrán.
activity_course.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <android.support.v7.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" android:background="?attr/colorPrimary" app:popupTheme="@style/ThemeOverlay.AppCompat.Light" app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" /> <android.support.v7.widget.RecyclerView android:id="@+id/reciclador" android:layout_width="match_parent" android:layout_height="match_parent" android:scrollbars="vertical" /> </LinearLayout>
Paso 17. Finalmente modifica la actividad principal para que relacione el recycler view con un linear layout.
CourseActivity.java
import android.annotation.TargetApi; import android.os.Build; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.Toolbar; import android.transition.Explode; import android.view.Menu; import android.view.MenuItem; public class CourseActivity extends AppCompatActivity { public RecyclerView recyclerView; public LinearLayoutManager linearLayout; @TargetApi(Build.VERSION_CODES.LOLLIPOP) @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_course); setToolbar(); // Reemplazar toolbar setupRecyclerView(); // Preparar recycler view setupWindowAnimations(); // Añadir animaciones } private void setupRecyclerView() { linearLayout = new LinearLayoutManager(this); recyclerView = (RecyclerView) findViewById(R.id.reciclador); recyclerView.setLayoutManager(linearLayout); CourseAdapter adapter = new CourseAdapter(this, Courses.getCourses()); recyclerView.setAdapter(adapter); } private void setToolbar() { Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); } private void setupWindowAnimations() { getWindow().setReenterTransition(new Explode()); getWindow().setExitTransition(new Explode().setDuration(500)); } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.menu_course, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { int id = item.getItemId(); if (id == R.id.action_settings) { return true; } return super.onOptionsItemSelected(item); } }
Paso 18. Corre la aplicación en Android Studio y prueba cada curso online para ver su transición.
Conclusión
Las transiciones son elementos visuales que comunican retroalimentación a los usuarios, con el fin de mantener congruencia e integridad entre las acciones que realizan dentro de la aplicación.
El Material Design ha establecido una gran cantidad de conceptos muy cercanos a la realidad física, dándonos otra perspectiva sobre lo que es la interfaz de un software.
Ya sabiendo cómo implementar transiciones, ahora puedes experimentar creando coreografías atractivas que potencialicen tus aplicaciones.
Te recomiendo visites el proyecto Material Up. Este sitio web contiene una gran cantidad de conceptos, aplicaciones, desarrollos e ideas sobre Material Design. Encontrarás mucha inspiración.
También puedes combinar las transiciones con los nuevos elementos de la librería de diseño, como lo son el Navigation View, el Tab Layout y la App Bar.
Lista de créditos para las imágenes que usamos en la aplicación. ¡Gracias Freepick!:
- Imagen marketing digital
- Imagen Seo
- Imagen publicidad
- Imagen finanzas
- Imagen Community Manager
- Imagen Coaching
- Imagen Inversiones
Únete Al Discord De Develou
Si tienes problemas con el código de este tutorial, preguntas, recomendaciones o solo deseas discutir sobre desarrollo Android conmigo y otros desarrolladores, únete a la comunidad de Discord de Develou y siéntete libre de participar como gustes. ¡Te espero!