تطوير التطبيقات

هذه المادة مخصّصة لمطوّري التطبيقات.

لكي يتوافق تطبيقك مع الشاشة الدوّارة، يجب استيفاء الشروط التالية:

  1. ضَع FocusParkingView في تنسيق النشاط المعنيّ.
  2. تأكَّد من أنّ المشاهدات التي يمكن التركيز عليها (أو لا يمكن التركيز عليها) ظاهرة.
  3. استخدِم FocusArea للالتفاف حول جميع طرق العرض التي يمكن التركيز عليها، باستثناء FocusParkingView.

يمكنك الاطّلاع على تفاصيل كلّ مهمة من هذه المهام أدناه، بعد إعداد بيئتك لمحاولة تطوير تطبيقات متوافقة مع الشاشات الدوّارة.

إعداد وحدة تحكّم دوّارة

قبل أن تتمكّن من بدء تطوير التطبيقات المزوّدة بجهاز تحكّم دوار، ستحتاج إلى جهاز تحكّم دوار أو بديل له. تتوفّر لك الخيارات الموضّحة أدناه.

المحاكي

source build/envsetup.sh && lunch car_x86_64-userdebug
m -j
emulator -wipe-data -no-snapshot -writable-system

يمكنك أيضًا استخدام aosp_car_x86_64-userdebug.

للوصول إلى وحدة التحكّم الدوّارة المحاكية:

  1. انقر على النقاط الثلاث في أسفل شريط الأدوات:

    الوصول إلى وحدة التحكّم الدوّارة المحاكية
    الشكل 1. الوصول إلى وحدة التحكّم الدوّارة المحاكية
  2. اختَر Car rotary (التدوير في السيارة) في نافذة عناصر التحكّم الموسّعة:

    اختيار مركبة دوار
    الشكل 2. اختَر رمز السيارة الدوّارة

لوحة مفاتيح USB

  • يُرجى توصيل لوحة مفاتيح USB بجهازك الذي يعمل بنظام التشغيل Android Automotive (AAOS). في بعض الحالات، يؤدي ذلك إلى منع ظهور لوحة المفاتيح على الشاشة.
  • استخدِم إصدار userdebug أو eng.
  • فعِّل فلترة الأحداث الرئيسية:
    adb shell settings put secure android.car.ROTARY_KEY_EVENT_FILTER 1
    
  • اطّلِع على الجدول التالي للعثور على المفتاح المقابل لكل إجراء:
    المفتاح الإجراء الدوار
    Q تدوير عكس عقارب الساعة
    E تدوير في اتجاه عقارب الساعة
    A دفع إلى اليسار
    D دفع لليمين
    واط دفع لأعلى
    S دفع إلى الأسفل
    F أو فاصلة الزر الأوسط
    R أو Esc زر الرجوع

أوامر ADB

يمكنك استخدام أوامر car_service لإدخال أحداث إدخال دوراني. يمكن تنفيذ هذه الأوامر على الأجهزة التي تعمل بنظام التشغيل Android Automotive (AAOS) أو على جهاز محاكاة.

أوامر car_service إدخال البيانات عبر وحدة تحكُّم دورانية
adb shell cmd car_service inject-rotary تدوير عكس عقارب الساعة
adb shell cmd car_service inject-rotary -c true تدوير في اتجاه عقارب الساعة
adb shell cmd car_service inject-rotary -dt 100 50 تدوير عكس عقارب الساعة عدة مرات (قبل 100 ملي ثانية وقبل 50 ملي ثانية)
adb shell cmd car_service inject-key 282 دفع إلى اليسار
adb shell cmd car_service inject-key 283 دفع لليمين
adb shell cmd car_service inject-key 280 دفع لأعلى
adb shell cmd car_service inject-key 281 دفع إلى الأسفل
adb shell cmd car_service inject-key 23 النقر على الزر الأوسط
adb shell input keyevent inject-key 4 النقر على زر الرجوع

وحدة التحكّم الدوّارة من المصنّع الأصلي للجهاز

عندما يكون جهاز التحكّم الدوار مفعّلاً وقيد الاستخدام، هذا هو الخيار الأكثر واقعية. ويُعدّ ذلك مفيدًا بشكل خاص لاختبار السرعة العالية.

FocusParkingView

FocusParkingView هو عرض شفاف في مكتبة واجهة المستخدم في السيارة (car-ui-library). يستخدم RotaryService هذا الإجراء لتمكين التنقّل باستخدام وحدة التحكّم الدوّارة. يجب أن يكون FocusParkingView أول عرض يمكن التركيز عليه في التنسيق. يجب وضعها خارج جميع FocusArea. يجب أن تحتوي كل نافذة على رمز FocusParkingView واحد. إذا كنت تستخدم التنسيق الأساسي لـ car-ui-library، الذي يحتوي على FocusParkingView، لن تحتاج إلى إضافة FocusParkingView آخر. في ما يلي مثال على FocusParkingView في RotaryPlayground.

<FrameLayout
   xmlns:android="http://schemas.android.com/apk/res/android"
   android:layout_width="match_parent"
   android:layout_height="match_parent">
   <com.android.car.ui.FocusParkingView
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"/>
   <FrameLayout
       android:layout_width="match_parent"
       android:layout_height="match_parent"/>
</FrameLayout>

