En este documento, se explica cómo seguir el estilo de Glass y cómo implementar prácticas recomendadas comunes de la IU cuando se usa la GDK.
Tema de Glass
Glass aplica un tema estándar a tu Glassware, por lo que se mantiene coherente con el resto de la interfaz de usuario. El tema tiene las siguientes características:
- Usa el tipo de letra Roboto
- Muestra las actividades en pantalla completa sin barra de estado ni de acción
- Aplica un fondo negro sólido.
Para aplicar el tema de Glass, no declares un tema en tu manifiesto de Android.
Si tienes un estilo personalizado para partes de tu Glassware y quieres usar el tema predeterminado de Glass para todo lo demás, hereda de Theme.DeviceDefault
con el atributo parent
:
<resources>
<style name="CustomTheme" parent="@android:style/Theme.DeviceDefault">
<!-- Theme customization goes here. -->
</style>
</resources>
Consulta la guía para desarrolladores de Android sobre estilos y temas a fin de obtener más información sobre cómo crear temas.
Tarjetas con estilo de vidrio
La clase CardBuilder
crea tarjetas con el formato correcto según un conjunto de propiedades. Usa los diseños proporcionados por CardBuilder.Layout
siempre que sea posible para que tu contenido se vea y se sienta como otro contenido en Glass.
Para usar CardBuilder
, haz lo siguiente:
- Crea una instancia de
CardBuilder
y asígnale el diseño que desees desdeCardBuilder.Layout
. - Configura las propiedades de la tarjeta, como el texto, la nota al pie y la marca de tiempo.
- Llama a
CardBuilder.getView()
para convertir la tarjeta en unView
de Android, o aCardBuilder.getRemoteViews()
para convertirla en un objetoRemoteViews
. - Usa
View
en tus actividades, diseños oCardScrollView
, o usaRemoteViews
enLiveCard
.
Funciones comunes de la IU
Muchos de los diseños que proporciona CardBuilder
admiten las funciones comunes de la interfaz de usuario que se describen a continuación. Consulta la documentación de los diseños individuales en CardBuilder.Layout
para obtener una lista de las funciones compatibles con cada tipo de tarjeta.
Ícono de atribución
El ícono de atribución es un ícono opcional de 36 × 36 píxeles que aparece en la esquina inferior derecha de una tarjeta y a la derecha de la marca de tiempo. A fin de configurar este ícono, llama a CardBuilder.setAttributionIcon()
para identificar tu aplicación, especialmente en tarjetas activas, de modo que el usuario pueda ver rápidamente la fuente de la información en esa tarjeta.
Indicador de pila
El indicador de pila, controlado por CardBuilder.showStackIndicator()
, es un pliegue de esquina que aparece en la esquina superior derecha de una tarjeta. Úsalo como indicador visual de que tu tarjeta representa un paquete de otras tarjetas que el usuario puede presionar directamente.
View view = new CardBuilder(context, CardBuilder.Layout.TEXT)
.setText("A stack indicator can be added to the corner of a card...")
.setAttributionIcon(R.drawable.ic_smile)
.showStackIndicator(true)
.getView();
Diseños
En los siguientes ejemplos, se muestran los diseños disponibles con CardBuilder
.
TEXT
y TEXT_FIXED
El diseño CardBuilder.Layout.TEXT
muestra texto sin márgenes con un mosaico de imagen opcional en segundo plano. El texto cambia de tamaño de forma dinámica para adaptarse mejor al espacio disponible.
CardBuilder.Layout.TEXT_FIXED
es similar, pero corrige su texto en un tamaño más pequeño.
View view1 = new CardBuilder(context, CardBuilder.Layout.TEXT)
.setText("This is the TEXT layout. The text size will adjust dynamically.")
.setFootnote("This is the footnote")
.setTimestamp("just now")
.getView();
View view2 = new CardBuilder(context, CardBuilder.Layout.TEXT)
.setText("You can also add images to the background of a TEXT card.")
.setFootnote("This is the footnote")
.setTimestamp("just now")
.addImage(R.drawable.image1)
.addImage(R.drawable.image2)
.addImage(R.drawable.image3)
.addImage(R.drawable.image4)
.addImage(R.drawable.image5)
.getView();
View view3 = new CardBuilder(context, CardBuilder.Layout.TEXT_FIXED)
.setText("This is the TEXT_FIXED layout. The text size is always the same.")
.setFootnote("This is the footnote")
.setTimestamp("just now")
.getView();
COLUMNS
y COLUMNS_FIXED
El diseño CardBuilder.Layout.COLUMNS
muestra un ícono o mosaico de imagen en el lado izquierdo de la tarjeta y el texto en el lado derecho. El tamaño del texto se ajusta de forma dinámica para adaptarse mejor al espacio disponible. Para mantener fijo el tamaño del texto, usa CardBuilder.Layout.COLUMNS_FIXED
.
View view1 = new CardBuilder(context, CardBuilder.Layout.COLUMNS)
.setText("This is the COLUMNS layout with dynamic text.")
.setFootnote("This is the footnote")
.setTimestamp("just now")
.addImage(R.drawable.image1)
.addImage(R.drawable.image2)
.addImage(R.drawable.image3)
.addImage(R.drawable.image4)
.addImage(R.drawable.image5)
.getView();
View view2 = new CardBuilder(context, CardBuilder.Layout.COLUMNS)
.setText("You can even put a centered icon on a COLUMNS card instead of a mosaic.")
.setFootnote("This is the footnote")
.setTimestamp("just now")
.setIcon(R.drawable.ic_wifi)
.getView();
View view3 = new CardBuilder(context, CardBuilder.Layout.COLUMNS_FIXED)
.setText("This is the COLUMNS_FIXED layout. The text size is always the same.")
.setFootnote("This is the footnote")
.setTimestamp("just now")
.addImage(R.drawable.image1)
.addImage(R.drawable.image2)
.addImage(R.drawable.image3)
.addImage(R.drawable.image4)
.addImage(R.drawable.image5)
.getView();
CAPTION
El diseño CardBuilder.Layout.CAPTION
tiene un mosaico de imagen en el fondo y un texto de leyenda breve alineado en la parte inferior de la tarjeta. También se puede colocar un ícono junto al subtítulo para representar, por ejemplo, la identidad de una persona asociada con el contenido de la tarjeta.
View view1 = new CardBuilder(context, CardBuilder.Layout.CAPTION)
.setText("The caption layout.")
.setFootnote("This is the footnote")
.setTimestamp("just now")
.addImage(R.drawable.beach)
.setAttributionIcon(R.drawable.ic_smile)
.getView();
View view2 = new CardBuilder(context, CardBuilder.Layout.CAPTION)
.setText("The caption layout with an icon.")
.setFootnote("This is the footnote")
.setTimestamp("just now")
.addImage(R.drawable.beach)
.setIcon(R.drawable.ic_avatar)
.setAttributionIcon(R.drawable.ic_smile)
.getView();
TITLE
El diseño CardBuilder.Layout.TITLE
tiene un mosaico de imágenes en el fondo con un título centrado y un ícono opcional en la parte inferior de la tarjeta. Este diseño a menudo se usa para representar objetivos de contactos o compartir. La posición y la marca de tiempo no son compatibles con este diseño.
View view = new CardBuilder(context, CardBuilder.Layout.TITLE)
.setText("TITLE Card")
.setIcon(R.drawable.ic_phone)
.addImage(R.drawable.beach)
.getView();
AUTHOR
Usa el diseño CardBuilder.Layout.AUTHOR
para mostrar un mensaje o conversación donde el foco esté en el autor. Admite un mosaico de imágenes en segundo plano, un ícono que se usa como el avatar del autor, y un encabezado y subtítulo en el que puedes enumerar la información de identificación.
View view = new CardBuilder(context, CardBuilder.Layout.AUTHOR)
.setText("The AUTHOR layout lets you display a message or conversation "
+ " with a focus on the author.")
.setIcon(R.drawable.ic_avatar)
.setHeading("Joe Lastname")
.setSubheading("Mountain View, California")
.setFootnote("This is the footnote")
.setTimestamp("just now")
.getView();
MENU
El diseño CardBuilder.Layout.MENU
se parece a un menú estándar de Glass. Tiene un ícono y un título centrados, y una nota al pie opcional. Usa este diseño para las pantallas de confirmación (transición de "Borrado" a "Borrado") después de que el usuario seleccione un elemento de menú, por ejemplo. Si necesitas un menú real, debes usar un menú de opciones estándar.
View view = new CardBuilder(context, CardBuilder.Layout.MENU)
.setText("MENU layout")
.setIcon(R.drawable.ic_phone)
.setFootnote("Optional menu description")
.getView();
EMBED_INSIDE
El diseño CardBuilder.Layout.EMBED_INSIDE
incorpora un XML de diseño personalizado de tu propio diseño en la plantilla estándar de la tarjeta de Glass. Esto te permite diseñar una IU personalizada para la aplicación y, aun así, tener la ubicación correcta de la nota al pie, la marca de tiempo, el ícono de atribución y el indicador de pila de una tarjeta, si es necesario.
Después de llamar a CardBuilder.getView()
, usa findViewById()
en el resultado para acceder a las vistas dentro del diseño incorporado. Del mismo modo, si llamas a CardBuilder.getRemoteViews()
, puedes manipular las vistas del diseño incorporado si pasas sus ID directamente a los métodos set de RemoteViews
.
View view = new CardBuilder(context, CardBuilder.Layout.EMBED_INSIDE)
.setEmbeddedLayout(R.layout.food_table)
.setFootnote("Foods you tracked")
.setTimestamp("today")
.getView();
TextView textView1 = (TextView) view.findViewById(R.id.text_view_1);
textView1.setText("Water");
// ...and so on
Para obtener un ejemplo más detallado, consulta el proyecto ApiDemo de GitHub.
ALERT
El diseño CardBuilder.Layout.ALERT
contiene un ícono grande y centrado con un mensaje principal y una nota al pie. Usa este diseño en un Dialog
para mostrar un mensaje informativo importante, una advertencia o un error en tu Glassware.
En el siguiente ejemplo, se muestra una implementación de AlertDialog
y descarta la tarjeta y abre la configuración de Wi-Fi cuando el usuario presiona la tarjeta:
- Crea una clase que extienda
Dialog
. - Crea la tarjeta usando
CardBuilder
con el diseñoCardBuilder.Layout.ALERT
y, luego, establece la vista de contenido con esta tarjeta. (Opcional) Crea un
GestureDetector
para controlar los gestos de los usuarios en esta tarjeta.public class AlertDialog extends Dialog { private final DialogInterface.OnClickListener mOnClickListener; private final AudioManager mAudioManager; private final GestureDetector mGestureDetector; /** * Handles the tap gesture to call the dialog's * onClickListener if one is provided. */ private final GestureDetector.BaseListener mBaseListener = new GestureDetector.BaseListener() { @Override public boolean onGesture(Gesture gesture) { if (gesture == Gesture.TAP) { mAudioManager.playSoundEffect(Sounds.TAP); if (mOnClickListener != null) { // Since Glass dialogs do not have buttons, // the index passed to onClick is always 0. mOnClickListener.onClick(AlertDialog.this, 0); } return true; } return false; } }; public AlertDialog(Context context, int iconResId, int textResId, int footnoteResId, DialogInterface.OnClickListener onClickListener) { super(context); mOnClickListener = onClickListener; mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); mGestureDetector = new GestureDetector(context).setBaseListener(mBaseListener); setContentView(new CardBuilder(context, CardBuilder.Layout.ALERT) .setIcon(iconResId) .setText(textResId) .setFootnote(footnoteResId) .getView()); } /** Overridden to let the gesture detector handle a possible tap event. */ @Override public boolean onGenericMotionEvent(MotionEvent event) { return mGestureDetector.onMotionEvent(event) || super.onGenericMotionEvent(event); } }
En tu actividad, implementa un
OnClickListener
para controlar los flujos adicionales cuando el usuario presione (opcional). Para obtener más información sobre cómo iniciar actividades de configuración, como Wi-Fi, consulta Configuración de inicio.Llama al constructor
AlertDialog
para mostrar la tarjeta de alerta.public class MyActivity extends Activity { ... private final DialogInterface.OnClickListener mOnClickListener = new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int button) { // Open WiFi Settings startActivity(new Intent(Settings.ACTION_WIFI_SETTINGS)); } }; @Override protected void onCreate(Bundle bundle) { ... new AlertDialog(context, R.drawable.ic_cloud_sad_150, R.string.alert_text, R.string.alert_footnote_text, mOnClickListener).show(); ... } }
Diseños XML
A continuación, se incluyen dos diseños de tarjeta básicos que puedes usar si la clase CardBuilder no satisface tus necesidades.
Diseño principal
Este diseño define el relleno y el pie de página estándar de una tarjeta. Ingresa tus propias vistas en el RelativeLayout
vacío.
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<RelativeLayout
android:id="@+id/body_layout"
android:layout_width="match_parent"
android:layout_height="@dimen/glass_card_body_height"
android:layout_marginLeft="@dimen/glass_card_margin"
android:layout_marginTop="@dimen/glass_card_margin"
android:layout_marginRight="@dimen/glass_card_margin"
tools:ignore="UselessLeaf"
>
<!-- Put your widgets inside this RelativeLayout. -->
</RelativeLayout>
<LinearLayout
android:id="@+id/footer_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom|left"
android:layout_marginLeft="@dimen/glass_card_margin"
android:layout_marginBottom="@dimen/glass_card_footer_margin"
android:layout_marginRight="@dimen/glass_card_margin"
android:orientation="horizontal"
>
<!-- The footer view will grow to fit as much content as possible while the
timestamp view keeps a fixed width. If the footer text is too long, it
will be ellipsized with a 40px margin between it and the timestamp. -->
<TextView
android:id="@+id/footer"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="1"
android:ellipsize="end"
android:singleLine="true"
android:textAppearance="?android:attr/textAppearanceSmall"
/>
<TextView
android:id="@+id/timestamp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/glass_card_margin"
android:ellipsize="end"
android:singleLine="true"
android:textAppearance="?android:attr/textAppearanceSmall"
/>
</LinearLayout>
</FrameLayout>
Diseño de la columna izquierda
Esto define una columna izquierda de 240 px y una columna derecha de 400 px en forma de dos RelativeLayout
en las que puedes colocar tus vistas.
<?xml version="1.0" encoding="utf-8"?>
<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"
>
<RelativeLayout
android:id="@+id/left_column"
android:layout_width="@dimen/glass_card_left_column_width"
android:layout_height="match_parent"
>
<!-- Put widgets for the left column inside this RelativeLayout. -->
</RelativeLayout>
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="@dimen/glass_card_body_height"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true"
android:layout_marginLeft="@dimen/glass_card_two_column_margin"
android:layout_marginRight="@dimen/glass_card_margin"
android:layout_marginTop="@dimen/glass_card_margin"
android:layout_toRightOf="@+id/left_column"
tools:ignore="UselessLeaf"
>
<!-- Put widgets for the right column inside this RelativeLayout. -->
</RelativeLayout>
<LinearLayout
android:id="@+id/footer_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:layout_gravity="bottom|left"
android:layout_marginBottom="@dimen/glass_card_footer_margin"
android:layout_marginLeft="@dimen/glass_card_two_column_margin"
android:layout_marginRight="@dimen/glass_card_margin"
android:layout_toRightOf="@+id/left_column"
android:orientation="horizontal"
>
<!--
The footer view will grow to fit as much content as possible while the
timestamp view keeps a fixed width. If the footer text is too long, it
will be ellipsized with a 40px margin between it and the timestamp.
-->
<TextView
android:id="@+id/footer"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="1"
android:ellipsize="end"
android:singleLine="true"
android:textAppearance="?android:attr/textAppearanceSmall"
/>
<TextView
android:id="@+id/timestamp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/glass_card_margin"
android:ellipsize="end"
android:singleLine="true"
android:textAppearance="?android:attr/textAppearanceSmall"
/>
</LinearLayout>
</RelativeLayout>
Dimensiones estándar
Usa este archivo junto con los diseños anteriores o con tus propios diseños para cumplir con el estilo estándar de Glass. Crea este archivo como res/values/dimens.xml
en tu proyecto de Android.
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- The recommended margin for the top, left, and right edges of a card. -->
<dimen name="glass_card_margin">40px</dimen>
<!-- The recommended margin between the bottom of the card and the footer. This is
an adjusted value so that the baseline of the text in the footer sits 40px
from the bottom of the card, matching the other margins. -->
<dimen name="glass_card_footer_margin">33px</dimen>
<!-- The recommended margin for the left column of the two-column card. -->
<dimen name="glass_card_two_column_margin">30px</dimen>
<!-- The maximum height of the body content inside a card. -->
<dimen name="glass_card_body_height">240px</dimen>
<!-- The width of the left column in the two-column layout. -->
<dimen name="glass_card_left_column_width">240px</dimen>
</resources>