Дизайн карты

В этом документе рассказывается, как следовать стилю Glass и внедрять общие передовые методы пользовательского интерфейса при использовании GDK.

Стеклянная тема

Glass применяет к вашему Glassware стандартную тему, чтобы она соответствовала остальному пользовательскому интерфейсу. Тема имеет следующие характеристики:

  • Использует шрифт Roboto
  • Отображает действия в полноэкранном режиме без строки состояния или панели действий.
  • Применяет сплошной черный фон

Чтобы применить тему Glass, не объявляйте тему в манифесте Android.

Если у вас есть пользовательский стиль для частей вашего Glassware и вы хотите использовать тему 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 : (фоновое изображение с сайта 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() , вы можете манипулировать представлениями вашего встроенного макета, передавая их идентификаторы непосредственно в методы установки 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 , чтобы отобразить важное информационное сообщение, предупреждение или ошибку в Glassware.

В следующем примере показана реализация 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>