Progettazione carte

Questo documento illustra come seguire lo stile di Glass e implementare best practice comuni per l'interfaccia utente.

Tema vetro

Glass applica un tema standard a Glassware, in modo da mantenere la coerenza con il resto dell'interfaccia utente. Il tema ha le seguenti caratteristiche:

  • Utilizza il carattere tipografico Roboto
  • Visualizza le attività a schermo intero senza barra di stato o barra delle azioni
  • Applica uno sfondo nero in tinta unita

Per applicare il tema Vetro, non dichiarare un tema nel file manifest Android.

Se hai uno stile personalizzato per alcune parti di Glassware e vuoi usare il tema Vetro predefinito per tutti gli altri, eredita da Theme.DeviceDefault con l'attributo parent:

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

Per ulteriori informazioni sulla creazione di temi, consulta la guida per gli sviluppatori Android su Stili e temi.

Schede in stile vetro

La classe CardBuilder crea schede con formato corretto in base a un insieme di proprietà. Utilizza i layout forniti da CardBuilder.Layout quando possibile, in modo che i tuoi contenuti abbiano l'aspetto e il design di altri contenuti su Glass.

Per usare CardBuilder:

  1. Crea un'istanza di CardBuilder, assegnandogli il layout che preferisci da CardBuilder.Layout.
  2. Imposta le proprietà della scheda, come testo, nota a piè di pagina e timestamp.
  3. Chiama CardBuilder.getView() per convertire la carta in un View Android o CardBuilder.getRemoteViews() per convertirla in un oggetto RemoteViews.
  4. Utilizza View nelle tue attività, nei layout o in una CardScrollView oppure utilizza RemoteViews in una LiveCard.

Funzionalità comuni dell'interfaccia utente

Molti dei layout forniti da CardBuilder supportano le funzionalità comuni dell'interfaccia utente descritte di seguito. Consulta la documentazione dei singoli layout in CardBuilder.Layout per un elenco delle funzionalità supportate da ciascun tipo di scheda.

Icona di attribuzione

L'icona di attribuzione è un'icona facoltativa di 36 × 36 pixel che viene visualizzata nell'angolo in basso a destra di una scheda e a destra del timestamp. Imposta questa icona chiamando il numero CardBuilder.setAttributionIcon() per identificare la tua applicazione, in particolare sulle schede attive, in modo che l'utente possa dare un'occhiata rapidamente e vedere la fonte delle informazioni su quella scheda.

Indicatore dello stack

L'indicatore della pila, controllato da CardBuilder.showStackIndicator(), è una piegatura ad angolo che appare nell'angolo in alto a destra di una scheda. Utilizza questo elemento come indicatore visivo che indichi che la tua scheda rappresenta un insieme di altre schede a cui l'utente può accedere direttamente.

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

Layout

I seguenti esempi mostrano i layout disponibili utilizzando l'CardBuilder.

TEXT e TEXT_FIXED

Il layout CardBuilder.Layout.TEXT mostra testo al vivo con un mosaico di immagini facoltativo sullo sfondo. Il testo si ridimensiona in modo dinamico per adattarsi al meglio allo spazio disponibile. CardBuilder.Layout.TEXT_FIXED è simile, ma riduce le dimensioni del testo.

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

Il layout CardBuilder.Layout.COLUMNS mostra un mosaico di immagini o un'icona sul lato sinistro della scheda e il testo sul lato destro. Il testo viene dimensionato in modo dinamico per adattarsi al meglio allo spazio disponibile. Per mantenere fisse la dimensione del testo, utilizza 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

Il layout CardBuilder.Layout.CAPTION ha un mosaico di immagini sullo sfondo e un breve testo della didascalia allineato nella parte inferiore della scheda. Accanto alla didascalia può essere posizionata anche un'icona per rappresentare, ad esempio, l'identità di una persona associata ai contenuti della scheda.

