עיצוב כרטיסים

במסמך הזה נפרט איך לעקוב אחר סגנון Glass וליישם את השיטות המומלצות של ממשק המשתמש בזמן השימוש ב-GDK.

עיצוב זכוכית

Glass מחיל עיצוב סטנדרטי על Glassware, כך שהוא נשאר תואם לשאר ממשק המשתמש. לעיצוב יש את המאפיינים הבאים:

  • שימוש בגופן Roboto
  • הצגת הפעילויות במסך מלא ללא שורת סטטוס או סרגל פעולות
  • החלת רקע מוצק, שחור

כדי להחיל את עיצוב ה-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 או להמיר אותו ל-RemoteViews כדי להמיר אותו למכשיר.
  4. אפשר להשתמש בView בפעילויות, בפריסות או בCardScrollView, או להשתמש בRemoteViews בLiveCard.

תכונות נפוצות בממשק המשתמש

חלק גדול מהפריסות שסופקו על ידי CardBuilder תומכות בתכונות הנפוצות של ממשק המשתמש, שמפורטות בהמשך. במסמכי התיעוד של הפריסות השונות בדף CardBuilder.Layout תוכלו לראות את רשימת התכונות הנתמכות על ידי כל סוג של כרטיס.

סמל ייחוס

סמל השיוך הוא סמל אופציונלי של 36x36 פיקסלים שמופיע בפינה השמאלית התחתונה של כרטיס, ומשמאל לחותמת הזמן. תוכלו להגדיר את הסמל הזה על ידי התקשרות ל-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 יש פסיפס של תמונות ברקע וטקסט של כתוביות קצרות מיושר בתחתית הכרטיס. ניתן גם להציב סמל לצד הכיתוב כדי לייצג, לדוגמה, את זהותו של אדם המשויך לתוכן הכרטיס.

איור 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. כך תוכלו לעצב ממשק משתמש מותאם אישית לאפליקציה, אבל להמשיך להשתמש במיקום הנכון של הערת השוליים, חותמת הזמן, סמל השיוך ואינדיקטור הערימה של כרטיס, לפי הצורך.

אחרי שמתקשרים ל-CardBuilder.getView(), משתמשים בתוצאה findViewById() כדי לקבל גישה לצפיות בתוך הפריסה המוטמעת. בדומה לכך, אם תתקשרו אל CardBuilder.getRemoteViews(), תוכלו לשנות את תצוגות הפריסה המוטמעת על ידי העברת המזהים שלהן ישירות לשיטות המגדיר של 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

דוגמה מפורטת יותר זמינה בפרויקט ApiDemo ב-GitHub.

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 לא עונה על הצרכים שלכם.

הפריסה הראשית

הפריסה הזו מגדירה את המרווח הפנימי והכותרת התחתונה הרגילות של כרטיס. תנו לתצוגות שלכם את המקום הריק 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>

פריסת העמודה השמאלית

פעולה זו מגדירה עמודה שמאלית של 240 פיקסלים ועמודה ימנית של 400 פיקסלים בפורמט של RelativeLayout שניות שאליהן ניתן להוסיף את הצפיות.

<?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 סטנדרטי. יצירת הקובץ הזה בתור res/values/dimens.xml בפרויקט Android שלך.

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