في ما يلي الأسباب التي تتطلّب منك الحصول على FocusParkingView:

  1. لا يزيل نظام التشغيل Android التركيز تلقائيًا عند ضبط التركيز في نافذة أخرى. إذا جرّبت إزالة التركيز في النافذة السابقة، سيعيد Android تركيز العرض في تلك النافذة، ما يؤدي إلى التركيز على نافذتَين في الوقت نفسه. يمكن أن تؤدي إضافة FocusParkingView إلى كل نافذة إلى حلّ هذه المشكلة. تكون طريقة العرض هذه شفافة ويتم إيقاف تمييز التركيز التلقائي ، وبالتالي لا تظهر للمستخدم بغض النظر عمّا إذا كان يتم التركيز عليها أم لا. يمكن أن يأخذ التركيز حتى يتمكّن RotaryService من إيقاف التركيز عليه لإزالة تمييز التركيز.
  2. إذا كان هناك FocusArea واحد فقط في النافذة الحالية، يؤدي تدوير وحدة التحكّم في FocusArea إلى نقل RotaryService للتركيز من العرض على اليمين إلى العرض على اليسار (والعكس صحيح). يمكن أن تؤدي إضافة طريقة العرض هذه إلى كل نافذة إلى حلّ المشكلة. عندما يحدِّد RotaryService أنّ FocusParkingView هو هدف التركيز ، يمكنه تحديد أنّه على وشك بدء لفّ الشاشة، وعندها يتجنّب RotaryService لفّ الشاشة من خلال عدم نقل التركيز.
  3. عندما يشغِّل جهاز التحكّم الدوّار أحد التطبيقات، يركّز Android على أول عرض يمكن التركيز عليه، وهو دائمًا FocusParkingView. يحدِّد الرمز FocusParkingView العرض الأمثل للتركيز عليه، ثم يطبِّق التركيز.

طرق العرض التي يمكن التركيز عليها

تستند RotaryService إلى المفهوم الحالي لتركيز العرض في إطار عمل Android، والذي يعود إلى الأيام التي كانت فيها الهواتف تحتوي على لوحات مفاتيح خارجية وأزرار تحكم ألعاب. تم إعادة استخدام السمة الحالية android:nextFocusForward لتحديد العناصر الدوّارة (راجِع تخصيص FocusArea)، ولكن ليس android:nextFocusLeft وandroid:nextFocusRight و android:nextFocusUp وandroid:nextFocusDown.

RotaryService لا تركّز إلا على المشاهدات التي يمكن التركيز عليها. يمكن عادةً التركيز على بعض طرق العرض، مثل Button، أما الأرقام الأخرى، مثل TextView وViewGroup، فلا تكون عادةً قابلة للاستخدام. يمكن التركيز تلقائيًا على المشاهدات القابلة للنقر، ويمكن للنقر عليها تلقائيًا عند توفّر مستمع للنقر. إذا أدّى هذا المنطق التلقائي إلى إمكانية التركيز المطلوبة، لن تحتاج إلى ضبط إمكانية التركيز في العرض بشكل صريح. إذا لم يؤدي المنطق التلقائي إلى تحقيق إمكانية التركيز المطلوبة، اضبط سمة android:focusable على true أو false، أو اضبط إمكانية التركيز في العرض آليًا باستخدام View.setFocusable(boolean). لكي يتمكّن RotaryService من التركيز على العنصر، يجب أن يستوفي المشهد المتطلبات التالية:

  • يمكن التركيز عليه
  • مفعّلة
  • المراجعات المعروضة
  • أن تحتوي على قيم غير صفرية للعرض والارتفاع

إذا لم تستوفِ طريقة العرض جميع هذه المتطلبات، على سبيل المثال زر يمكن التركيز عليه ولكنّه غير مفعّل، لن يتمكّن المستخدم من استخدام عنصر التحكّم الدوّار للتركيز عليه. إذا كنت تريد التركيز على طرق العرض غير المفعّلة، ننصحك باستخدام حالة مخصّصة بدلاً من android:state_enabled للتحكّم في كيفية ظهور طريقة العرض بدون الإشارة إلى أنّه يجب أن يعتبرها Android غير مفعّلة. يمكن لتطبيقك إعلام العميل بسبب إيقاف العرض عند النقر عليه. يوضّح القسم التالي كيفية إجراء ذلك.

حالة مخصّصة

لإضافة حالة مخصّصة:

  1. لإضافة سمة مخصّصة إلى عرضك: على سبيل المثال، لإضافة حالة مخصّصة state_rotary_enabled إلى CustomView فئة العرض، استخدِم:
    <declare-styleable name="CustomView">
        <attr name="state_rotary_enabled" format="boolean" />
    </declare-styleable>
    
  2. لتتبُّع هذه الحالة، أضِف متغيّر مثيل إلى العرض مع طرق الوصول:
    private boolean mRotaryEnabled;
    public boolean getRotaryEnabled() { return mRotaryEnabled; }
    public void setRotaryEnabled(boolean rotaryEnabled) {
        mRotaryEnabled = rotaryEnabled;
    }
    
  3. لقراءة قيمة السمة عند إنشاء العرض:
    TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CustomView);
    mRotaryEnabled = a.getBoolean(R.styleable.CustomView_state_rotary_enabled);
    
  4. في فئة العرض، يمكنك إلغاء طريقة onCreateDrawableState() ثم إضافة الحالة المخصّصة، عند الاقتضاء. مثلاً:
    @Override
    protected int[] onCreateDrawableState(int extraSpace) {
        if (mRotaryEnabled) extraSpace++;
        int[] drawableState = super.onCreateDrawableState(extraSpace);
        if (mRotaryEnabled) {
            mergeDrawableStates(drawableState, { R.attr.state_rotary_enabled });
        }
        return drawableState;
    }
    
  5. يمكنك جعل معالِج النقرات في العرض يعمل بشكل مختلف حسب حالته. على سبيل المثال، قد لا يفعل معالج النقرة أيّ شيء أو قد يعرض إشعارًا منبثقًا عندما يكون mRotaryEnabled هو false.
  6. لجعل الزر يبدو غير مفعّل، استخدِم app:state_rotary_enabled بدلاً من android:state_enabled في الخلفية القابلة للرسم لعرضك. إذا لم يكن لديك حساب، عليك إضافة ما يلي:
    xmlns:app="http://schemas.android.com/apk/res-auto"
    
  7. إذا كان العرض غير مفعّل في أيّ تنسيقات، استبدِل android:enabled="false" ب app:state_rotary_enabled="false" ثم أضِف مساحة الاسم app، كما هو موضّح أعلاه.
  8. إذا تم إيقاف طريقة العرض آليًا، استبدِل طلبات البيانات إلى setEnabled() بطلبات البيانات إلى setRotaryEnabled().

