إيماءات بيد واحدة باستخدام ميزة "الكتابة السحرية"


في الإصدار 7 من Wear OS (مستوى واجهة برمجة التطبيقات 37) والإصدارات الأحدث، يتيح إطار عمل الإيماءات بيد واحدة، بالإضافة إلى واجهة برمجة تطبيقات تشكّل جزءًا من Compose for Wear OS، للمستخدمين التفاعل مع تطبيقك بدون لمس الشاشة.

مع أنّ إطار العمل هذا كان متوافقًا في البداية مع أجهزة Pixel Watch (ساعة Pixel Watch 3 والإصدارات الأحدث)، إلا أنّه متاح لجميع المصنّعين الأصليين للأجهزة. ومن خلال استخدام واجهة برمجة التطبيقات هذه، يمكن توسيع نطاق توافق تطبيقك مع الإيماءات تلقائيًا ليشمل المنظومة المتكاملة بأكملها مع توسّع نطاق توافق الأجهزة.

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

الإيماءات والإجراءات المتوافقة

يتوافق إطار عمل الإيماءات في Wear OS مع نوعَين من الإيماءات:

  • الإجراء الأساسي (الضغط بإصبعين): يتم ربطه بالإجراء الرئيسي على الشاشة، مثل الرد على مكالمة أو تبديل تشغيل الوسائط.
  • إجراء الإغلاق (تحريك المعصم): يؤدي إلى الرجوع إلى الصفحة السابقة أو إغلاق مربع حوار أو إلغاء طلب.

ضبط الإيماءات في Compose

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

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

تتتبّع الدالة rememberOneHandedGestureConfiguration سجلّ تفاعلات المستخدمين في عمليات إعادة الإنشاء بدون الكشف عن حالة التطبيق. بعد أن ينشئ تطبيقك الإعدادات، يجب أن يمرّرها إلى Modifier.oneHandedGesture في الدالة المركّبة التفاعلية.

لمساعدة المستخدمين في اكتشاف الإيماءات المتاحة، توفّر المكتبة الطريقة OneHandedGestureClickIndicator. يعمل هذا الإجراء كغلاف يستبدل المحتوى الأساسي للإشارة إلى المستخدم بأنّ إجراء إيماءة متاح.

المكوّنات التفاعلية

لتفعيل الإيماءات على عنصر تحكّم تفاعلي، مثل زر، أنشئ إعدادًا يحدّد OneHandedGestureAction.Primary وطبِّق المعدِّل oneHandedGesture. مرِّر MutableInteractionSource نفسه إلى كلّ من عنصر التحكّم والمعدِّل، كي تُصدر أحداث الإيماءات ملاحظات مرئية عند الضغط على عنصر التحكّم.

لتفعيل مؤشر الإيماءة، أنشئ مثيلاً من OneHandedGestureClickIndicatorState وتذكَّره. بعد ذلك، لتفعيل الملاحظات المرئية، استدعِ الدالة showIndicator ضِمن دالة رد الاتصال onGestureAvailable التي يوفّرها المعدِّل oneHandedGesture، ما يشير إلى النظام بحدوث حدث مؤشر. بعد استدعاء المكوّن، يستبدل المكوّن محتواه العادي لفترة وجيزة بصورة متحركة للإيماءة.

var isPlaying by remember { mutableStateOf(false) }
val onClick = { isPlaying = !isPlaying }

val gestureConfig = rememberOneHandedGestureConfiguration(
    action = OneHandedGestureAction.Primary
)
val indicatorState = remember { OneHandedGestureClickIndicatorState() }
val coroutineScope = rememberCoroutineScope()
val interactionSource = remember { MutableInteractionSource() }

Button(
    onClick = onClick,
    interactionSource = interactionSource,
    modifier = Modifier
        .fillMaxWidth()
        .oneHandedGesture(
            gestureConfiguration = gestureConfig,
            interactionSource = interactionSource,
            onGestureLabel = if (isPlaying) "pause" else "play",
            onGestureAvailable = { coroutineScope.launch { indicatorState.showIndicator() } },
            onGesture = onClick
        )
) {
    OneHandedGestureClickIndicator(
        gestureConfiguration = gestureConfig,
        state = indicatorState
    ) {
        Text(if (isPlaying) "Pause" else "Play", modifier = Modifier.fillMaxWidth())
    }
}

