카드 디자인

이 문서에서는 Glass 스타일을 따르는 방법과 GDK를 사용할 때 일반적인 UI 권장사항을 구현하는 방법을 설명합니다.

유리 테마

Glass는 Glass웨어에 표준 테마를 적용하므로 나머지 사용자 인터페이스와 일관되게 유지됩니다. 이 테마에는 다음과 같은 특성이 있습니다.

  • Roboto 서체 사용
  • 상태 표시줄 또는 작업 모음 없이 활동을 전체 화면으로 표시합니다.
  • 단색, 검은색 배경 적용

Glass 테마를 적용하려면 Android 매니페스트에 테마를 선언하지 마세요.

Glassware 일부 부분에 맞춤 스타일이 있고 다른 모든 부분에는 기본 Glass 테마가 필요한 경우 parent 속성을 사용하여 Theme.DeviceDefault에서 상속합니다.

<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()를 호출하면 ID가 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에서 이 레이아웃을 사용하여 Glass 소프트웨어에서 중요한 정보 메시지, 경고 또는 오류를 표시합니다.

다음 예는 AlertDialog의 구현을 보여주고, 카드를 닫고, 사용자가 카드를 탭할 때 Wi-Fi 설정을 엽니다.

  1. Dialog를 확장하는 클래스를 만듭니다.
  2. CardBuilder.Layout.ALERT 레이아웃과 함께 CardBuilder를 사용하여 카드를 만든 다음 이 카드로 콘텐츠 뷰를 설정합니다.
  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>

왼쪽 열 레이아웃

이는 뷰를 배치할 수 있는 2개의 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>