Interfaz de usuario en Android: CardView

Junto a Android 5.0 Lollipop nos llegó un nuevo componente que, si bien se llevaba utilizando ya algún tiempo en diversas aplicaciones, no tenía soporte directo en el SDK. Este nuevo componente llamado CardView es la implementación que nos proporciona Google del elemento visual en forma de tarjetas de información que tanto utiliza en muchas de sus aplicaciones, entre ellas Google Now, quizá la que más a ayudado a popularizar este componente.

Hasta la llegada de Android 5.0, para utilizar estas “tarjetas” en la interfaz de nuestras aplicaciones teníamos que recurrir a librerías de terceros o bien trabajar un poco para implementar nuestra propia versión. Sin embargo ahora las tenemos disponibles en forma de nueva librería de soporte oficial, proporcionada junto al SDK de Android.

Para hacer uso de la librería tan sólo tendremos que hacer referencia a ella en la sección de dependencias del fichero build.gradle del módulo principal de la aplicación:

dependencies {
    ...
    compile 'com.android.support:cardview-v7:21.0.+'
}

Una vez incluida la referencia a la librería, la utilización del componente es muy sencilla. Tan sólo tendremos que añadir a nuestro layout XML un control de tipo <android.support.v7.widget.CardView> y establecer algunas de sus propiedades principales. Yo por ejemplo le asignaré una altura de 200dp (layout_height), una anchura que se ajuste a su control padre (layout_width), y un color de fondo amarillo estilo post-it (cardBackgroundColor).

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:card_view="http://schemas.android.com/apk/res-auto"
    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"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <android.support.v7.widget.CardView
        android:id="@+id/card2"
        android:layout_width="match_parent"
        android:layout_height="200dp"
        card_view:cardBackgroundColor="#fffffe91" >

        <TextView
            android:id="@+id/txt1"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="10dp"
            android:text="@string/tarjeta_1" />

    </android.support.v7.widget.CardView>

</LinearLayout>

Por supuesto, como en el caso de cualquier otro contenedor (un CardView no es más que una extensión de FrameLayout con esquinas redondeadas y una sombra inferior), dentro de un CardView podemos añadir todos los controles que necesitemos. Como podéis ver en el código anterior, a modo de ejemplo he añadido tan solo una etiqueta de texto (TextView).

Si ejecutamos en este momento el ejemplo sobre un dispositivo o emulador con Android 4.x encontraremos el resultado que esperábamos:

demo1-cardview-android4

Sin embargo, si hacemos lo mismo sobre Android 5.x el resultado será el que se muestra en la siguiente imagen:

demo1-cardview-android5

¿Notáis la diferencia en la sombra en los bordes derecho e izquierdo de la tarjeta con respecto a la captura de Android 4? Efectivamente, la sombra casi no es visible por los laterales. Sin entrar en mucho detalle, tan sólo indicar que esto es debido a las diferencias en la forma de calcular y mostrar las sombras (ente otras cosas) en Android 5 respecto a versiones anteriores. Estas diferencias de comportamiento entre versiones del sistema operativo provocan que en casos como el mostrado para un CardView, los tamaños efectivos del contenido de la tarjeta y los márgenes o padding de la vista (utilizado en esta ocasión para dibujar la sombra) no coincidan entre versiones, lo que nos puede llevar a alguna que otra sorpresa. En este caso, la solución es sencilla, y pasa por utilizar una propiedad adicional de CardView llamada cardUseCompatPadding. Dando un valor true a esta propiedad conseguiremos que en en Android 5.x se añada un padding extra al CardView de forma que su representación en pantalla sea similar a la de versiones anteriores.

<android.support.v7.widget.CardView
    android:id="@+id/card2"
    android:layout_width="match_parent"
    android:layout_height="200dp"
    card_view:cardBackgroundColor="#fffffe91"
    card_view:cardUseCompatPadding="true" >

Con este simple cambio, conseguiremos un comportamiento muy similar tanto en Android 4 como en Android 5.

