이 문서에서는 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
앱을 사용하려면 다음 안내를 따르세요
CardBuilder
인스턴스를 만들어CardBuilder.Layout
에서 원하는 레이아웃을 제공합니다.- 텍스트, 각주, 타임스탬프와 같은 카드 속성을 설정합니다.
CardBuilder.getView()
를 호출하여 카드를 AndroidView
로 변환하거나CardBuilder.getRemoteViews()
를 호출하여RemoteViews
객체로 변환합니다.- 활동, 레이아웃 또는
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
를 사용하여 사용 가능한 레이아웃을 보여줍니다.
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
레이아웃의 배경에는 이미지 모자이크가 있고 카드 하단에 짧은 자막 텍스트가 정렬되어 있습니다. 자막 옆에 아이콘을 배치하여 카드의 콘텐츠와 관련된 사람의 신원을 나타낼 수도 있습니다.
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();
MENU
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 설정을 엽니다.
Dialog
를 확장하는 클래스를 만듭니다.CardBuilder.Layout.ALERT
레이아웃과 함께CardBuilder
를 사용하여 카드를 만든 다음 이 카드로 콘텐츠 뷰를 설정합니다.(선택사항)
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); } }
(선택사항) 활동에서
OnClickListener
를 구현하여 사용자가 탭할 때 추가 흐름을 처리합니다. Wi-Fi와 같은 설정 활동을 시작하는 방법에 관한 자세한 내용은 시작 설정을 참고하세요.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>