FocusArea

استخدِم FocusAreas لتقسيم طرق العرض التي يمكن التركيز عليها إلى وحدات لتسهيل التنقّل وجعلها متسقة مع التطبيقات الأخرى. على سبيل المثال، إذا كان تطبيقك يتضمّن شريط أدوات، يجب أن يكون شريط الأدوات في FocusArea منفصل عن بقية التطبيق. يجب أيضًا فصل أشرطة علامات التبويب وغيرها من عناصر التنقّل عن بقية التطبيق. يجب أن يكون للقوائم الكبيرة FocusArea خاص بها بشكل عام. وإذا لم يكن الأمر كذلك، على المستخدمين التنقّل في القائمة بأكملها للوصول إلى بعض طرق العرض.

FocusArea هي فئة فرعية من LinearLayout في car-ui-library. عند تفعيل هذه الميزة، يرسم FocusArea تمييزًا عند التركيز على أحد أطفاله. لمزيد من المعلومات، يُرجى الاطّلاع على مقالة تخصيص ميزة "تمييز التركيز".

عند إنشاء كتلة تنقّل في ملف التنسيق، إذا كنت تريد استخدام علامة LinearLayout كسمة حاوية لهذه الكتلة، استخدِم FocusArea بدلاً من ذلك. بخلاف ذلك، احط الكتلة بعلامة FocusArea.

لا تُدرِج FocusArea في FocusArea آخر. يؤدي ذلك إلى سلوك تنقّل غير محدّد. تأكَّد من أنّ جميع طرق العرض التي يمكن التركيز عليها مُدمجة في FocusArea.

في ما يلي مثال على FocusArea في RotaryPlayground:

<com.android.car.ui.FocusArea
       android:layout_margin="16dp"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:orientation="vertical">
       <EditText
           android:layout_width="match_parent"
           android:layout_height="wrap_content"
           android:singleLine="true">
       </EditText>
   </com.android.car.ui.FocusArea>

تعمل FocusArea على النحو التالي:

  1. عند معالجة إجراءات التدوير والدفع، تبحث RotaryService عن نُسخ من FocusArea في التسلسل الهرمي للعرض.
  2. عند تلقّي حدث دوران، ينقل RotaryService التركيز إلى عرض آخر يمكنه جذب التركيز في FocusArea نفسه.
  3. عند تلقّي حدث تنبيه، ينقل RotaryService التركيز إلى طريقة عرض أخرى يمكنها جذب التركيز في FocusArea آخر (عادةً مجاور).

إذا لم تُدرِج أي FocusAreas في التنسيق، يتم التعامل مع العرض الجذر كمنطقة تركيز ضمنية. لا يمكن للمستخدم النقر لتحريك المحتوى في التطبيق، بل يمكنه بدلاً من ذلك التمرير بين جميع طرق العرض التي يمكن التركيز عليها، ما قد يكون مناسبًا للمحادثات.

تخصيص FocusArea

يمكن استخدام سمتَي عرض عاديتَين لتخصيص التنقّل الدوّري:

  • تسمح android:nextFocusForward لمطوّري التطبيقات بتحديد ترتيب التدوير في منطقة التركيز. هذه هي السمة نفسها المستخدَمة للتحكّم في ترتيب مفتاح التبويب لأجل التنقّل باستخدام لوحة المفاتيح. لا تستخدِم هذه السمة لإنشاء حلقة. بدلاً من ذلك، استخدِم app:wrapAround (راجِع ما يلي) لإنشاء حلقة.
  • يسمح android:focusedByDefault لمطوّري التطبيقات بتحديد عرض التركيز التلقائي في النافذة. لا تستخدِم هذه السمة و app:defaultFocus (راجِع المعلومات أدناه) في FocusArea نفسه.

