カードデザイン

このドキュメントでは、Glass のスタイルを実践し、GDK を使用する際の一般的な UI のベスト プラクティスを実装する方法について説明します。

Glass のテーマ

Glass は Glassware に標準のテーマを適用するため、残りのユーザー インターフェースとの一貫性が維持されます。テーマには次の特性があります。

  • Roboto Typeface を使用する
  • ステータスバーやアクションバーを使わずにアクティビティを全画面表示します
  • 黒一色の背景を適用

Glass テーマを適用するには、Android マニフェストでテーマを宣言しないでください。

Glassware の一部にカスタム スタイルがあり、その他すべてにデフォルトの Glass テーマが必要な場合は、parent 属性を持つ Theme.DeviceDefault から継承します。

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

テーマの作成について詳しくは、Android デベロッパー ガイドのスタイルとテーマをご覧ください。

Glass スタイルのカード

CardBuilder クラスは、一連のプロパティに基づいて適切な形式のカードを作成します。可能な限り、CardBuilder.Layout が提供するレイアウトを使用して、コンテンツが Glass の他のコンテンツと同じように見えるようにしてください。

CardBuilder を使用するには:

  1. CardBuilder のインスタンスを作成し、CardBuilder.Layout から目的のレイアウトを指定します。
  2. カードのプロパティ(テキスト、脚注、タイムスタンプなど)を設定します。
  3. カードを Android View に変換するには CardBuilder.getView() を呼び出し、RemoteViews オブジェクトに変換するには CardBuilder.getRemoteViews() を呼び出します。
  4. アクティビティ、レイアウト、CardScrollViewView を使用するか、LiveCardRemoteViews を使用します。

一般的な 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 レイアウトでは、背景に画像のモザイクが使用され、短い字幕テキストはカードの下部に配置されています。また、字幕の横にアイコンを配置して、カードのコンテンツに関連付けられている人物の ID などを表すこともできます。

図 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() を呼び出して、埋め込みレイアウトのビューを操作するには、その ID を 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 クラスがニーズに合わない場合に使用可能な基本的なカード レイアウトを 2 つ紹介します。

メイン レイアウト

このレイアウトでは、カードの標準のパディングとフッターを定義します。空の 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>

左列のレイアウト

これにより、ビューを配置できる 2 つの RelativeLayout の形式で、240 ピクセルの左の列と 400 ピクセルの右の列が定義されます。

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