Design de cartão

Este documento mostra como seguir o estilo do Google Glass e implementar as práticas recomendadas comuns de IU ao usar o GDK.

Tema do Google Glass

O Google Glass aplica um tema padrão ao Glassware para que permaneça consistente com o restante da interface do usuário. O tema tem as características abaixo:

  • Usa a fonte Roboto
  • Mostra atividades em tela cheia sem barra de status ou de ação
  • Aplica o plano de fundo sólido e preto

Para aplicar o tema Glass, não declare um tema no manifesto do Android.

Se você tiver um estilo personalizado para partes do Glassware e quiser o tema padrão do Glass para todo o restante, herde o Theme.DeviceDefault com o atributo parent:

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

Consulte o guia do desenvolvedor Android sobre Estilos e temas para ver mais informações sobre como criar temas.

Cartões no estilo do vidro

A classe CardBuilder cria cards bem formados considerando um conjunto de propriedades. Use os layouts fornecidos pelo CardBuilder.Layout sempre que possível para que o conteúdo pareça ser outro conteúdo do Glass.

Para usar o app CardBuilder:

  1. Crie uma instância de CardBuilder, fornecendo a ela o layout desejado de CardBuilder.Layout.
  2. Defina as propriedades do card, como o texto, a nota de rodapé e o carimbo de data/hora.
  3. Chame CardBuilder.getView() para converter o cartão em um View do Android ou CardBuilder.getRemoteViews() para converter em um objeto RemoteViews.
  4. Use a View nas suas atividades, layouts ou em uma CardScrollView, ou use a RemoteViews em um LiveCard.

Recursos comuns da IU

Muitos dos layouts fornecidos por CardBuilder oferecem suporte aos recursos comuns de interface do usuário descritos abaixo. Consulte a documentação dos layouts individuais em CardBuilder.Layout para ver uma lista dos recursos compatíveis com cada tipo de card.

Ícone de atribuição

O ícone de atribuição é um ícone opcional de 36 × 36 pixels que aparece no canto inferior direito de um cartão e à direita do carimbo de data/hora. Para definir esse ícone, chame CardBuilder.setAttributionIcon() para identificar o aplicativo, principalmente em cards ativos, para que o usuário possa ver rapidamente e ver a fonte das informações nesse card.

Indicador de pilha

O indicador de pilha, controlado por CardBuilder.showStackIndicator(), é uma dobra no canto superior direito de um card. Use-o como um indicador visual de que seu cartão representa um pacote de outros cartões em que o usuário pode tocar diretamente.

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

Layouts

Os exemplos a seguir mostram os layouts disponíveis usando CardBuilder.

TEXT e TEXT_FIXED

O layout CardBuilder.Layout.TEXT mostra texto sem margens com um mosaico de imagens opcional em segundo plano. O texto é redimensionado dinamicamente para se ajustar ao espaço disponível. CardBuilder.Layout.TEXT_FIXED é semelhante, mas corrige o texto para um tamanho menor.

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

O layout CardBuilder.Layout.COLUMNS mostra um mosaico ou um ícone de imagem no lado esquerdo do cartão e um texto no lado direito. O texto é dimensionado dinamicamente para se ajustar ao espaço disponível. Para manter o tamanho do texto fixo, use 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

O layout CardBuilder.Layout.CAPTION tem um mosaico de imagens em segundo plano e um breve texto de legenda alinhado na parte de baixo do card. Também é possível colocar um ícone ao lado da legenda para representar, por exemplo, a identidade de uma pessoa associada ao conteúdo do cartão.

Figura 1: (imagem de plano de fundo por photoeverywhere.co.uk, cortada)
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

O layout CardBuilder.Layout.TITLE tem um mosaico de imagens em segundo plano com um título centralizado e ícone opcional na parte de baixo do card. Esse layout geralmente é usado para representar contatos ou compartilhar destinos. A anotação e o carimbo de data/hora não são compatíveis com esse layout.

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

AUTHOR

Use o layout CardBuilder.Layout.AUTHOR para exibir uma mensagem ou conversa com foco no autor. Ele é compatível com um mosaico de imagens em segundo plano, um ícone usado como avatar do autor e um título e subtítulo em que é possível listar informações de identificação.

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

O layout CardBuilder.Layout.MENU é parecido com um menu padrão do Google Glass. Ele tem um ícone e um título centralizados e uma nota de rodapé opcional. Use esse layout para telas de confirmação (fazendo a transição de "Excluir" para "Excluído" depois que o usuário selecionar um item de menu, por exemplo). Se você precisar de um menu real, use um menu de opções padrão.

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

EMBED_INSIDE

O layout CardBuilder.Layout.EMBED_INSIDE incorpora um XML de layout personalizado do seu próprio design ao modelo de cartão padrão do Glass. Isso permite que você projete uma IU personalizada para o aplicativo, mas ainda tenha o posicionamento correto da nota de rodapé, carimbo de data/hora, ícone de atribuição e indicador da pilha de um cartão, se necessário.

Depois de chamar CardBuilder.getView(), use findViewById() no resultado para acessar as visualizações no layout incorporado. Da mesma forma, se você chamar CardBuilder.getRemoteViews(), poderá manipular as visualizações do layout incorporado transmitindo os IDs diretamente para os métodos 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

Para ver um exemplo mais detalhado, consulte o projeto ApiDemo do GitHub.

ALERT

O layout CardBuilder.Layout.ALERT contém um grande ícone centralizado com uma mensagem principal e uma nota de rodapé. Use esse layout em uma Dialog para mostrar uma mensagem informativa, um aviso ou um erro importante no Glassware.

O exemplo a seguir mostra uma implementação de AlertDialog e dispensa o cartão e abre as configurações de Wi-Fi quando o usuário toca no cartão:

  1. Crie uma classe que estenda o Dialog.
  2. Crie o card usando o CardBuilder com o layout CardBuilder.Layout.ALERT e defina a visualização de conteúdo com esse card.
  3. (Opcional) Crie um GestureDetector para processar os gestos do usuário neste card.

    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. (Opcional) Na sua atividade, implemente um OnClickListener para processar fluxos adicionais quando o usuário tocar. Para ver mais informações sobre como iniciar atividades de configurações, como Wi-Fi, consulte Configurações de inicialização.

  5. Chame o construtor AlertDialog para mostrar o card 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();
    
            ...
        }
    }
    

Layouts XML

Aqui estão dois layouts de cartão básicos que você pode usar se a classe CardBuilder não atender às suas necessidades.

Layout principal

Esse layout define o padding e o rodapé padrão de um card. Coloque suas próprias visualizações na RelativeLayout vazia.

<?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 de coluna esquerda

Isso define uma coluna esquerda de 240 px e uma coluna direita de 400 px na forma de duas RelativeLayouts em que você pode colocar suas visualizações.

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

Dimensões padrão

Use esse arquivo com os layouts anteriores ou seus próprios layouts para aderir ao estilo Glass padrão. Crie esse arquivo como res/values/dimens.xml no projeto do 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>