การออกแบบการ์ด

เอกสารนี้จะอธิบายวิธีทําตามสไตล์ของ Glass และนําแนวทางปฏิบัติแนะนําทั่วไปเกี่ยวกับ UI ไปใช้เมื่อใช้ GDK

ธีมกระจก

Glass นําธีมมาตรฐานไปใช้กับ Glassware เพื่อให้สอดคล้องกับอินเทอร์เฟซส่วนอื่นๆ ของผู้ใช้ ธีมดังกล่าวมีลักษณะดังต่อไปนี้

  • ใช้แบบอักษร Roboto
  • แสดงกิจกรรมแบบเต็มหน้าจอโดยไม่มีแถบสถานะหรือแถบการทํางาน
  • ใช้พื้นหลังสีดําทึบ

หากต้องการใช้ธีม Glass อย่าประกาศธีมในไฟล์ Manifest ของ 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. ใช้ View ในกิจกรรม เลย์เอาต์ หรือใน CardScrollView หรือใช้ RemoteViews ใน LiveCard

ฟีเจอร์ 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 มีภาพโมเสคในพื้นหลังและข้อความคําบรรยายวิดีโอสั้นๆ ที่กําหนดไว้ที่ด้านล่างของการ์ด นอกจากนี้ คุณยังวางไอคอนไว้ข้างคําบรรยายวิดีโอเพื่อแสดงถึงตัวตน เช่น ตัวตนของบุคคลที่เชื่อมโยงกับเนื้อหาในการ์ดได้ด้วย

รูปที่ 1: (ภาพพื้นหลังโดย photobeforewhere.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() คุณก็สามารถเปลี่ยนมุมมองของเลย์เอาต์ที่ฝังไว้ได้โดยส่งรหัสของตนไปยังเมธอด setRemoteViews โดยตรง

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

ต่อไปนี้เป็นเลย์เอาต์พื้นฐาน 2 แบบที่คุณสามารถใช้ได้ในกรณีที่คลาส 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 2 คอลัมน์ที่คุณมุมมองได้

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