卡片設計

本文件將說明如何使用 Glass 樣式,並在使用 GDK 時實作常見的 UI 最佳做法。

玻璃主題

Glass 將標準主題套用到您的 Glassware,因此與使用者介面的其他部分一致。主題具有下列特性:

  • 使用 Roboto 字體
  • 以全螢幕顯示沒有狀態列或動作列的活動
  • 已套用純黑色背景

如要套用 Google 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() 以將資訊卡轉換為 Android View,或呼叫 CardBuilder.getRemoteViews() 以將其轉換為 RemoteViews 物件。
  4. 在活動、版面配置或 CardScrollView 中使用 View,或在 LiveCard 中使用 RemoteViews

常用 UI 功能

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 的可用版面配置。

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

COLUMNSCOLUMNS_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 卡範本。這可讓您為應用程式設計自訂 UI,但如果需要,則可以使用資訊卡的註腳、時間戳記、歸屬圖示和堆疊指標。

呼叫 CardBuilder.getView() 後,請使用結果中的 findViewById() 來存取內嵌版面配置中的檢視畫面。同樣地,如果呼叫 CardBuilder.getRemoteViews(),可以將內嵌版面配置的檢視畫面直接傳遞至 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 設定:

  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,在使用者輕觸時處理任何其他流程。如要進一步瞭解如何啟動設定活動 (例如 WiFi),請參閱啟動設定一文。

  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>

左欄版面配置

這會定義由兩個 RelativeLayout 所組成的 240px 左欄和 400px 右欄,您可將其放入檢視中。

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