Projekt karty

Ten dokument pokazuje, jak zachować zgodność ze stylem Glass i wdrażać typowe sprawdzone metody dotyczące interfejsu podczas korzystania z GDK.

Motyw szklany

Google Glass stosuje do oprogramowania Glassware standardowy motyw, dzięki czemu zachowuje spójność z resztą interfejsu. Motyw ma te cechy:

  • Używa kroju Roboto.
  • Wyświetla aktywności na pełnym ekranie bez paska stanu ani paska działań
  • Zastosuj jednolite, czarne tło

Aby zastosować motyw Glass, nie deklaruj motywu w pliku manifestu Androida.

Jeśli masz niestandardowy styl dla elementów Glassware i chcesz ustawić domyślny motyw szklany dla całej reszty, możesz odziedziczyć go z elementu Theme.DeviceDefault za pomocą atrybutu parent:

<resources>
    <style name="CustomTheme" parent="@android:style/Theme.DeviceDefault">
        <!-- Theme customization goes here. -->
    </style>
</resources>

Więcej informacji o tworzeniu motywów znajdziesz w przewodniku dla programistów aplikacji na Androida na temat stylów i motywów.

Karty ze szkła

Klasa CardBuilder tworzy poprawnie sformatowane karty z uwzględnieniem zestawu właściwości. O ile to możliwe, korzystaj z układów udostępnianych przez CardBuilder.Layout, aby Twoje treści wyglądały i wyglądały jak inne materiały w Google Glass.

Aby użyć aplikacji CardBuilder:

  1. Utwórz instancję CardBuilder z wybranym układem z tabeli CardBuilder.Layout.
  2. Ustaw właściwości karty, takie jak tekst, przypis i sygnatura czasowa.
  3. Wywołaj CardBuilder.getView(), aby przekonwertować kartę na Androida, View, lub CardBuilder.getRemoteViews(), aby przekonwertować ją na obiekt RemoteViews.
  4. Korzystaj z View w działaniach, układach, w CardScrollView lub RemoteViews w LiveCard.

Wspólne funkcje interfejsu

Wiele układów dostępnych w CardBuilder obsługuje typowe funkcje interfejsu opisane poniżej. Listę funkcji obsługiwanych przez poszczególne typy kart znajdziesz w dokumentacji poszczególnych układów na CardBuilder.Layout.

Ikona informacji

Ikona informacji o pochodzeniu danych to opcjonalna ikona o wymiarach 36 × 36 pikseli, widoczna w prawym dolnym rogu karty i po prawej stronie sygnatury czasowej. Aby ustawić tę ikonę, wywołaj CardBuilder.setAttributionIcon(), aby zidentyfikować aplikację, zwłaszcza na aktywnych kartach. Dzięki temu użytkownik może szybko spojrzeć na źródło informacji na danej karcie.

Wskaźnik stosu

Wskaźnik stosu, którym steruje CardBuilder.showStackIndicator(), to zagięcie narożne widoczne w prawym górnym rogu karty. Użyj tej wartości jako wizualnego wskaźnika, który sygnalizuje, że karta reprezentuje pakiet innych kart, z których użytkownik może bezpośrednio korzystać.

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

Układy

W przykładach poniżej pokazujemy układy dostępne przy użyciu parametru CardBuilder.

TEXTTEXT_FIXED

W układzie CardBuilder.Layout.TEXT wyświetla się tekst na całej linii, z opcjonalną mozaiką w tle. Rozmiar tekstu dynamicznie się zmienia, aby jak najlepiej dopasować go do dostępnego miejsca. CardBuilder.Layout.TEXT_FIXED jest podobny, ale zmniejsza rozmiar tekstu.

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

W układzie CardBuilder.Layout.COLUMNS po lewej stronie karty wyświetla się mozaika obrazów lub ikona, a po prawej tekst. Rozmiar tekstu jest dynamicznie dopasowywany do dostępnego miejsca. Aby utrzymać stały rozmiar tekstu, użyj wartości 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

W układzie CardBuilder.Layout.CAPTION w tle znajduje się mozaika obrazów oraz krótki tekst napisów wyrównany u dołu karty. Obok napisu można też umieścić ikonę, która reprezentuje np. tożsamość osoby związanej z zawartością karty.

Rys. 1 (obraz tła: photoeverywhere.co.uk, przycięty)
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

