В этом документе рассказывается, как следовать стилю Glass и применять общие рекомендации по пользовательскому интерфейсу при использовании GDK.
Стеклянная тема
Glass применяет стандартную тему к вашей посуде Glassware, поэтому она остается согласованной с остальным пользовательским интерфейсом. Тема имеет следующие характеристики:
- Использует шрифт Roboto.
- Отображает действия в полноэкранном режиме без строки состояния или панели действий.
- Применяет сплошной черный фон
Чтобы применить тему Glass, не объявляйте ее в манифесте Android.
Если у вас есть собственный стиль для частей вашей посуды и вам нужна тема Glass по умолчанию для всего остального, наследуйте ее от Theme.DeviceDefault
с parent
атрибутом:
<resources>
<style name="CustomTheme" parent="@android:style/Theme.DeviceDefault">
<!-- Theme customization goes here. -->
</style>
</resources>
Дополнительные сведения о создании тем см. в руководстве для разработчиков Android «Стили и темы» .
Карты в стеклянном стиле
Класс CardBuilder
создает правильно сформированные карты с учетом набора свойств. По возможности используйте макеты, предоставляемые CardBuilder.Layout
, чтобы ваш контент выглядел и воспринимался как другой контент на Glass.
Чтобы использовать CardBuilder
:
- Создайте экземпляр
CardBuilder
, придав ему желаемый макет изCardBuilder.Layout
. - Установите свойства карточки, такие как текст, сноска и метка времени.
- Вызовите
CardBuilder.getView()
, чтобы преобразовать карту вView
Android, илиCardBuilder.getRemoteViews()
, чтобы преобразовать ее в объектRemoteViews
. - Используйте
View
в своих действиях, макетах или вCardScrollView
или используйтеRemoteViews
вLiveCard
.
Общие функции пользовательского интерфейса
Многие макеты, предоставляемые 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
выглядит как стандартное стеклянное меню. Он имеет центрированный значок и заголовок, а также необязательную сноску. Используйте этот макет для экранов подтверждения (например, переход от «Удаление» к «Удаление» после того, как пользователь выбирает пункт меню). Если вам нужно настоящее меню, вместо него следует использовать стандартное меню опций.
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()
, вы можете манипулировать представлениями встроенного макета, передавая их идентификаторы непосредственно в методы установки 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
Более подробный пример смотрите в проекте GitHub ApiDemo .
ALERT
Макет CardBuilder.Layout.ALERT
содержит большой значок по центру с основным сообщением и сноской. Используйте этот макет в Dialog
, чтобы отобразить важное информационное сообщение, предупреждение или ошибку в вашей стеклянной посуде.
В следующем примере показана реализация AlertDialog
, закрытие карты и открытие настроек Wi-Fi, когда пользователь касается карты:
- Создайте класс, расширяющий
Dialog
. - Создайте карточку с помощью
CardBuilder
с макетомCardBuilder.Layout.ALERT
, а затем настройте представление содержимого с помощью этой карточки. (Необязательно) Создайте
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); } }
(Необязательно) В своей деятельности реализуйте
OnClickListener
для обработки любых дополнительных потоков, когда пользователь нажимает. Дополнительную информацию о запуске действий по настройке, таких как 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>
Расположение левой колонки
Это определяет левый столбец размером 240 пикселей и правый столбец размером 400 пикселей в форме двух 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. Создайте этот файл как res/values/dimens.xml
в своем проекте 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>