卡片设计

本文档将介绍如何使用 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 提供的布局,使您的内容看起来与 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 可用的布局。

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

COLUMNSCOLUMNS_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 菜单。该图标具有居中的图标和标题,以及可选的脚注。对此布局使用确认屏幕(例如,在用户选择菜单项后从“Deleteing”转换为“Deleted”)。如果需要真实的菜单,您应该使用标准选项菜单。

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 以在用户点按时处理任何其他数据流。如需详细了解如何启动 Wi-Fi 等设置 activity,请参阅启动设置

  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>

左列布局

这定义了一个 240px 左列和 400px 右侧列,形式为两个 RelativeLayout,您可以将这些视图放入其中。

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