تحدِّد FocusArea أيضًا بعض السمات لتخصيص التنقّل الدائري. لا يمكن تخصيص مناطق التركيز الضمنية باستخدام هذه السمات.

  1. (Android 11 QPR3 وAndroid 11 Car وAndroid 12)
    يمكن استخدام app:defaultFocus لتحديد رقم تعريف عرض فرعي قابل للتركيز، والذي يجب التركيز عليه عندما ينقل المستخدم مؤشر الماوس إلى هذا FocusArea.
  2. (Android 11 QPR3 وAndroid 11 Car وAndroid 12)
    يمكن ضبط app:defaultFocusOverridesHistory على true لجعل طريقة العرض المحدّدة أعلاه تستحوذ على التركيز حتى إذا كان هناك سجلّ يشير إلى أنّه تم التركيز على طريقة عرض أخرى في هذا FocusArea.
  3. (Android 12)
    استخدِم app:nudgeLeftShortcut وapp:nudgeRightShortcut app:nudgeUpShortcut وapp:nudgeDownShortcut لتحديد رقم تعريف عرض فرعي قابل للتركيز، والذي يجب التركيز عليه عندما يضغط المستخدم في اتجاه معيّن. لمزيد من المعلومات، يُرجى الاطّلاع على محتوى اختصارات الإشعارات أدناه.

    (Android 11 QPR3 وAndroid 11 Car تم إيقافها نهائيًا في Android 12) app:nudgeShortcut وapp:nudgeShortcutDirection يتيحان اختصارًا واحدًا فقط لإرسال إشعار تذكير.

  4. (Android 11 QPR3 وAndroid 11 Car وAndroid 12)
    لتفعيل ميزة التدوير في هذا FocusArea، يمكن ضبط app:wrapAround على true. يُستخدَم هذا الخيار عادةً عند ترتيب الملفات في دائرة أو شكل بيضاوي.
  5. (Android 11 QPR3 وAndroid 11 Car وAndroid 12)
    لضبط سمك الفاصل في اللقطة المميّزة في هذا FocusArea، استخدِم app:highlightPaddingStart، app:highlightPaddingEnd، app:highlightPaddingTop، app:highlightPaddingBottom، app:highlightPaddingHorizontal، وapp:highlightPaddingVertical.
  6. (Android 11 QPR3 وAndroid 11 Car وAndroid 12)
    لتعديل الحدود المرئية لهذا FocusArea للعثور على هدف إشعار بنقرة، استخدِم app:startBoundOffset وapp:endBoundOffset app:topBoundOffset وapp:bottomBoundOffset app:horizontalBoundOffset وapp:verticalBoundOffset.
  7. (Android 11 QPR3 وAndroid 11 Car وAndroid 12)
    لتحديد رقم تعريف FocusArea (أو المناطق) المجاور بشكل صريح في الاتجاهات المحدّدة، استخدِم app:nudgeLeft وapp:nudgeRight وapp:nudgeUp و app:nudgeDown. استخدِم هذا الخيار عندما لا يعثر البحث الهندسي المستخدَم تلقائيًا على الهدف المطلوب.

تؤدي النقرة عادةً إلى التنقّل بين مساحات التركيز. ولكن باستخدام اختصارات الإشعارات، يؤدي التمرير سريعًا أحيانًا إلى التنقّل أولاً ضمن FocusArea، ما قد يتطلّب من المستخدم التمرير سريعًا مرتين للانتقال إلى FocusArea التالي. تكون اختصارات الإشعارات الناعمة مفيدة عندما يحتوي رمز FocusArea على قائمة طويلة متبوعة بزرّ عمل عائم، كما هو موضّح في المثال أدناه:

اختصار &quot;إشعار لطيف&quot;
الشكل 3. اختصار "إرسال تنبيه"

بدون اختصار الإشعارات المنبثقة، على المستخدم التنقّل في القائمة بأكملها للوصول إلى العنصر النائب الكبير.

تخصيص تمييز التركيز

كما ذكرنا أعلاه، يستند RotaryService إلى مفهوم "تركيز العرض" الحالي في إطار عمل Android. عندما يدير المستخدم الشاشة ويدفعها، ينقل RotaryService التركيز حول الشاشة، ويشدّد على عرض معيّن ويزيل التركيز عن عرض آخر. في Android، عند التركيز على عرض، إذا كان العرض:

  • إذا حدّدت عنصر تمييز للتركيز، يرسم Android عنصر تمييز التركيز للعرض.
  • إذا لم يتم تحديد تمييز للتركيز ولم يتم إيقاف تمييز التركيز التلقائي، يرتسم تمييز التركيز التلقائي للعرض على نظام التشغيل Android.

لا تحدّد التطبيقات المصمّمة للاستخدام باللمس عادةً عناصر التركيز المناسبة.

يقدّم إطار عمل Android ميزة تمييز التركيز التلقائية، ويمكن للمصنع الأصلي للجهاز تجاوزها. يتلقّى مطوّرو التطبيقات هذا الإشعار عندما يكون المظهر الذي يستخدمونه مشتقًا من Theme.DeviceDefault.

لتوفير تجربة متّسقة للمستخدم، استخدِم ميزة تمييز التركيز التلقائية كلما أمكن. إذا كنت بحاجة إلى تمييز مخصّص الشكل (مثلاً، دائري أو مستطيل الشكل) للتركيز، أو إذا كنت تستخدم مظهرًا لا يستند إلى Theme.DeviceDefault، استخدِم موارد مكتبة car-ui-library لتحديد تمييز التركيز الخاص بك لكل عرض.

لتحديد تمييز مخصّص للتركيز على عرض معيّن، غيِّر العنصر المرسوم للخلفية أو المقدّمة للعرض إلى عنصر مرسوم مختلف عند التركيز على العرض. عادةً ما يتم تغيير الخلفية. إذا تم استخدام العنصر القابل للرسم التالي كخلفية لعرض مربّع، يؤدي ذلك إلى توليد تمييز دائري للتركيز:

