Progettazione carte

Questo documento illustra come seguire lo stile Glass e implementare le best practice più comuni per l'utilizzo di GDK.

Tema del vetro

Glass applica un tema standard al tuo Glassware, in modo che rimanga coerente con il resto dell'interfaccia utente. Il tema ha le seguenti caratteristiche:

  • Utilizza il carattere tipografico Roboto
  • Mostra le attività a schermo intero senza barra di stato o barra delle azioni
  • Applica uno sfondo nero pieno

Per applicare il tema Glass, non dichiararlo nel file manifest Android.

Se hai uno stile personalizzato per parti del tuo Glassware e vuoi il tema Glass predefinito per tutto il resto, 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 di Android su stili e temi.

Carte in vetro

La classe CardBuilder crea schede ben strutturate in base a un insieme di proprietà. Se possibile, utilizza i layout forniti da CardBuilder.Layout in modo che i contenuti abbiano lo stesso aspetto e lo stesso aspetto degli altri contenuti su Glass.

Per usare CardBuilder:

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

Funzionalità UI comuni

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

Icona 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 alla destra del timestamp. Imposta questa icona chiamando CardBuilder.setAttributionIcon() per identificare la tua applicazione, in particolare sulle schede pubblicate, in modo che un utente possa vedere rapidamente e vedere la fonte delle informazioni su quella scheda.

Indicatore stack

L'indicatore di stack, controllato da CardBuilder.showStackIndicator(), è una piega angolare visualizzata nell'angolo in alto a destra di una scheda. Utilizza questo indicatore per indicare che la tua carta rappresenta un insieme di altre schede che l'utente può toccare 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

Gli esempi riportati di seguito mostrano i layout disponibili utilizzando CardBuilder.

TEXT e TEXT_FIXED

Il layout CardBuilder.Layout.TEXT mostra testo al vivo con un mosaico di immagini facoltativo sullo sfondo. Il testo viene ridimensionato in modo dinamico per adattarsi al meglio allo spazio disponibile. CardBuilder.Layout.TEXT_FIXED è simile, ma corregge il testo con dimensioni più piccole.

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 o un'icona immagine sul lato sinistro della scheda e del testo sul lato destro. Il testo viene ridimensionato in modo dinamico per adattarsi al meglio allo spazio disponibile. Per mantenere fisse le dimensioni 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 immagine sullo sfondo e un breve testo dei sottotitoli allineato nella parte inferiore della scheda. Puoi anche inserire un'icona accanto ai sottotitoli 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 immagine 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 i target. La nota a piè di pagina e il timestamp non sono supportati su 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 l'autore è incentrato sull'autore. Supporta un mosaico di immagine sullo sfondo, un'icona utilizzata come avatar dell'autore e un'intestazione e un sottotitolo in cui è possibile 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 Glass standard. Ha un'icona e un titolo centrati e una nota a piè di pagina facoltativa. Utilizza questo layout per le schermate di conferma (passando da "Eliminazione" a "Eliminata" dopo che l'utente ha selezionato una voce di menu, ad esempio). Se hai bisogno di un menu reale, devi utilizzare un menu di 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 file XML di layout personalizzato di tua progettazione nel modello di scheda standard di Glass. In questo modo puoi progettare un'interfaccia utente personalizzata per la tua applicazione, ma mantenere comunque il corretto posizionamento della nota a piè di pagina, del timestamp, dell'icona di attribuzione e dell'indicatore di stack, se necessario.

Dopo aver chiamato CardBuilder.getView(), utilizza findViewById() nel risultato per accedere alle visualizzazioni all'interno del tuo layout incorporato. Analogamente, se chiami CardBuilder.getRemoteViews(), puoi manipolare le viste del layout incorporato trasmettendo i relativi ID direttamente nei metodi RemoteViews di configurazione.

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 di 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 Dialog per mostrare un messaggio informativo, un avviso o un errore importante nel tuo Glassware.

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

  1. Crea un corso che estende 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 degli utenti 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 risorsa OnClickListener per gestire eventuali flussi aggiuntivi quando l'utente tocca. Per ulteriori informazioni sull'avvio di attività delle 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 base delle schede 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 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 a sinistra della colonna

Questo 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

Usa questo file con i layout precedenti o con i tuoi layout per ottemperare allo stile standard di Glass. Crea questo file come res/values/dimens.xml nel 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>