تصميم البطاقة

يتناول هذا المستند كيفية اتباع نمط Glass وتنفيذ أفضل الممارسات الشائعة لواجهة المستخدم عند استخدام GDK.

مظهر Glass

يطبق 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، أو CardBuilder.getRemoteViews() لتحويلها إلى عنصر RemoteViews.
  4. يمكنك استخدام View في أنشطتك أو تنسيقاتك أو في CardScrollView، أو استخدام RemoteViews في LiveCard.

الميزات الشائعة لواجهة المستخدم

يتوافق العديد من التنسيقات التي يوفّرها 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: (صورة خلفية باستخدام 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 للتعامل مع أي مسارات إضافية عندما ينقر المستخدم. لمزيد من المعلومات حول بدء أنشطة الإعدادات مثل WiFi، راجع إعدادات البدء.

  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>