<selector xmlns:android="http://schemas.android.com/apk/res/android">
   <item android:state_focused="true" android:state_pressed="true">
      <shape android:shape="oval">
         <solid android:color="@color/car_ui_rotary_focus_pressed_fill_color"/>
         <stroke
            android:width="@dimen/car_ui_rotary_focus_pressed_stroke_width"
            android:color="@color/car_ui_rotary_focus_pressed_stroke_color"/>
      </shape>
   </item>
   <item android:state_focused="true">
      <shape android:shape="oval">
         <solid android:color="@color/car_ui_rotary_focus_fill_color"/>
         <stroke
            android:width="@dimen/car_ui_rotary_focus_stroke_width"
            android:color="@color/car_ui_rotary_focus_stroke_color"/>
      </shape>
   </item>
   <item>
      <ripple...>
         ...
      </ripple>
   </item>
</selector>

(Android 11 QPR3 وAndroid 11 Car وAndroid 12) تشير مراجع الموارد الغامقة في المثال أعلاه إلى الموارد التي تحدّدها مكتبة car-ui-library. ويلغيها المصنّع الأصلي للجهاز لتكون متسقة مع ميزة تسليط الضوء على العنصر الذي يتم التركيز عليه تلقائيًا التي يحدّدها. ويضمن ذلك عدم تغيُّر لون تمييز التركيز وعرضه وما إلى ذلك عندما ينتقل المستخدم بين عرض يتضمّن تمييزًا مخصّصًا للتركيز وعرض يتضمّن تمييز التركيز التلقائي. العنصر الأخير هو تموج يُستخدَم لللمس. تظهر القيم التلقائية المستخدَمة للموارد المميّزة بخط غامق على النحو التالي:

القيم التلقائية للموارد المميّزة بخط عريض
الشكل 4. القيم التلقائية للموارد المميّزة بخط عريض

بالإضافة إلى ذلك، يتمّ استخدام تمييز مخصّص للتركيز عندما يتمّ منح زرّ لون خلفية جامد للفت انتباه المستخدِم إليه، كما هو موضّح في المثال أدناه. وقد يصعّب ذلك رؤية ميزة تمييز التركيز. في هذه الحالة، حدِّد تمييزًا مخصّصًا للتركيز باستخدام ألوان ثانوية:

لون خلفية خالص
  • (Android 11 QPR3 وAndroid 11 Car وAndroid 12)
    car_ui_rotary_focus_fill_secondary_color
    car_ui_rotary_focus_stroke_secondary_color
  • (Android 12)
    car_ui_rotary_focus_pressed_fill_secondary_color
    car_ui_rotary_focus_pressed_stroke_secondary_color

مثلاً:

مركّز، وليس مضغوطة تركيز عالٍ، ضغط
مُركّز عليه، وليس مُضغوطًا مُركّز عليه، تم الضغط عليه

التنقّل بالتناوب

إذا كان تطبيقك يستخدم RecyclerView، عليك استخدام CarUiRecyclerView بدلاً من ذلك. يضمن ذلك اتساق واجهة المستخدم مع الآخرين لأنّ تخصيص المصنّع الأصلي للجهاز ينطبق على جميع CarUiRecyclerView.

إذا كانت جميع العناصر في قائمتك قابلة للتركيز، لن تحتاج إلى اتّخاذ أي إجراء آخر. ينقل التنقّل الدوّري التركيز بين العناصر في القائمة، ويتم الانتقال في القائمة لإظهار العنصر الذي تم التركيز عليه حديثًا.

(Android 11 QPR3 وAndroid 11 Car وAndroid 12)
إذا كان هناك مزيج من العناصر التي يمكن التركيز عليها والعناصر التي لا يمكن التركيز عليها، أو إذا كانت جميع العناصر لا يمكن التركيز عليها، يمكنك تفعيل التمرير الدوّري، ما يسمح للمستخدم باستخدام وحدة التحكّم الدوّرية للتمرير تدريجيًا خلال القائمة بدون تخطّي العناصر التي لا يمكن التركيز عليها. لتفعيل ميزة التمرير الدوّري، اضبط السمة app:rotaryScrollEnabled على true.

(Android 11 QPR3 وAndroid 11 Car وAndroid 12)
يمكنك تفعيل ميزة "الانتقال الدائري" في أي عرض قابل للانتقال، بما في ذلك avCarUiRecyclerView، باستخدام setRotaryScrollEnabled() فيCarUiUtils. في هذه الحالة، عليك إجراء ما يلي:

  • أن يكون بالإمكان التركيز على العرض القابل للتمرير حتى يمكن التركيز عليه عندما لا تكون أي من العروض الفرعية القابلة للتركيز مرئية
  • أوقِف ميزة إبراز التركيز التلقائي في العرض القابل للتقديم أو الإيقاف من خلال استدعاء setDefaultFocusHighlightEnabled(false) لكي لا يبدو أنّه تم التركيز على العرض القابل للتقديم أو الإيقاف.
  • تأكَّد من التركيز على العرض القابل للتنقّل قبل العناصر الفرعية من خلال استدعاء setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS).
  • انتبِه إلى أحداث MotionEvents التي تحتوي على SOURCE_ROTARY_ENCODER وأحد الخيارَين التاليَين: AXIS_VSCROLL أو AXIS_HSCROLL للإشارة إلى المسافة التي يجب التمرير خلالها و اتجاه التمرير (من خلال العلامة).

عند تفعيل الانتقال الدائري للأعلى أو للأسفل على CarUiRecyclerView وإدارة المستخدم إلى منطقة لا تتوفّر فيها طرق عرض يمكن التركيز عليها، يتغيّر شريط التمرير من الرمادي إلى الأزرق، كما لو كان يشير إلى أنّه تم التركيز على شريط التمرير. يمكنك تنفيذ تأثير مشابه إذا أردت.

