Conception de cartes

Ce document explique comment suivre le style Glass et implémenter les bonnes pratiques courantes en matière d'interface utilisateur lors de l'utilisation du GDK.

Thème Glass

Glass applique un thème standard à votre Glassware afin qu'il reste cohérent avec le reste de l'interface utilisateur. Ce thème présente les caractéristiques suivantes:

  • Utilise la police de caractères Roboto
  • Affiche les activités en plein écran sans barre d'état ni barre d'action.
  • S'applique à un arrière-plan uni noir

Pour appliquer le thème Glass, ne déclarez pas de thème dans votre fichier manifeste Android.

Si vous utilisez un style personnalisé pour certaines parties de votre Glassware et que vous souhaitez utiliser le thème Glass par défaut pour tout le reste, héritez de Theme.DeviceDefault avec l'attribut parent:

<resources>
    <style name="CustomTheme" parent="@android:style/Theme.DeviceDefault">
        <!-- Theme customization goes here. -->
    </style>
</resources>

Pour en savoir plus sur la création de thèmes, consultez le guide du développeur Android sur les styles et thèmes.

Cartes en verre

La classe CardBuilder crée des cartes bien formées en fonction d'un ensemble de propriétés. Dans la mesure du possible, utilisez les mises en page fournies par CardBuilder.Layout pour que votre contenu ressemble à un autre contenu sur Glass.

Pour utiliser CardBuilder :

  1. Créez une instance de CardBuilder en lui attribuant la mise en page souhaitée à partir de CardBuilder.Layout.
  2. Définissez les propriétés de la carte, telles que le texte, la note de bas de page et le code temporel.
  3. Appelez CardBuilder.getView() pour convertir la carte en View Android ou CardBuilder.getRemoteViews() pour la convertir en objet RemoteViews.
  4. Utilisez le View dans vos activités, vos mises en page ou dans un CardScrollView, ou utilisez le RemoteViews dans un LiveCard.

Fonctionnalités courantes de l'interface utilisateur

De nombreuses mises en page fournies par CardBuilder sont compatibles avec les fonctionnalités courantes de l'interface utilisateur décrites ci-dessous. Consultez la documentation sur les mises en page individuelles dans CardBuilder.Layout pour obtenir la liste des fonctionnalités compatibles avec chaque type de carte.

Icône d'attribution

L'icône d'attribution est une icône facultative de 36 × 36 pixels, qui apparaît en bas à droite d'une carte et à droite de l'horodatage. Définissez cette icône en appelant CardBuilder.setAttributionIcon() pour identifier votre application, en particulier sur les cartes actives. Ainsi, un utilisateur peut rapidement consulter la source des informations qu'elle contient.

Indicateur de pile

L'indicateur de pile, contrôlé par CardBuilder.showStackIndicator(), correspond à un pli d'angle qui apparaît dans l'angle supérieur droit d'une carte. Utilisez-le comme indicateur visuel que votre carte représente un groupe d'autres cartes sur lesquelles l'utilisateur peut appuyer directement.

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();

Mises en page

Les exemples suivants présentent les mises en page disponibles à l'aide de CardBuilder.

TEXT et TEXT_FIXED

La mise en page CardBuilder.Layout.TEXT affiche du texte à fond perdu avec une mosaïque d'images facultative en arrière-plan. Le texte est redimensionné de manière dynamique pour s'adapter au mieux à l'espace disponible. CardBuilder.Layout.TEXT_FIXED est similaire, mais réduit la taille de son texte.

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 et COLUMNS_FIXED

La mise en page CardBuilder.Layout.COLUMNS affiche une mosaïque d'images ou une icône à gauche de la carte et du texte à droite. Le texte est dimensionné dynamiquement pour mieux s'adapter à l'espace disponible. Pour que la taille du texte reste fixe, utilisez 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

La mise en page CardBuilder.Layout.CAPTION comporte une mosaïque d'images en arrière-plan et un court texte de légende alignés en bas de la carte. Vous pouvez également placer une icône à côté de la légende pour représenter, par exemple, l'identité d'une personne associée au contenu de la fiche.

Figure 1: (image de fond de photoeverywhere.co.uk recadrée)
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

