Дизайн карты

В этом документе рассказывается, как следовать стилю 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 :

  1. Создайте экземпляр CardBuilder , придав ему желаемый макет из CardBuilder.Layout .
  2. Установите свойства карточки, такие как текст, сноска и метка времени.
  3. Вызовите CardBuilder.getView() , чтобы преобразовать карту в View Android, или CardBuilder.getRemoteViews() , чтобы преобразовать ее в объект RemoteViews .
  4. Используйте 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 имеет мозаику изображений на заднем плане и текст краткой подписи, выровненный по нижней части карточки. Рядом с подписью также можно разместить значок, обозначающий, например, личность человека, связанного с содержимым карты.

Рисунок 1 : (фоновое изображение предоставлено photoeverwhere.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 выглядит как стандартное стеклянное меню. Он имеет центрированный значок и заголовок, а также необязательную сноску. Используйте этот макет для экранов подтверждения (например, переход от «Удаление» к «Удаление» после того, как пользователь выбирает пункт меню). Если вам нужно настоящее меню, вместо него следует использовать стандартное меню опций.

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, когда пользователь касается карты:

  1. Создайте класс, расширяющий Dialog .
  2. Создайте карточку с помощью CardBuilder с макетом CardBuilder.Layout.ALERT , а затем настройте представление содержимого с помощью этой карточки.
  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. (Необязательно) В своей деятельности реализуйте OnClickListener для обработки любых дополнительных потоков, когда пользователь нажимает. Дополнительную информацию о запуске действий по настройке, таких как 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>

Расположение левой колонки

Это определяет левый столбец размером 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>