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

يتناول هذا المستند كيفية اتباع نمط 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 متى أمكن ذلك حتى يظهر المحتوى الخاص بك بمظهر ومضمون آخر على الزجاج.

لاستخدام 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.

ALERT

يحتوي تنسيق CardBuilder.Layout.ALERT على رمز متوسط تتوسطه رسالة أساسية وحاشية سفلية. يمكنك استخدام هذا التنسيق في Dialog لعرض رسالة معلوماتية مهمة أو تحذير أو خطأ في Glassware.

يوضح المثال التالي تنفيذ AlertDialog ويتجاهل البطاقة ويفتح إعدادات WiFi عند نقر المستخدم على البطاقة:

  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>