أحداث MotionEvents هي نفسها التي يتم إنشاؤها بواسطة عجلة التمرير على الماوس، باستثناء المصدر.

وضع التلاعب المباشر

في العادة، تؤدي النقرات الخفيفة والدوران إلى التنقّل في واجهة المستخدم، في حين تؤدي الضغطات على الزر "وسط" إلى اتّخاذ إجراء، ولكن هذا ليس صحيحًا دائمًا. على سبيل المثال، إذا أراد المستخدم تعديل مستوى صوت المنبّه، يمكنه استخدام وحدة التحكّم الدوّارة للانتقال إلى شريط تمرير مستوى الصوت، والضغط على زر المركز، وتدوير وحدة التحكّم لضبط مستوى صوت المنبّه، ثم الضغط على زر الرجوع للعودة إلى التنقّل. ويُشار إلى هذا الوضع باسم الاستهداف المباشر (DM). في هذا الوضع، يتم استخدام وحدة التحكّم الدوّارة للتفاعل مع العرض مباشرةً بدلاً من التنقّل.

يمكنك تنفيذ ميزة "الرسائل المباشرة" بطريقتَين: إذا كنت بحاجة فقط إلى التعامل مع عملية الدوران وكان العرض الذي تريد التلاعب به يستجيب لACTION_SCROLL_FORWARD و ACTION_SCROLL_BACKWARD AccessibilityEvent بشكلٍ مناسب، استخدِم الآلية البسيطة. وبخلاف ذلك، استخدِم الآلية المتقدّمة.

إنّ الآلية البسيطة هي الخيار الوحيد في نوافذ النظام، ويمكن للتطبيقات استخدام أيّ من الآليتين.

آلية بسيطة

(Android 11 QPR3 وAndroid 11 Car وAndroid 12)
يجب أن يستدعي تطبيقك DirectManipulationHelper.setSupportsRotateDirectly(View view, boolean enable). يرصد RotaryService حالات استخدام المستخدم لوضع "عرض مزدوج" ويدخل هذا الوضع عندما يضغط المستخدم على زر "الوسط" أثناء التركيز على عرض. في وضع "العرض على شاشة مزدوجة"، يتم تنفيذ عمليات التدوير ACTION_SCROLL_FORWARD أو ACTION_SCROLL_BACKWARD والخروج من وضع "العرض على شاشة مزدوجة" عندما يضغط المستخدم على زر الرجوع. تعمل الآلية البسيطة على تبديل الحالة المحدّدة للعرض عند الدخول إلى وضع المحادثة المباشرة والخروج منه.

لتقديم إشارة مرئية بأنّ المستخدم في وضع المحادثة المباشرة، اجعل طريقة العرض تبدو مختلفة عند اختيارها. على سبيل المثال، يمكنك تغيير الخلفية عندما يكون android:state_selected يساوي true.

آلية متقدّمة

يحدِّد التطبيق حالات دخول RotaryService إلى وضع "الرسائل المباشرة" والخروج منه. لتوفير تجربة مستخدم متّسقة، يجب أن يؤدي الضغط على الزر "وسط" مع التركيز على عرض المحادثة المباشرة إلى الدخول في وضع المحادثة المباشرة، ويجب أن يؤدي الزر "رجوع" إلى الخروج من وضع المحادثة المباشرة. إذا لم يتم استخدام زر "المركز" و/أو الإشعارات، يمكن أن تكونا طرقًا بديلة للخروج من وضع المحادثة المباشرة. في التطبيقات، مثل "خرائط Google"، يمكن استخدام زر يمثّل الرسائل المباشرة للدخول إلى وضع الرسائل المباشرة.

لتتوافق مع وضع "إدارة العملاء" المتقدّم، يجب أن تستوفي طريقة العرض الشروط التالية:

  1. (Android 11 QPR3 وAndroid 11 Car وAndroid 12) يجب أن يستمع الجهاز إلى حدث KEYCODE_DPAD_CENTER للدخول إلى وضع "وضع القيادة" والاستماع إلى حدث KEYCODE_BACK للخروج من وضع "وضع القيادة"، مع استدعاء DirectManipulationHelper.enableDirectManipulationMode() في كلتا الحالتَين. للانتباه إلى هذه الأحداث، نفِّذ أحد الإجراءات التالية:
    • سجِّل OnKeyListener.
    • أو
    • وسِّع العرض ثم استبدِل طريقة dispatchKeyEvent().
  2. يجب الاستماع إلى أحداث التذكيرات التلقائية (KEYCODE_DPAD_UP أو KEYCODE_DPAD_DOWN أو KEYCODE_DPAD_LEFT أو KEYCODE_DPAD_RIGHT) إذا كان من المفترض أن تتم إدارة التذكيرات التلقائية في طريقة العرض.
  3. يجب الاستماع إلى MotionEvent والحصول على عدد مرات التناوب في AXIS_SCROLL إذا كان العرض يريد التعامل مع التناوب. هناك عدة طرق لإجراء ذلك:
    1. سجِّل OnGenericMotionListener.
    2. وسِّع العرض وألغِ طريقة dispatchTouchEvent().
  4. لتجنُّب البقاء في وضع "العرض المباشر"، يجب الخروج من هذا الوضع عندما يكون المقتطف أو النشاط الذي ينتمي إليه العرض غير تفاعلي.
  5. يجب أن يوفّر إشارة مرئية للإشارة إلى أنّ العرض في وضع المحادثة المباشرة.