الحاويات القابلة للتمرير

بالنسبة إلى الشاشات أو القوائم القابلة للتمرير، أنشئ إعدادًا يحدّد OneHandedGestureAction.Primary وطبِّق المعدِّل oneHandedGesture على الحاوية، مع استدعاء أداة مساعدة للتمرير مثل scrollDown.

لتقديم ملاحظات مرئية بشأن إجراءات التمرير، يمكنك استخدام OneHandedGestureScrollIndicator. يعمل هذا المكوّن كمؤشر تمرير عادي يعرض موضع التمرير، ولكن يمكنه أيضًا الإشارة إلى أنّ إيماءة التمرير متاحة للمستخدم. يتم عادةً تمرير هذا المؤشر إلى موضع الإعلان scrollIndicator في ScreenScaffold، كما يتم ربطه بحالة حاوية قابلة للتمرير، مثل TransformingLazyColumn. ويحتوي أيضًا على OneHandedGestureScrollIndicatorState لإدارة انتقالاته المرئية.

لتفعيل الملاحظات المرئية، استدعِ الدالة showIndicator في هذه الحالة، وعادةً ما يكون ذلك داخل الدالة onGestureAvailable التي يتم استدعاؤها في المعدِّل oneHandedGesture. وبعد تفعيل المؤشر، سيحلّ محل حالته المرئية العادية بشكل مؤقت سلسلة من الرسوم المتحركة للإيماءات لتنبيه المستخدم.

val scrollState = rememberTransformingLazyColumnState()
val gestureConfig = rememberOneHandedGestureConfiguration(
    action = OneHandedGestureAction.Primary,
    priority = OneHandedGesturePriority.Scrollable
)
val indicatorState = remember(gestureConfig) { OneHandedGestureScrollIndicatorState() }
val coroutineScope = rememberCoroutineScope()

ScreenScaffold(
    scrollState = scrollState,
    scrollIndicator = {
        OneHandedGestureScrollIndicator(
            gestureConfiguration = gestureConfig,
            indicatorState = indicatorState,
            scrollState = scrollState,
            modifier = Modifier.align(Alignment.CenterEnd)
        )
    }
) { contentPadding ->
    TransformingLazyColumn(
        state = scrollState,
        contentPadding = contentPadding,
        modifier = Modifier
            .fillMaxSize()
            .oneHandedGesture(
                gestureConfiguration = gestureConfig,
                onGestureLabel = "scroll",
                onGestureAvailable = {
                    coroutineScope.launch { indicatorState.showIndicator() }
                },
                onGesture = { OneHandedGestureDefaults.scrollDown(scrollState) }
            )
    ) {
        items(10) { index ->
            Text("Item $index", modifier = Modifier.padding(8.dp))
        }
    }
}

الجمع بين إيماءات متعددة

يمكنك ضبط إيماءة التمرير وإيماءة النقر باستخدام الإجراء الأساسي نفسه من خلال إضافة gesturePriority إلى عنصر OneHandedGestureConfiguration:

  • OneHandedGesturePriority.Clickable (الأعلى): يجب تعيين هذا المستوى لعناصر التحكّم التفاعلية، مثل تلك التي تحمل النوع Button أو Card، لكي تتمكّن من رصد الإيماءات عندما تكون مرئية على الشاشة.
  • OneHandedGesturePriority.Scrollable (متوسط): يجب تعيين هذا الخيار للحاويات القابلة للتمرير أو التقسيم إلى صفحات، وذلك لكي يتم التمرير عند عدم ظهور أي عنصر تحكّم قابل للنقر.
  • OneHandedGesturePriority.Unspecified (الأدنى): أولوية غير محدَّدة. هذه هي القيمة التلقائية لإيماءة لم يتم ضبط priority لها.