Además del color de fondo que ya hemos comentado, también podremos definir la elevación de la tarjeta y el radio de las esquinas redondeadas, utilizando las propiedades cardElevation y cardCornerRadius respectivamente.

No son muchas más las opciones de personalización que nos ofrece CardView, pero con poco esfuerzo deben ser suficientes para crear tarjetas más “sofisticadas”, jugando por supuesto también con el contenido de la tarjeta. Como ejemplo, si incluimos una imagen a modo de fondo (ImageView), y una etiqueta de texto superpuesta y alineada al borde inferior (layout_gravity=”bottom”) con fondo negro algo traslúcido (por ejemplo background=”#8c000000″) y un color y tamaño de texto adecuados, podríamos conseguir una tarjeta con el aspecto siguiente en Android 5.x:

demo2-cardview-android5

El código concreto para conseguir lo anterior sería el siguiente:

<android.support.v7.widget.CardView
    android:id="@+id/card1"
    android:layout_width="match_parent"
    android:layout_height="200dp"
    card_view:cardCornerRadius="6dp"
    card_view:cardElevation="10dp"
    card_view:cardUseCompatPadding="true" >

    <ImageView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:src="@drawable/city"
        android:scaleType="centerCrop"/>

    <TextView
        android:id="@+id/txt2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="10dp"
        android:text="@string/tarjeta_2"
        android:layout_gravity="bottom"
        android:background="#8c000000"
        android:textColor="#ffe3e3e3"
        android:textSize="30sp"
        android:textStyle="bold"/>

</android.support.v7.widget.CardView>

Todo parece correcto, pero si ejecutamos una vez más el ejemplo en un dispositivo/emulador con Android 4.x veremos lo siguiente:

demo2-cardview-android4

Nueva sorpresa. Como podemos ver, en Android 4.x y anteriores la imagen no llega al borde del CardView, sino que se introduce un pequeño margen adicional que evita que se tengan que hacer las operaciones necesarias para redondear las esquinas de la imagen. Esta operación de “redondeo”, aunque a priori pueda parecer simple, es bastante costosa. En Android 5.x puede realizarse sin afectar a la experiencia de usuario gracias a que muchas de las operaciones necesarias para mostrar la interfaz gráfica de la aplicación (animaciones, sombras, …) se realizan en un hilo de ejecución independiente del principal (render thread). En Android 4 y anteriores se evitan estas operaciones costosas dado que se ejecutarían en el hilo principal pudiendo afectar al rendimiento de la aplicación.

Para evitar este margen adicional en Android 4 y anteriores, puede asignarse el valor false a la propiedad cardPreventCornerOverlap del CardView.

<android.support.v7.widget.CardView
    android:id="@+id/card1"
    android:layout_width="match_parent"
    android:layout_height="200dp"
    card_view:cardCornerRadius="6dp"
    card_view:cardElevation="10dp"
    card_view:cardUseCompatPadding="true"
    card_view:cardPreventCornerOverlap="false" >

Esto evitará que se incluya el margen extra alrededor de la imagen, aunque no redondeará las esquinas de la imagen, quedando un efecto extraño respecto a la sombra del CardView si se ha utilizado un radio amplio para redondear las esquinas (en el ejemplo he utilizado un radio alto para que se note bien el efecto, si el radio es menor podría pasar más desapercibido).

demo2-cardview-android4-fix

Si queréis corregir completamente este efecto, aún a costa de afectar al rendimiento de la aplicación, podéis echar un vistazo a este gist de Gabriele Mariotti, donde utiliza un tipo de Drawable personalizado (RoundCornersDrawable) que redondea las esquinas de la imagen para adaptarla exactamente a los bordes y la sombra de un CardView en APIs < 21 (Android 5.0). De cualquier forma, antes de utilizar esta opción yo optaría por retocar el layout de forma que la imagen no ocupe todo el fondo de la tarjeta, eliminando así el problema planteado.

Puedes consultar y/o descargar el código completo de los ejemplos desarrollados en este artículo accediendo a la pagina del curso en GitHub.

Enlaces de interés: