本文档介绍了如何在使用 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
,请执行以下操作:
- 创建一个
CardBuilder
实例,从CardBuilder.Layout
为其提供所需的布局。 - 设置卡片的属性,例如文本、脚注和时间戳。
- 调用
CardBuilder.getView()
可将卡片转换为 AndroidView
,或调用CardBuilder.getRemoteViews()
以将其转换为RemoteViews
对象。 - 在您的 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
布局的背景中有一个拼接图,并将简短的图片说明文本对齐卡片底部。例如,还可以在图片说明旁边放置一个图标,以表示与卡片内容相关联的人员的身份。
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();
MENU
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 设置:
- 创建一个扩展
Dialog
的类。 - 使用带有
CardBuilder.Layout.ALERT
布局的CardBuilder
创建卡片,然后使用此卡片设置内容视图。 (可选)创建
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); } }
(可选)在您的 activity 中,实现
OnClickListener
以在用户点按时处理任何其他流程。如需详细了解如何启动设置 activity(如 Wi-Fi),请参阅启动设置。调用
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>