Figura 1: (immagine di sfondo di photoeverywhere.co.uk, ritagliata)
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

Il layout CardBuilder.Layout.TITLE ha un mosaico di immagini sullo sfondo con un titolo centrato e un'icona facoltativa nella parte inferiore della scheda. Questo layout viene spesso utilizzato per rappresentare i contatti o condividere obiettivi. Nota a piè di pagina e timestamp non supportati in questo layout.

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

AUTHOR

Utilizza il layout CardBuilder.Layout.AUTHOR per visualizzare un messaggio o una conversazione in cui lo stato attivo è l'autore. Supporta un mosaico di immagini sullo sfondo, un'icona utilizzata come avatar dell'autore e un'intestazione e un sottotitolo in cui puoi elencare le informazioni identificative.

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

Il layout CardBuilder.Layout.MENU ha l'aspetto di un menu di Glass standard. con un'icona e un titolo centrali e una nota a piè di pagina facoltativa. Utilizza questo layout per le schermate di conferma (ad esempio, la transizione da "Eliminazione" a "Eliminata" dopo che l'utente ha selezionato una voce di menu). Se hai bisogno di un menu reale, dovresti usare invece un menu opzioni standard.

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

EMBED_INSIDE

Il layout CardBuilder.Layout.EMBED_INSIDE incorpora un XML di layout personalizzato di tua scelta nel modello di scheda Glass standard. In questo modo puoi progettare un'interfaccia utente personalizzata per la tua applicazione, mantenendo comunque il corretto posizionamento della nota a piè di pagina, del timestamp, dell'icona di attribuzione e dell'indicatore dello stack di una scheda, se necessario.

Dopo aver chiamato CardBuilder.getView(), utilizza findViewById() sul risultato per accedere alle visualizzazioni all'interno del layout incorporato. Allo stesso modo, se chiami CardBuilder.getRemoteViews(), puoi modificare le visualizzazioni del layout incorporato trasmettendone gli ID direttamente ai metodi del setter di 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

Per un esempio più dettagliato, consulta il progetto ApiDemo GitHub.

ALERT

Il layout CardBuilder.Layout.ALERT contiene una grande icona centrata con un messaggio principale e una nota a piè di pagina. Utilizza questo layout in una Dialog per mostrare un messaggio informativo, un avviso o un errore importante in Glassware.

L'esempio seguente mostra un'implementazione di AlertDialog e chiude la scheda e apre le impostazioni Wi-Fi quando l'utente tocca la scheda:

  1. Crea un corso che estenda Dialog.
  2. Crea la scheda utilizzando CardBuilder con il layout CardBuilder.Layout.ALERT e imposta la visualizzazione dei contenuti con questa scheda.
  3. (Facoltativo) Crea una GestureDetector per gestire i gesti dell'utente su questa scheda.

    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. (Facoltativo) Nella tua attività, implementa una OnClickListener per gestire eventuali flussi aggiuntivi quando l'utente tocca il pulsante. Per ulteriori informazioni sull'avvio di attività relative alle impostazioni come il Wi-Fi, consulta Avvio delle impostazioni.

  5. Chiama il costruttore AlertDialog per visualizzare la scheda di avviso.

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

Layout XML

Ecco due layout di schede di base che puoi utilizzare se la classe CardBuilder non soddisfa le tue esigenze.

Layout principale

Questo layout definisce la spaziatura interna e il piè di pagina standard di una scheda. Inserisci le tue visualizzazioni nel campo RelativeLayout vuoto.

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

Layout colonna a sinistra

Definisce una colonna sinistra di 240 px e una colonna destra di 400 px sotto forma di due RelativeLayout in cui puoi inserire le tue visualizzazioni.

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

Dimensioni standard

Utilizza questo file insieme ai layout precedenti o ai tuoi layout per aderire allo stile standard di Glass. Crea questo file come res/values/dimens.xml nel tuo progetto 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>