Thiết kế thiệp

Tài liệu này trình bày cách làm theo kiểu Glass và triển khai các phương pháp hay nhất về giao diện người dùng phổ biến khi sử dụng GDK.

Giao diện Glass

Glass áp dụng một giao diện tiêu chuẩn cho Glassware của bạn, vì vậy Glassware nhất quán với phần còn lại của giao diện người dùng. Giao diện có các đặc điểm sau:

  • Sử dụng kiểu chữ Roboto
  • Hiển thị các hoạt động ở chế độ toàn màn hình không có thanh trạng thái hoặc thanh hành động
  • Áp dụng nền đen đồng nhất

Để áp dụng giao diện Glass, không khai báo giao diện trong Tệp kê khai Android.

Nếu bạn có một kiểu tuỳ chỉnh cho các phần của Glassware và muốn giao diện Glass mặc định cho mọi nội dung khác, hãy kế thừa từ Theme.DeviceDefault bằng thuộc tính parent:

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

Xem hướng dẫn dành cho nhà phát triển Android về Kiểu và Giao diện để biết thêm thông tin về cách tạo giao diện.

Thẻ kiểu kính

Lớp CardBuilder tạo các thẻ có định dạng phù hợp với một tập hợp các thuộc tính. Sử dụng các bố cục do CardBuilder.Layout cung cấp bất cứ khi nào có thể để nội dung của bạn có giao diện như nội dung khác trên Glass.

Cách sử dụng CardBuilder:

  1. Tạo một thực thể của CardBuilder, cung cấp cho bố cục mong muốn từ CardBuilder.Layout.
  2. Đặt các thuộc tính của thẻ, chẳng hạn như văn bản, chú thích cuối trang và dấu thời gian.
  3. Gọi CardBuilder.getView() để chuyển đổi thẻ thành Android View hoặc CardBuilder.getRemoteViews() để chuyển đổi thành đối tượng RemoteViews.
  4. Sử dụng View trong các hoạt động, bố cục hoặc trong CardScrollView hoặc sử dụng RemoteViews trong LiveCard.

Các tính năng phổ biến của giao diện người dùng

Nhiều bố cục do CardBuilder cung cấp hỗ trợ các tính năng giao diện người dùng phổ biến như mô tả dưới đây. Hãy xem tài liệu về từng bố cục trong CardBuilder.Layout để biết danh sách các tính năng mà từng loại thẻ hỗ trợ.

Biểu tượng thuộc tính

Biểu tượng phân bổ là biểu tượng không bắt buộc có kích thước 36 x 36 pixel xuất hiện ở góc dưới cùng bên phải của thẻ và ở bên phải dấu thời gian. Đặt biểu tượng này bằng cách gọi CardBuilder.setAttributionIcon() để xác định ứng dụng của bạn, đặc biệt là trên thẻ trực tiếp để người dùng có thể xem nhanh và xem nguồn thông tin trên thẻ đó.

Chỉ báo ngăn xếp

Chỉ báo ngăn xếp do CardBuilder.showStackIndicator() kiểm soát là một nếp gập góc xuất hiện ở góc trên cùng bên phải của thẻ. Sử dụng thẻ này làm chỉ báo trực quan cho biết thẻ của bạn đại diện cho một gói các thẻ khác mà người dùng có thể nhấn trực tiếp vào.

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();

Bố cục

Các ví dụ sau đây cho thấy các bố cục có sẵn bằng cách sử dụng CardBuilder.

TEXTTEXT_FIXED

Bố cục CardBuilder.Layout.TEXT hiển thị văn bản tràn lề với tranh khảm hình ảnh không bắt buộc trong nền. Văn bản sẽ tự động đổi kích thước cho vừa với không gian có sẵn. CardBuilder.Layout.TEXT_FIXED tương tự nhưng sửa văn bản thành kích thước nhỏ hơn.

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

Bố cục CardBuilder.Layout.COLUMNS hiển thị tranh khảm hoặc biểu tượng ở bên trái thẻ và văn bản ở bên phải. Văn bản có kích thước động để phù hợp nhất với không gian hiện có. Để cố định kích thước văn bản, hãy sử dụng 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

Bố cục CardBuilder.Layout.CAPTION có tranh khảm hình ảnh ở nền và văn bản phụ đề ngắn được căn chỉnh ở cuối thẻ. Bạn cũng có thể đặt một biểu tượng bên cạnh phụ đề để thể hiện, chẳng hạn như danh tính của người được liên kết với nội dung của thẻ.

Hình 1: (hình nền của photomọi nơi.co.uk, bị cắt)
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

Bố cục CardBuilder.Layout.TITLE có tranh khảm hình ảnh ở nền với tiêu đề ở giữa và biểu tượng không bắt buộc ở cuối thẻ. Bố cục này thường dùng để biểu thị địa chỉ liên hệ hoặc chia sẻ mục tiêu. Bố cục này không hỗ trợ chú thích cuối trang và dấu thời gian.