La mise en page CardBuilder.Layout.TITLE comporte une mosaïque d'images en arrière-plan, avec un titre centré et une icône facultative en bas de la carte. Cette mise en page est souvent utilisée pour représenter des contacts ou des cibles de partage. Les notes de bas de page et les codes temporels ne sont pas compatibles avec cette mise en page.

View view = new CardBuilder(context, CardBuilder.Layout.TITLE)
    .setText("TITLE Card")
    .setIcon(R.drawable.ic_phone)
    .addImage(R.drawable.beach)
    .getView();

AUTHOR

Utilisez la mise en page CardBuilder.Layout.AUTHOR pour afficher un message ou une conversation où l'accent est mis sur l'auteur. Il prend en charge une mosaïque d'images en arrière-plan, une icône utilisée comme avatar de l'auteur, ainsi qu'un titre et un sous-titre dans lesquels vous pouvez indiquer des informations d'identification.

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();

La mise en page CardBuilder.Layout.MENU ressemble à un menu Glass standard. Elle comporte une icône et un titre centrés et une note de bas de page facultative. Utilisez cette mise en page pour les écrans de confirmation (passage de "Suppression" à "Supprimé" après que l'utilisateur a sélectionné un élément de menu, par exemple). Si vous avez besoin d'un vrai menu, utilisez plutôt un menu d'options standard.

View view = new CardBuilder(context, CardBuilder.Layout.MENU)
    .setText("MENU layout")
    .setIcon(R.drawable.ic_phone)
    .setFootnote("Optional menu description")
    .getView();

EMBED_INSIDE

La mise en page CardBuilder.Layout.EMBED_INSIDE intègre un code XML de mise en page personnalisé de votre propre conception dans le modèle de carte Glass standard. Cela vous permet de concevoir une interface utilisateur personnalisée pour votre application tout en conservant la position correcte de la note de bas de page, du code temporel, de l'icône d'attribution et de l'indicateur de pile d'une carte si nécessaire.

Après avoir appelé CardBuilder.getView(), utilisez findViewById() sur le résultat pour accéder aux vues de votre mise en page intégrée. De même, si vous appelez CardBuilder.getRemoteViews(), vous pouvez manipuler les vues de votre mise en page intégrée en transmettant leurs ID directement aux méthodes setter 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

Pour obtenir un exemple plus détaillé, consultez le projet ApiDemo sur GitHub.

ALERT

La mise en page CardBuilder.Layout.ALERT contient une grande icône centrée avec un message principal et une note de bas de page. Utilisez cette mise en page dans un Dialog pour afficher un message d'information important, un avertissement ou une erreur dans votre Glassware.

L'exemple suivant montre une implémentation de AlertDialog. La carte est ignorée et les paramètres Wi-Fi s'ouvrent lorsque l'utilisateur appuie dessus:

  1. Créez une classe qui étend Dialog.
  2. Créez la fiche à l'aide de CardBuilder avec la mise en page CardBuilder.Layout.ALERT, puis définissez la vue de contenu avec cette fiche.
  3. (Facultatif) Créez un GestureDetector pour gérer les gestes des utilisateurs sur cette carte.

    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);
        }
    }
    
  4. (Facultatif) Dans votre activité, implémentez un OnClickListener pour gérer les flux supplémentaires lorsque l'utilisateur appuie sur l'écran. Pour en savoir plus sur le démarrage d'activités de paramétrage telles que le Wi-Fi, consultez Démarrage des paramètres.

  5. Appelez le constructeur AlertDialog pour afficher la fiche d'alerte.

    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();
    
            ...
        }
    }
    

Mises en page XML

Voici deux mises en page de carte de base que vous pouvez utiliser si la classe CardBuilder ne répond pas à vos besoins.

Mise en page principale

Cette mise en page définit la marge intérieure et le pied de page standards d'une fiche. Placez vos propres vues dans le fichier RelativeLayout vide.

<?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>

Disposition en colonnes de gauche

Cela définit une colonne de gauche de 240 pixels et une colonne de droite de 400 pixels sous la forme de deux éléments RelativeLayout dans lesquels vous pouvez placer vos vues.

<?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>

Dimensions standards

Utilisez ce fichier conjointement avec les mises en page précédentes ou vos propres mises en page pour respecter le style Glass standard. Créez ce fichier en tant que res/values/dimens.xml dans votre projet 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>