卡片设计

本文档介绍了如何在使用 GDK 时遵循 Glass 样式并实现常见的界面最佳做法。

玻璃主题

Glass 会对 Glassware 应用标准主题,使其与界面的其余部分保持一致。该主题具有以下特征:

  • 使用 Roboto 字体
  • 全屏显示活动,无状态栏或操作栏
  • 应用纯黑色背景

如要应用 Glass 主题,请勿在 Android 清单中声明主题。

如果您为 Glassware 的某些部分设置了自定义样式,并希望为其他所有内容使用默认的 Glass 主题,请使用 parent 属性从 Theme.DeviceDefault 继承样式:

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

如需详细了解如何创建主题,请参阅 Android 开发者指南中的样式和主题

玻璃风格的卡片

CardBuilder 类会根据一组属性创建格式正确的卡片。尽可能使用 CardBuilder.Layout 提供的布局,以使您的内容在外观和风格上与 Google Glass 上的其他内容类似。

如需使用 CardBuilder,请执行以下操作:

  1. 创建一个 CardBuilder 实例,从 CardBuilder.Layout 为其提供所需的布局。
  2. 设置卡片的属性,例如文本、脚注和时间戳。
  3. 调用 CardBuilder.getView() 可将卡片转换为 Android View,或调用 CardBuilder.getRemoteViews() 以将其转换为 RemoteViews 对象。
  4. 在您的 activity、布局或 CardScrollView 中使用 View,或在 LiveCard 中使用 RemoteViews

常见界面功能

CardBuilder 提供的许多布局都支持下述常见界面功能。如需查看每种卡片支持的功能的列表,请参阅 CardBuilder.Layout 中各个布局的文档。

提供方图标

归因图标是可选的 36 × 36 像素的图标,显示在卡片的右下角和时间戳的右侧。可通过调用 CardBuilder.setAttributionIcon() 来设置此图标,以标识您的应用(尤其是在实时卡片上),以便用户快速浏览和看到该卡片上的信息来源。

堆栈指示器

堆叠指示器由 CardBuilder.showStackIndicator() 控制,是卡片右上角的角折。您可以将其用作直观的指示符,以表明您的卡片代表一组可供用户直接点按的其他卡片。

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

布局

以下示例展示了使用 CardBuilder 时可用的布局。

TEXT”和“TEXT_FIXED

CardBuilder.Layout.TEXT 布局会显示全出血文本,且背景中包含一个可选的拼接图。文本会动态调整大小,以最好地适应可用空间。 CardBuilder.Layout.TEXT_FIXED 与之类似,但会将其文本固定为较小的文本。

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”和“COLUMNS_FIXED

CardBuilder.Layout.COLUMNS 布局会在卡片左侧显示拼接图或图标,在右侧显示文本。系统会动态调整文本大小,以最好地适应可用空间。如需保持文本大小固定,请使用 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

CardBuilder.Layout.CAPTION 布局的背景中有一个拼接图,并将简短的图片说明文本对齐卡片底部。例如,还可以在图片说明旁边放置一个图标,以表示与卡片内容相关联的人员的身份。

图 1:(经剪裁的 photoeverywhere.co.uk 背景图片)
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

CardBuilder.Layout.TITLE 布局在后台具有一个拼接图图片,其标题居中显示,卡片底部有一个可选的图标。此布局通常用于表示联系人或共享目标。此布局不支持脚注和时间戳。

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

AUTHOR

使用 CardBuilder.Layout.AUTHOR 布局显示焦点在作者上的消息或对话。它支持在背景中使用拼接图、用作作者头像的图标,以及用于列出标识信息的标题和子标题。

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

CardBuilder.Layout.MENU 布局看起来像标准的 Glass 菜单。它具有居中的图标和标题,以及可选的脚注。对于确认屏幕(例如,在用户选择菜单项后从“正在删除”变为“已删除”状态),请使用此布局。如果您需要实际菜单,则应改用标准选项菜单。

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

EMBED_INSIDE

CardBuilder.Layout.EMBED_INSIDE 布局会将您自己设计的自定义布局 XML 嵌入到标准 Glass 卡片模板中。这样,您就可以为应用设计自定义界面,但仍根据需要正确放置卡片的脚注、时间戳、提供方图标和堆栈指示器。

调用 CardBuilder.getView() 后,对结果使用 findViewById() 即可访问嵌入式布局内的视图。同样,如果调用 CardBuilder.getRemoteViews(),您可以通过直接将嵌入式布局的 ID 传递给 RemoteViews setter 方法来操纵嵌入式布局的视图。

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

如需查看更详细的示例,请参阅 GitHub ApiDemo 项目

ALERT

CardBuilder.Layout.ALERT 布局包含一个居中的大型图标,其中显示主要消息和脚注。在 Dialog 中使用此布局在 Glassware 中显示重要信息性消息、警告或错误。

以下示例展示了 AlertDialog 的实现,当用户点按卡片时,它会关闭卡片并打开 Wi-Fi 设置:

  1. 创建一个扩展 Dialog 的类。
  2. 使用带有 CardBuilder.Layout.ALERT 布局的 CardBuilder 创建卡片,然后使用此卡片设置内容视图。
  3. (可选)创建 GestureDetector 来处理此卡片上的用户手势。

    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. (可选)在您的 activity 中,实现 OnClickListener 以在用户点按时处理任何其他流程。如需详细了解如何启动设置 activity(如 Wi-Fi),请参阅启动设置

  5. 调用 AlertDialog 构造函数以显示提醒卡片。

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

XML 布局

如果 CardBuilder 类不符合您的需求,您可以使用下面两种基本的卡片布局。

主布局

此布局定义了卡片的标准内边距和页脚。将您自己的视图放在空的 RelativeLayout 中。

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

左列布局

这会以两个 RelativeLayout 的形式定义一个 240 像素的左列和一个 400 像素的右列,您可以将视图放入其中。

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

标准尺寸

请将此文件与之前的布局或您自己的布局结合使用,以遵循标准 Glass 样式。在您的 Android 项目中将此文件创建为 res/values/dimens.xml

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