View view = new CardBuilder(context, CardBuilder.Layout.TITLE)
    .setText("TITLE Card")
    .setIcon(R.drawable.ic_phone)
    .addImage(R.drawable.beach)
    .getView();

AUTHOR

Sử dụng bố cục CardBuilder.Layout.AUTHOR để hiển thị một thông báo hoặc cuộc trò chuyện tập trung vào tác giả. Tính năng này hỗ trợ tranh ảnh trên nền, biểu tượng dùng làm hình đại diện của tác giả, tiêu đề và tiêu đề phụ để bạn có thể liệt kê thông tin nhận dạng.

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();

Bố cục CardBuilder.Layout.MENU có dạng một trình đơn Glass tiêu chuẩn. Phần này có biểu tượng và tiêu đề ở giữa, chú thích cuối trang (không bắt buộc). Sử dụng bố cục này cho màn hình xác nhận (ví dụ: chuyển từ "Xoá" sang "Đã xoá" sau khi người dùng chọn một mục trong trình đơn. Nếu cần một trình đơn thực tế, bạn nên sử dụng trình đơn tuỳ chọn chuẩn.

View view = new CardBuilder(context, CardBuilder.Layout.MENU)
    .setText("MENU layout")
    .setIcon(R.drawable.ic_phone)
    .setFootnote("Optional menu description")
    .getView();

EMBED_INSIDE

Bố cục CardBuilder.Layout.EMBED_INSIDE nhúng XML bố cục tuỳ chỉnh do bạn thiết kế vào mẫu thẻ Glass tiêu chuẩn. Điều này cho phép bạn thiết kế giao diện người dùng tuỳ chỉnh cho ứng dụng của mình nhưng vẫn có vị trí chính xác của chú thích cuối trang, dấu thời gian, biểu tượng phân bổ và chỉ báo ngăn xếp nếu cần.

Sau khi gọi CardBuilder.getView(), hãy sử dụng findViewById() trên kết quả để truy cập vào các thành phần hiển thị bên trong bố cục được nhúng. Tương tự như vậy, nếu gọi CardBuilder.getRemoteViews(), bạn có thể thao tác với các thành phần hiển thị của bố cục được nhúng bằng cách truyền trực tiếp mã nhận dạng của thành phần đó vào các phương thức setter 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

Để biết ví dụ chi tiết hơn, hãy xem Dự án ApiDemo của GitHub.

ALERT

Bố cục CardBuilder.Layout.ALERT chứa biểu tượng lớn ở giữa với thông báo chính và chú thích cuối trang. Sử dụng bố cục này trong Dialog để hiển thị thông báo, cảnh báo hoặc lỗi thông tin quan trọng trong Glassware của bạn.

Ví dụ sau đây cho thấy cách triển khai AlertDialog và loại bỏ thẻ, đồng thời mở phần cài đặt Wi-Fi khi người dùng nhấn vào thẻ:

  1. Tạo một lớp mở rộng Dialog.
  2. Tạo thẻ bằng cách sử dụng CardBuilder với bố cục CardBuilder.Layout.ALERT, sau đó đặt chế độ xem nội dung bằng thẻ này.
  3. (Không bắt buộc) Tạo một GestureDetector để xử lý các cử chỉ của người dùng trên thẻ này.

    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. (Không bắt buộc) Trong hoạt động của bạn, hãy triển khai một OnClickListener để xử lý mọi luồng bổ sung khi người dùng nhấn vào. Để biết thêm thông tin về các hoạt động cài đặt bắt đầu như Wi-Fi, hãy xem Các chế độ cài đặt bắt đầu.

  5. Gọi hàm khởi tạo AlertDialog để hiển thị thẻ cảnh báo.

    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();
    
            ...
        }
    }
    

Bố cục XML

Dưới đây là hai bố cục thẻ cơ bản mà bạn có thể sử dụng nếu lớp CardBuilder không đáp ứng được nhu cầu của bạn.

Bố cục chính

Bố cục này xác định khoảng đệm và chân trang chuẩn cho một thẻ. Đặt thành phần hiển thị của riêng bạn vào RelativeLayout trống.

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

Bố cục cột bên trái

Thao tác này xác định cột bên trái 240px và cột bên phải 400px ở dạng hai RelativeLayout mà bạn có thể đặt chế độ xem vào đó.

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

Phương diện chuẩn

Sử dụng tệp này cùng với các bố cục trước đó hoặc bố cục của riêng bạn để tuân thủ kiểu Glass tiêu chuẩn. Tạo tệp này dưới dạng res/values/dimens.xml trong dự án Android của bạn.

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