في ما يلي عيّنة من طريقة عرض مخصّصة تستخدِم وضع "العرض على الشاشة" لتمرير خريطة وتحريكها وتكبيرها أو تصغيرها:

/** Whether this view is in DM mode. */
private boolean mInDirectManipulationMode;

/** Initializes the view. Called by the constructors. */ private void init() { setOnKeyListener((view, keyCode, keyEvent) -> { boolean isActionUp = keyEvent.getAction() == KeyEvent.ACTION_UP; switch (keyCode) { // Always consume KEYCODE_DPAD_CENTER and KEYCODE_BACK events. case KeyEvent.KEYCODE_DPAD_CENTER: if (!mInDirectManipulationMode && isActionUp) { mInDirectManipulationMode = true; DirectManipulationHelper.enableDirectManipulationMode(this, true); setSelected(true); // visually indicate DM mode } return true; case KeyEvent.KEYCODE_BACK: if (mInDirectManipulationMode && isActionUp) { mInDirectManipulationMode = false; DirectManipulationHelper.enableDirectManipulationMode(this, false); setSelected(false); } return true; // Consume controller nudge events only when in DM mode. // When in DM mode, nudges pan the map. case KeyEvent.KEYCODE_DPAD_UP: if (!mInDirectManipulationMode) return false; if (isActionUp) pan(0f, -10f); return true; case KeyEvent.KEYCODE_DPAD_DOWN: if (!mInDirectManipulationMode) return false; if (isActionUp) pan(0f, 10f); return true; case KeyEvent.KEYCODE_DPAD_LEFT: if (!mInDirectManipulationMode) return false; if (isActionUp) pan(-10f, 0f); return true; case KeyEvent.KEYCODE_DPAD_RIGHT: if (!mInDirectManipulationMode) return false; if (isActionUp) pan(10f, 0f); return true; // Don't consume other key events. default: return false; } });
// When in DM mode, rotation zooms the map. setOnGenericMotionListener(((view, motionEvent) -> { if (!mInDirectManipulationMode) return false; float scroll = motionEvent.getAxisValue(MotionEvent.AXIS_SCROLL); zoom(10 * scroll); return true; })); }
@Override public void onPause() { if (mInDirectManipulationMode) { // To ensure that the user doesn't get stuck in DM mode, disable DM mode // when the fragment is not interactive (e.g., a dialog shows up). mInDirectManipulationMode = false; DirectManipulationHelper.enableDirectManipulationMode(this, false); } super.onPause(); }

يمكن العثور على المزيد من الأمثلة في مشروع RotaryPlayground.

ActivityView

عند استخدام ActivityView:

  • يجب ألا يكون بالإمكان التركيز على ActivityView.
  • (Android 11 QPR3 وAndroid 11 Car، سيتم إيقافها نهائيًا في Android 11)
    يجب أن تحتوي محتويات ActivityView على FocusParkingView كأول عرض يمكن التركيز عليه، ويجب أن تكون سمة app:shouldRestoreFocus false.
  • يجب ألا يكون لمحتوى ActivityView سوى android:focusByDefault مشاهدة.

بالنسبة إلى المستخدم، من المفترض ألا يكون لـ ActivityViews أي تأثير في التنقّل، باستثناء أنّه لا يمكن أن تمتد مناطق التركيز على ActivityViews. بعبارة أخرى، لا يمكنك استخدام منطقة تركيز واحدة تتضمّن محتوى داخل و خارج ActivityView. إذا لم تُضِف أي FocusAreas إلى ActivityView، يُعتبر جذر التسلسل الهرمي للعرض في ActivityView منطقة تركيز ضمنية.

الأزرار التي تعمل عند الضغط عليها باستمرار

تؤدي معظم الأزرار إلى تنفيذ بعض الإجراءات عند النقر عليها. تعمل بعض الأزرار عند الضغط عليها مع الاستمرار بدلاً من ذلك. على سبيل المثال، يعمل زرّا "تقديم سريع" و"الترجيع السريع" عادةً عند الضغط عليهما مع الاستمرار. لجعل هذه buttons تتيح استخدام الشاشة الدوّارة، انتظِر ظهور KEYCODE_DPAD_CENTER KeyEvents على النحو التالي:

mButton.setOnKeyListener((v, keyCode, event) ->
{
    if (keyCode != KEYCODE_DPAD_CENTER) {
        return false;
    }
    if (event.getAction() == ACTION_DOWN) {
        mButton.setPressed(true);
        mHandler.post(mRunnable);
    } else {
        mButton.setPressed(false);
        mHandler.removeCallbacks(mRunnable);
    }
    return true;
});

حيث يتّخذ mRunnable إجراءً (مثل ترجيع الفيديو) ويحدّد موعدًا لتنفيذه بعد تأخير.

وضع اللمس

يمكن للمستخدمين استخدام وحدة تحكّم دوّارة للتفاعل مع وحدة التحكم الرئيسية في السيارة بطريقتَين، إما باستخدام وحدة التحكّم الدوّارة أو من خلال لمس الشاشة. عند استخدام وحدة التحكّم الدوّارة، يتم تمييز أحد طرق العرض التي يمكن التركيز عليها. عند لمس الشاشة، لا يظهر تمييز للتركيز. يمكن للمستخدم التبديل بين أوضاع الإدخال هذه في أي وقت:

  • مفتاح دوار → لمس عندما يلمس المستخدم الشاشة، يختفي تمييز التركيز.
  • انقر على رمز الدوار. عندما يضغط المستخدم على زر "التوسيع/التصغير" أو يضغط عليه مع الاستمرار أو يضغط عليه مع التدوير، يظهر تمييز التركيز.

لا يؤثر زرا الرجوع والصفحة الرئيسية في وضع الإدخال.

يستند تطبيق Rotary إلى مفهوم وضع اللمس الحالي في Android. يمكنك استخدام View.isInTouchMode() لتحديد وضع الإدخال الذي يستخدمه المستخدم. يمكنك استخدام OnTouchModeChangeListener للاستماع إلى التغييرات. على الرغم من أنّه يمكن استخدام هذه الميزة لتخصيص واجهة المستخدم لحال وضع الإدخال الحالي، تجنَّب إجراء أي تغييرات كبيرة لأنّها قد تكون مربكة.

تحديد المشاكل وحلّها

في التطبيق المصمّم للاستخدام باللمس، من الشائع أن تتضمّن طرق عرض متداخلة يمكن التركيز عليها. على سبيل المثال، قد يكون هناك FrameLayout حول ImageButton، وكلاهما يمكن التركيز عليه. لا يضرّ ذلك بالشاشة التي تعمل باللمس، ولكن يمكن أن يؤدي إلى تقديم تجربة استخدام رديئة للشاشة الدوّارة لأنّ المستخدم يجب أن يدير جهاز التحكّم مرتين للانتقال إلى طريقة العرض التفاعلية التالية. لتوفير تجربة جيدة للمستخدم، تنصح Google بجعل إما العرض الخارجي أو العرض الداخلي قابلَين للتركيز، ولكن ليس كليهما.

إذا فقد زر أو مفتاح التركيز عند الضغط عليه من خلال وحدة التحكّم الدوّارة، قد ينطبق أحد الشروط التالية:

  • يتم إيقاف الزر أو المفتاح (لفترة قصيرة أو إلى أجل غير مسمى) بسبب الضغط على الزر. في كلتا الحالتَين، هناك طريقتان لحلّ هذه المشكلة:
    • اترك حالة android:enabled كما هي true واستخدِم حالة مخصّصة لإخفاء الزر أو مفتاح التبديل باللون الرمادي كما هو موضّح في الحالة المخصّصة.
    • استخدِم حاوية لإحاطة الزر أو المفتاح وجعل الحاوية قابلة للتركيز بدلاً من الزر أو المفتاح. (يجب أن يكون مستمع النقرات في الحاوية).
  • يتم استبدال الزر أو مفتاح التبديل. على سبيل المثال، قد يؤدي الإجراء الذي يتم اتّخاذه عند الضغط على الزر أو تبديل المفتاح إلى إعادة تحميل الإجراءات المتاحة ما يؤدي إلى استبدال الأزرار الجديدة بالزرّات الحالية. هناك طريقتان لحلّ هذه المشكلة:
    • بدلاً من إنشاء زر أو مفتاح تبديل جديدَين، اضبط الرمز و/أو النص للزر أو مفتاح التبديل الحاليَين.
    • كما هو موضّح أعلاه، أضِف حاوية يمكن التركيز عليها حول الزر أو مفتاح التبديل.

RotaryPlayground

RotaryPlayground هو تطبيق مرجعي للأجهزة الدوّارة. يمكنك الاطّلاع عليه للتعرّف على كيفية دمج ميزات الالتفاف في تطبيقاتك. يتم تضمين RotaryPlayground في إصدارات المحاكي وفي إصدارات الأجهزة التي تعمل بنظام التشغيل Android Automotive (AAOS).

  • RotaryPlayground المستودع: packages/apps/Car/tests/RotaryPlayground/
  • الإصدارات: Android 11 QPR3 وAndroid 11 Car وAndroid 12

يعرض تطبيق RotaryPlayground علامات التبويب التالية على اليمين:

  • البطاقات: اختبِر التنقّل في مناطق التركيز، وتخطّي العناصر التي لا يمكن التركيز عليها وإدخال النصوص.
  • التفاعل المباشر: اختبِر التطبيقات المصغّرة التي تتيح وضع المعالجة المباشرة البسيط والمتقدّم. هذه علامة التبويب مخصّصة للتعديل المباشر داخل نافذة التطبيق.
  • التلاعب بواجهة مستخدم النظام اختبِر التطبيقات المصغّرة التي تتيح التفاعل المباشر في نوافذ النظام التي تتيح وضع التفاعل المباشر البسيط فقط.
  • شبكة: اختبِر التنقّل الدوار باتجاه z مع الانتقال للأعلى أو للأسفل.
  • الإشعار: اختبِر ميزة "الدفع" للدخول إلى الإشعارات المنبثقة والخروج منها.
  • الانتقال للأسفل أو للأعلى: اختبِر الانتقال للأسفل أو للأعلى من خلال مزيج من المحتوى الذي يمكن التركيز عليه والمحتوى الذي لا يمكن التركيز عليه.
  • WebView: اختبِر الانتقال من خلال الروابط في WebView.
  • مخصّص FocusArea: اختبار تخصيص FocusArea:
    • شاشة محيطية
    • android:focusedByDefault وapp:defaultFocus
    • .
    • استهدافات الإشعارات التلقائية الواضحة
    • اختصارات "إرسال إشعار تنبيه"
    • FocusArea بدون ملفّات شخصية يمكن التركيز عليها