من خلال ضبط priority = OneHandedGesturePriority.Clickable بشكلٍ صريح على زر داخلي وpriority = OneHandedGesturePriority.Scrollable على القائمة الرئيسية، يمكن للنظام عرض هذا السلوك ذي الأولوية للإيماءات. عندما ينفّذ المستخدم الإجراء الأساسي باستخدام الإيماءة بيد واحدة، يتم أولاً تمرير القائمة للأسفل إلى أن يظهر الزر، ثم يتم تسجيل إجراء النقر على الزر.

اختبار الإيماءات وتصحيح أخطائها باستخدام ADB

يمكنك اختبار الإيماءات بيد واحدة على جهاز فعلي أو محاكي بدون تحريك معصمك باستخدام أداة Android Debug Bridge (adb) وخدمة تابعة لنظام التشغيل IWearGestureService.

تفعيل محاكاة الإيماءات

قبل محاكاة الإيماءات باستخدام ADB، عليك ضبط إعدادات جهازك وتجاوز القيود على النحو التالي:

  1. تأكَّد من أنّ جهاز Wear OS يعمل بالإصدار Wear OS 7 (المستوى 37 من واجهة برمجة التطبيقات) أو إصدار أحدث:

    adb shell getprop ro.build.version.sdk
    
  2. إذا كنت تختبر على جهاز فعلي غير موضوع على معصمك أو موضوع على شاحن، عليك تجاهل شرط عدم الارتداء لكي يظل إطار عمل الإيماءات نشطًا:

    adb shell cmd IWearGestureService override-constraints offbody-state
    

تشغيل أحداث الإيماءات باستخدام ADB

لمحاكاة إيماءة الضمّ بإصبعين معًا مرّتين (وهي إجراء Primary على ساعات Pixel)، نفِّذ أمر ADB shell التالي:

adb shell cmd IWearGestureService gesture DoublePinch

لمحاكاة إيماءة تحريك المعصم (وهي الإجراء Dismiss على ساعات Pixel)، نفِّذ أمر ADB shell التالي:

adb shell cmd IWearGestureService gesture WristTurn

إعادة ضبط تتبُّع تلميحات الإيماءات

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

  • على إصدارات userdebug أو المحاكيات:

    adb shell cmd IWearGestureService hint clear <your_package_name>
    
  • في إصدارات البيع بالتجزئة (user):

    على الأجهزة التجارية التي لا يمكن الوصول إلى جذرها، يتم حظر hint clear من خلال أذونات النظام. محو البيانات المحلية للتطبيق لإعادة ضبط ميزة "اقتراحات البحث":

    adb shell pm clear <your_package_name>
    

استعادة القيود التلقائية

لإعادة ضبط جميع عمليات إلغاء قيود تصحيح الأخطاء عند الانتهاء من الاختبار، اتّبِع الخطوات التالية:

adb shell cmd IWearGestureService override-constraints reset

تحديد المشاكل وحلّها في إدخال الإيماءات

إذا لم يتلقَّ تطبيقك إيماءات محاكاة:

  1. تأكَّد من أنّ شاشة الساعة منشَّطة وفي وضع التشغيل. لا يرسل إطار عمل الإيماءات الإيماءات إلى التطبيقات عندما تكون الشاشة مطفأة أو في وضع العرض المحيط. لتنشيط الشاشة باستخدام ADB، نفِّذ الأمر التالي:

    adb shell input keyevent KEYCODE_WAKEUP
    
  2. تحقَّق ممّا إذا كان تطبيقك مسجّلاً كمشترك نشط في الإيماءات ويحتفظ حاليًا بتركيز النافذة:

    adb shell cmd IWearGestureService get-active-gestures -readable
    

    عندما تكون شاشة الإيماءات في تطبيقك في المقدّمة والشاشة نشطة، يعرض هذا الأمر [DoublePinch] أو [DoublePinch, WristTurn]. إذا تم عرض قائمة فارغة ([])، تحقَّق مما إذا كانت النافذة في المقدّمة أو إذا كانت القيود خارج النص تمنع التفعيل.

  3. فحص حالة خدمة الإيماءات الداخلية ورموز المشتركين النشطين:

    adb shell dumpsys IWearGestureService
    

مراجع إضافية

للحصول على إرشادات بشأن التصميم حول متى وأين يجب استخدام الإيماءات بيد واحدة، اطّلِع على الإيماءات بيد واحدة.