W tle układu CardBuilder.Layout.TITLE znajduje się mozaika obrazów z wyśrodkowanym tytułem i opcjonalną ikoną na dole. Ten układ jest często używany do reprezentowania kontaktów lub udostępniania celów. Ten układ nie obsługuje przypisów ani sygnatur czasowych.

View view = new CardBuilder(context, CardBuilder.Layout.TITLE)
    .setText("TITLE Card")
    .setIcon(R.drawable.ic_phone)
    .addImage(R.drawable.beach)
    .getView();

AUTHOR

Użyj układu CardBuilder.Layout.AUTHOR, aby wyświetlić wiadomość lub wątek, w których zaznaczenie jest skupione na autorze. Można w nim umieścić mozaikę w tle, ikonę używaną jako awatar autora oraz nagłówek i podtytuł, w którym można podać dane identyfikacyjne.

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

Układ CardBuilder.Layout.MENU wygląda jak standardowe menu Google Glass. Ma on wyśrodkowaną ikonę, tytuł i opcjonalny przypis. Zastosuj ten układ do ekranów potwierdzenia (np. przejście z „Usuwanie” do „Usunięte” po wybraniu przez użytkownika pozycji menu). Jeśli potrzebujesz prawdziwego menu, użyj standardowego menu opcji.

View view = new CardBuilder(context, CardBuilder.Layout.MENU)
    .setText("MENU layout")
    .setIcon(R.drawable.ic_phone)
    .setFootnote("Optional menu description")
    .getView();

EMBED_INSIDE

W układzie CardBuilder.Layout.EMBED_INSIDE umieszczony jest własny kod XML układu niestandardowego układu w standardowym szablonie karty Google Glass. Dzięki temu możesz zaprojektować niestandardowy interfejs aplikacji z zachowaniem prawidłowego rozmieszczenia przypisu, sygnatury czasowej, ikony atrybucji i wskaźnika stosu karty, jeśli są potrzebne.

Po wywołaniu właściwości CardBuilder.getView() użyj w wyniku przycisku findViewById(), aby uzyskać dostęp do widoków w umieszczonym układzie. Podobnie, jeśli wywołujesz metodę CardBuilder.getRemoteViews(), możesz manipulować widokami układu umieszczonego, przekazując ich identyfikatory bezpośrednio do metod ustawiania 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

Bardziej szczegółowy przykład znajdziesz w projekcie Apigee na GitHubie.

ALERT

Układ CardBuilder.Layout.ALERT zawiera dużą wyśrodkowaną ikonę z wiadomością główną i przypisem. Użyj tego układu w elemencie Dialog, aby wyświetlać w Google Glassware ważny komunikat informacyjny, ostrzeżenie lub błąd.

Poniższy przykład pokazuje implementację AlertDialog. Gdy użytkownik kliknie kartę, zamyka kartę i otwiera ustawienia Wi-Fi:

  1. Utwórz zajęcia z rozszerzeniem Dialog.
  2. Utwórz kartę za pomocą elementu CardBuilder z układem CardBuilder.Layout.ALERT, a następnie ustaw widok treści za pomocą tej karty.
  3. (Opcjonalnie) Utwórz GestureDetector do obsługi gestów użytkownika na tej karcie.

    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. (Opcjonalnie) W swojej aktywności zaimplementuj OnClickListener do obsługi dodatkowych procesów, gdy użytkownik kliknie reklamę. Więcej informacji o rozpoczynaniu działań związanych z ustawieniami, takich jak Wi-Fi, znajdziesz w sekcji Uruchamianie ustawień.

  5. Aby wyświetlić kartę alertu, wywołaj konstruktor 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();
    
            ...
        }
    }
    

Układy XML

Oto 2 podstawowe układy kart, których możesz użyć, jeśli klasa CardBuilder nie spełnia Twoich potrzeb.

Układ główny

Ten układ definiuje standardowe dopełnienie i stopkę karty. Umieść własne widoki w pustym polu 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>

Układ lewej kolumny

Definiuje ona lewą kolumnę o długości 240 pikseli i prawą o długości 400 pikseli w postaci 2 elementów RelativeLayout, w których można umieszczać widoki.

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

Wymiary standardowe

Aby zachować standardowy styl Glass, używaj tego pliku w połączeniu z poprzednimi układami lub własnymi układami. Utwórz ten plik jako res/values/dimens.xml w swoim projekcie na Androida.

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