أساسيات التطبيقات

توسيع نطاق تفاعلات الإدخال خارج الشاشة التي تعمل باللمس باستخدام Jetpack Compose

سماعات

  • أليكس فانيو

    مهندس برمجيات

  • جيريمي ووكر

    مهندس برمجيات في Compose

لمحة

لمحة عن المحاضرة

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

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

ملخّص

  • يعالج Jetpack Compose أحداث المؤشر الواردة في Android من خلال مسار مكوّن من ست مراحل. يحافظ هذا المسار على شجرة مسار النقر ويرسل الأحداث في ثلاث تمريرات عبر عُقد واجهة المستخدم.
  • أصبحت إدخالات لوحة اللمس في Compose الآن مرتبطة بأحداث الماوس بدلاً من اللمس. تحوّل Compose أيضًا أحداث الإصبع الزائفة في المنصة إلى أحداث مخصّصة لمؤشر التحريك والتكبير/التصغير.
  • توفّر لوحات اللمس في النظارات الذكية أنواعًا من الإدخال غير المباشر بدلاً من لمس إحداثيات الشاشة. وتستخدم محور حركة اتجاهي أساسي لربط التمريرات السريعة الأفقية بالتنقّل العمودي التالي أو السابق.
  • تم توسيع نطاق دعم الاختبار ليشمل طرقًا جديدة غير اللمس، مثل performTrackpadInput. توفّر واجهات برمجة التطبيقات هذه مساحات اختبار مألوفة للتحقّق من أنّ التطبيقات تتعامل مع الأجهزة التي لا تعمل باللمس بشكل صحيح.

Gemini Notebook

المواضيع

  • jetpack-compose
  • pointer-input
  • لوحات لمس
  • الإيماءات
  • intelligent-eyewear
  • تسهيل الاستخدام

النص

تنزيل النص

مقدمة وفلسفة الإدخال

  • مرحبًا بالجميع!

  • نأمل أن تكون قد قضيت وقتًا ممتعًا في مؤتمر Android Developer Summit، ونشكرك على حضور جلستنا.

  • أنا "أليكس فانيو"، مهندس برمجيات في فريق Compose، وينضم إليّ "جيريمي"، وهو أيضًا مهندس برمجيات في فريق Compose.

  • لقد عملنا على تحسين نظام الإدخال في Jetpack Compose، واليوم، سنتحدث عن كيفية توسيع نطاقه ليتجاوز الشاشة التي تعمل باللمس لتسهيل دعم الإدخال غير اللمسي بشكل تلقائي.

  • بشكل عام، يشمل الإدخال أي طريقة يتفاعل بها المستخدم مع تطبيقك.

  • قد تكون الشاشات التي تعمل باللمس هي أول ما تفكر فيه لتطبيق جوّال، ولكنها بالتأكيد ليست النوع الوحيد من الإدخال.

  • وتشمل الأنواع الأخرى من أجهزة الإدخال لوحات المفاتيح الخارجية وأجهزة الماوس ولوحات اللمس وأقلام الشاشة وأجهزة التحكّم والأجهزة الدوّارة.

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

  • وإذا أردت استخدام أساليب أكثر عمومية، هناك أنواع أخرى من الإدخال أيضًا، مثل محرّرات أسلوب الإدخال (IME) البرمجية لتعديل النصوص والميكروفونات والكاميرات وأدوات الاستشعار الأخرى في الجهاز.

  • يجب أن تتوقّع استخدام تطبيقاتك مع مجموعة متنوّعة من أنواع الإدخال.

  • أحد أسباب ذلك هو التنوع المتزايد باستمرار للأجهزة التي تعمل عليها التطبيقات، وزيادة قدرة هذه الأجهزة على التكيف.

  • قد يكون لأشكال الأجهزة طريقة إدخال أكثر شيوعًا، ولكن من المهم عدم افتراض أنّ شكل جهاز معيّن يعني استخدام نوع إدخال معيّن.

  • على الرغم من أنّ جهاز Chromebook يحتوي على لوحة لمس ولوحة مفاتيح مدمجتَين، لا يجعل ذلك أنواع الإدخال هذه فريدة من نوعها لهذا الشكل.

  • يجب أن تتوقع استخدام لوحات المفاتيح الخارجية ولوحات اللمس وأجهزة الماوس على الهواتف والأجهزة اللوحية وأجهزة التلفزيون أيضًا، بل إنّ بعض أجهزة التلفزيون تحتوي على شاشات تعمل باللمس.

  • السبب الثاني الكبير لتوقع ودعم أنواع الإدخال المتعددة هو تسهيل الاستخدام.

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

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

  • باختصار، يجب أن تتيح استخدام أنواع مختلفة من الإدخال بغض النظر عن شكل الجهاز.

  • لنتعرّف على كيفية مساعدة Jetpack Compose لتطبيقاتك في تلبية احتياجات المستخدمين هذه.

  • تتمثل فلسفة Compose بشكل عام في أنّ أنواع الإدخال المختلفة هذه يجب أن تعمل بشكل مناسب مع المكوّنات العادية بدون الحاجة إلى اتخاذ أي إجراء خاص.

  • بعد ذلك، يجب أن تتوفّر لديك الأدوات التي تحتاج إليها لتخصيص السلوك وتعديله حسب ما تراه مناسبًا.

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

  • سنلقي نظرة على مثالَين من هذه الأمثلة في هذه المحاضرة، وكلاهما من الإضافات الحديثة إلى Compose.

  • من قبيل الصدفة، كلاهما جهازان يشبهان لوحة اللمس، ولكن كما سترى، يتم استخدامهما بطرق مختلفة للغاية.

  • لنبدأ ببعض الأعمال الحديثة على لوحات اللمس في أجهزة الكمبيوتر المحمول.

  • لوحة اللمس هي جهاز إدخال يعتمد على المؤشر.

  • وبشكل أكثر تحديدًا، عند استخدام لوحة اللمس، تحصل على إحداثيات ثنائية الأبعاد.

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

  • بدلاً من ذلك، بالنسبة إلى لوحة اللمس التي تشبه لوحة الكمبيوتر المحمول، هناك ترجمة للتحكّم في المؤشر.

  • في هذه الترجمة، يمكنك توقع مجموعة من تحويلات النظام الأساسي المعقدة: أشياء مثل تسريع المؤشر ورفض راحة اليد.

  • لذا، ما تحصل عليه في جانب Compose هو مؤشر يتوافق مع موقع المؤشر الذي يعمل عادةً كإدخال بالماوس.

  • من نظام Android الأساسي، نحصل على معلومات التمرير والنقرات، بما في ذلك النقرات الثانوية أو الثلاثية، ويتم تجميع كل هذه المعلومات في MotionEvents.

  • لفهم كيفية تحسين السلوك في أحدث إصدارات Compose، لنلقِ أولاً نظرة عامة على كيفية تعامل Compose مع أحداث المؤشر هذه عن كثب.

المراحل الست لمعالجة إدخال المؤشر

  • سأقسّم هذه العملية إلى ست مراحل مختلفة، وسنتناولها جميعًا بالترتيب.

  • تتمثل المرحلة الأولى في نقل MotionEvents إلى Compose.

  • لذا، فإنّ Compose هو في الواقع مجرد طريقة عرض: طريقة عرض معقّدة للغاية تؤدي الكثير من المهام، ولكنّها مجرد طريقة عرض، ما يعني أنّها تحتاج إلى العمل ضمن نظام Android العام لإدخال المؤشر.

  • لذا، في Compose، يعني ذلك أنّ Compose، بما أنّه يعمل داخل نافذة عادية داخل نشاط عادي، يتلقّى MotionEvents: وهي MotionEvents لنظام Android الأساسي.

  • بعد ذلك، يجب أن يتعامل Compose معها وينفّذ كل ما تريد تنفيذه ضمن Compose في ما يتعلّق بإدخال المؤشر.

  • المرحلة الأولى هنا هي ترجمة أحداث MotionEvents إلى تمثيل داخلي مستقل عن النظام الأساسي في Compose نسميه PointerInputEvent.

  • هذا نوع داخلي، لذا لن تراه مباشرةً.

  • وإذا أردت الاطّلاع على جميع تفاصيل ما يحدث هنا، يمكنك الاطّلاع على MotionEventAdapter، ولكننا سنتخطى هذه التفاصيل هنا.

  • لدينا الآن تمثيل مستقل عن المنصة لـ MotionEvent في Compose.

  • بعد ذلك، يتضح أن الإدخال يحدث عادةً بمرور الوقت.

  • ستحصل على مجموعة من أحداث الإدخال واحدًا تلو الآخر، لذا من المفيد حقًا معرفة ما يحدث بمرور الوقت.

  • فالإيماءات وتتبُّع السرعة ورصد الانزلاق، كل هذه الأمور تتطلب فهم ما يحدث في كل نقطة زمنية.

  • لذا، فإنّ المرحلة التالية في مسار المعالجة هذا هي إضافة معلومات الوقت هذه، وسيتم إجراء بعض المعالجة المسبقة استنادًا إلى أحداث المؤشر المتتالية.

  • في الرمز البرمجي، يمكنك النظر إلى ذلك على أنّه أخذ PointerInputEvent الحالي ودمجه مع ما حدث مع الحدث السابق، ثم إنشاء InternalPointerEvent يراعي الوقت.

  • هذا نوع داخلي آخر، كما يشير الاسم، ولكن يمكنك اعتباره خريطة لمعرّفات المؤشرات المختلفة لتغييرات إدخال المؤشر، وPointerInputChanges هو نوع عام.

  • هذا هو أول مثال لدينا.

  • والشيء الذي قد تلاحظه عند التفكير في معرّفات المؤشرات المختلفة هو أننا الآن على دراية بتتبع الوقت، ويمكننا أيضًا تتبع وقت ظهور معرّفات المؤشرات واختفائها.

  • هناك أكثر من طريقة.

  • إذا كان لديك عدة أصابع على شاشة لمس، فقد يكون لديك عدة مؤشرات في الوقت نفسه تأتي وتذهب، ونحن بحاجة إلى التعامل مع ذلك.

  • مرة أخرى، إذا أردت التعمّق في التفاصيل، يمكنك الاطّلاع على PointerInputChangeEventProducer الذي ينفّذ كل هذه الإجراءات، ولكننا سنتخطّى ذلك.

  • لدينا الكثير لنتناوله.

  • لدينا الآن تمثيل Compose للإدخال، ويتضمّن معلومات الوقت.

  • الخطوة التالية هي تحديد ما يجب فعله بهذا الحدث.

  • أين نرسلها؟

  • وهذا يؤدي إلى اختبار مسار الوصول.

  • لنتخيّل موقفًا يتم فيه إرسال MotionEvent، ويملأ ComposeView النافذة بأكملها، أو الشاشة بأكملها ربما.

  • علينا معرفة المكان الذي سنرسله إليه.

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

  • تحتوي عُقد التنسيق هذه على إحداثيات وحدود وأشكال، لذا علينا معرفة أي منها، إن وُجد، تم النقر عليه.

  • ولكن هذا ليس أيضًا العرض الأكثر عمومية.

  • يتضح أننا نحب وضع أشياء تفاعلية داخل أشياء تفاعلية أخرى، مثل وضع زر داخل قائمة.

  • واتضح أنّنا نفعل ذلك كثيرًا.

  • إذًا، الحالة العامة الكاملة هنا هي أنّ حدث إدخال معيّنًا ينشئ مسار نقر كاملاً يمرّ عبر كل عقدة قابلة للتفاعل يتم النقر عليها من خلال إحداثيات حدث الإدخال.

  • لذلك، في هذه الحالة، هناك ثلاثة عناصر أثناء مرورها في التسلسل الهرمي للتداخل.

  • لدينا مسار ناجح، لكنّه ليس كافيًا.

  • هل تذكرون عندما قلت إنّ لدينا أحداثًا متعددة لإدخال المؤشر، ومؤشرات متعددة قد تكون منخفضة؟

  • نحتاج في الواقع إلى الحصول على شجرة مسار زيارات كاملة.

  • لا يوجد مسار نقر واحد فقط عندما يكون لديك مؤشرات متعددة.

  • إذًا، هذه هي المرحلة التالية، وهي إنشاء "شجرة مسار النتائج" وتحديثها.

  • هذه نظرة أكثر تفصيلاً على ما يحدث.

  • على اليمين، لدينا عُقد تم تخطيطها بصريًا، ثم في أسفل اليمين، يمكنك رؤية التمثيل الهرمي، أي العُقد الموجودة داخل عُقد أخرى، وأيها أسلاف أو أحفاد لبعضها البعض.

  • عندما ينخفض المؤشر، نتتبع مسار النقر، والمسار الكامل عبر الشجرة، ثم نحصل على مسار النقر لهذا المؤشر المحدد.

  • يصبح مسار النتائج هذا شجرة عندما نحصل على المؤشر الثاني.

  • قد تتقاطع مع بعض العُقد نفسها مثل الأخرى، ولكنها قد تتفاعل مع عُقد أخرى أيضًا.

  • على اليسار، تظهر شجرة مسار النتائج التي نحتفظ بها الآن.

  • ويمكن أن تظهر المؤشرات وتختفي، لذا علينا الحفاظ على هذه القائمة بمرور الوقت للتأكّد من حصولنا على القائمة الكاملة للعُقد المهتمة بكل حدث جارٍ في هذه اللحظة.

  • حسنًا.

  • لدينا الآن "شجرة مسار النقرات" التي نحتفظ بها لأحداث الإدخال هذه، ما يعني أنّنا ننتقل إلى المرحلة التالية.

  • الآن، نعرف بالضبط أين سنرسل أحداث الإدخال بالمؤشر، وأي العُقد مهتمة بكل حدث محدّد.

  • الآن بعد أن عرفنا ذلك، يمكننا إجراء تحويل إحداثي.

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

  • بعد معرفة ذلك، يمكننا أخذ حدث إدخال المؤشر ودمجه مع إحداثيات العقدة المعنيّة، ثم إنشاء PointerEvent العام الذي سيتم إرساله إليكم جميعًا وإلى معدِّل إدخال المؤشر.

  • وهذا هو آخر شيء في مرحلتنا، المرحلة 6، وهو إرسال الحدث أخيرًا.

  • هذا حدث إدخال مؤشر يشير إلى وقوع الحدث، ويتم إرساله إلى العقدة لتنفيذ ما تريده به.

  • وننفّذ ذلك من خلال ثلاث مراحل مختلفة.

  • لدينا مرحلة أولية ومرحلة رئيسية ومرحلة نهائية.

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

  • لذا، فإنّ المسار الأول، أي المسار الأولي، ينتقل من الأصل إلى الفرع.

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

  • وهذا يسمح له، في المسار الأولي، باستهلاكه، ومن ثم لن تحصل العناصر الفرعية على أي أحداث أخرى.

  • أما المرحلة الثانية، فهي المرحلة الرئيسية.

  • وهذا هو الوحيد الذي ينتقل من السليل إلى السلف.

  • كما يوحي الاسم، هذه هي البطاقة الأساسية لإجراء المهام.

  • على سبيل المثال، هذا هو المكان الذي سيستهلك فيه العنصر الثانوي حدثًا سفليًا، ويبدأ في معالجة النقرة، ثم يحصل على فرصة للقيام بذلك قبل أي من العناصر الرئيسية إذا كان لديك عناصر قابلة للنقر مدمجة.

  • ثمّ في المرحلة الأخيرة، تكون النتيجة مماثلة للمرحلة الأولى، حيث يتم الانتقال من العنصر الرئيسي إلى العنصر الفرعي.

  • وهنا يمكن للحدث الفرعي إبلاغ الحدث الرئيسي بأنّه استهلك جزءًا منه.

  • لذلك، وربما إذا بدأ الطفل في التعامل مع نقرة، ولكن بعد ذلك بدأ أحد الوالدين في التمرير، يمكن للطفل أن يرى أن الوالد فعل ذلك، ومن ثم يمكن للطفل إلغاء النقرة التي بدأ في التعامل معها.

  • إذن، هذه هي المراجعات الثلاث، وهذه هي المراحل الست.

  • هذه هي الأساس لكل شيء في أعلى نظام إدخال المؤشر.

  • تعتمد جميع عمليات معالجة الإيماءات، والعناصر القابلة للتمرير، والعناصر القابلة للسحب، والعناصر القابلة للنقر، وأي إدخال مخصّص للمؤشر على هذا الأساس.

تحديثات لوحة اللمس والإيماءات بعدة أصابع

  • بعد أن تناولنا كل ذلك، يمكننا الآن التحدّث قليلاً عن تفاصيل كيفية تحسين دعم سلوك لوحة اللمس في Compose.

  • لذلك، قبل الإصدار 1.12 من Compose، كان يتم الإبلاغ عن أحداث الإدخال من لوحة اللمس كنوع من أنواع اللمس بالمؤشر، لأنّ الإصبع يتفاعل فعليًا مع لوحة اللمس للتحكّم في المؤشر.

  • ومع ذلك، يتوقع المستخدم أن يعمل نوع الإدخال هذا بشكل يشبه إلى حد كبير الماوس الفعلي.

  • لذلك، غيّرنا نوع المؤشر من PointerType.Touch إلى PointerType.Mouse.

  • من خلال هذا التغيير، سيلاحظ المستخدمون تجربة أكثر اتساقًا للماوس ولوحة اللمس في أي مكوّن يعرض هذه المعلومات.

  • على سبيل المثال، سلوك تلميح الشاشة: قد تريد أن يكون لديك السلوك نفسه للوحة اللمس والماوس لتلميحات الشاشة.

  • التغيير الكبير الثاني الذي أجريناه على لوحة اللمس يتعلّق بالإيماءات بعدة أصابع التي يمكن إجراؤها على لوحة اللمس.

  • في السابق، كان Compose يمر عبر مفهوم نظام Android الأساسي الذكي، ولكنه نوع من المفهوم المخيف للأصابع المزيفة.

  • لنتعرّف على معنى ذلك.

  • عند استخدام إصبعَين على لوحة اللمس للتحريك، ينشئ Android إصبعًا مزيفًا واحدًا على الشاشة التي تعمل باللمس ينزل إلى مكان المؤشر.

  • بعد ذلك، عندما تحرّك أصابعك على لوحة اللمس، يتحرّك الإصبع المزيّف على الشاشة التي تعمل باللمس بالمسافة والاتجاه الصحيحَين لمحاكاة السلوك المطلوب، ثم يتم تحريره.

  • لذلك، يتم تحويل حركة الإصبعين للأعلى على لوحة اللمس إلى حركة إصبع واحد وهمي على الشاشة التي تعمل باللمس، يتحرك أيضًا للأعلى، ما يؤدي بدوره إلى التمرير للأسفل.

  • الشيء الممتع هنا هو أنه إذا كنت، مثلي، تستخدم ميزة التمرير العكسي للوحة التعقب، فأنت تريد أن يسير التمرير في الاتجاه الآخر.

  • ويتم تحقيق ذلك من خلال قلب اتجاه الإصبع المزيّف.

  • الآن، يتم تحويل حركة الإصبعين على لوحة اللمس للأعلى إلى حركة إصبع واحد وهمي على الشاشة التي تعمل باللمس للأسفل، ما يؤدي بدوره إلى التمرير للأعلى، وهو عكس ما كان لدينا من قبل.

  • يعمل القياس بطريقة مشابهة نوعًا ما.

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

  • لذلك، منذ الإصدار 1.12 من Compose للتحريك والإصدار 1.13 من Compose لتغيير الحجم، أصبحنا الآن نعترض هذه الأصابع المزيفة.

  • لا نرسلها كما يوفّرها لنا النظام الأساسي، بل نعيد تفسيرها إلى أنواع أحداث مؤشرات جديدة: PanStart وPanMove وPanEnd للتحريك، وScaleStart وScaleChange وScaleEnd لتغيير الحجم.

  • ونفعل ذلك في مرحلة تحويل أحداث Android إلى Compose التي تحدثت عنها سابقًا.

  • لذلك، نحوّل أحداث "الإصبع الزائف" إلى أنواعها المناسبة، ثم نرسل من خلال هذه الإيماءات المحدّدة في النظام الأساسي إلى "مسار النقر" الذي يتوافق مع المؤشر.

  • لنتعرّف على أهمية ذلك.

  • لماذا نقوم بذلك؟

  • لنفترض أنّ هذا المربّع هنا هو صورة جميلة نريد أن نتمكّن من تكبيرها.

  • عند استخدام إصبعين على لوحة اللمس لتغيير الحجم، كان من الممكن أن يخطئ أحد الإصبعين المزيّفين العنصر.

  • لن يتقاطع مسار النقر مع الصورة، وسيبدأ خارج الحدود.

  • لذا، ما قد يسببه ذلك بدلاً من تغيير الحجم هو أن الصورة قد يتم تمريرها بدلاً من ذلك.

  • بعد ذلك، ربما إذا كان هناك عنصر آخر قابل للتمرير خارج الصورة، فربما يبدأ هذا العنصر أيضًا في التمرير معًا.

  • هذا ليس ما أراده المستخدم.

  • وأرادت توسيع نطاق أعمالها.

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

  • بغض النظر عن مكان المؤشر أو مدى قربه من حافة العنصر الذي تريد تغيير حجمه، سيتم الآن تغيير حجمه بشكل صحيح.

  • الخبر السار لكم جميعًا هو أنّ أحداث الإدخال المُعاد تفسيرها هذه تُستخدم تلقائيًا في مكوّنات مثل scrollable وtransformable لتوفير سلوك عالي الدقة لإدخالات لوحة اللمس إذا كنتم تستخدمون هذه العناصر الأساسية.

  • وإذا كنت تستخدم أي ميزة تستند إلى هذه المكوّنات، ستحصل على هذا السلوك المحسّن من خلال تحديث إصدار Compose إلى أحدث إصدار.

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

  • وبالطبع، أضفنا أيضًا واجهات برمجة تطبيقات للاختبار، والتي يمكنك من خلالها أيضًا التحقّق من أنّ تطبيقك يتعامل مع جميع أنواع الإدخال المختلفة هذه بشكل صحيح.

  • لذلك، تشبه واجهة برمجة التطبيقات performTrackpadInput إلى حد كبير واجهات برمجة التطبيقات الحالية المخصّصة لاختبار الماوس واللمس.

    onNodeWithContentDescription("Photo")
        .performTrackpadInput {
            click(center)
    
            pan(Offset(0f, 100f))
    
            scale(0.9f)
        }
  • وباستخدام كل هذه العناصر، يمكنك التحقّق من أنّ تطبيقك يعمل بشكل صحيح بغض النظر عن جهاز الإدخال الذي يستخدمه المستخدم على جهازك.

  • لذا، نأمل أن يمنحك هذا فكرة أفضل بكثير عن كيفية انتقال حدث المؤشر من الإصبع على لوحة اللمس إلى تطبيقك.

  • والآن، سأترك الكلمة لـ "جيريمي" ليتحدّث عن طريقة مختلفة تمامًا لاستخدام لوحات اللمس.

مقدمة عن الإدخال غير المباشر

  • شكرًا.

  • حسنًا.

  • مرحبًا، أنا "جيريمي ووكر".

  • أعمل كثيرًا على ميزات الإدخال، وأعتقد أنّه من المفيد أن أطلعكم على بعض الميزات الجديدة التي نقدّمها في ميزة الإدخال في Compose وتأثيرها عليكم.

  • وتحديدًا مع هذا النوع من الإدخال غير المباشر، والذي ظهر بسبب إدخال جديد يعتمد على شكل جهاز جديد: النظارات الذكية.

  • لذا، عندما تتلقّى مدخلات من النظارات، يمكنك إجراء ذلك بطريقتَين: يمكنك النظر إلى شيء ما، أو استخدام صوتك، أو استخدام الإيماءات، ولكنّني سأتحدث تحديدًا عن لوحة اللمس.

  • وهي مختلفة قليلاً عن تلك التي تحدّث عنها "أليكس"، فهي تقع على جانب الرأس، وهي عمودية وعريضة جدًا وقصيرة للغاية.

  • لذا، عندما كنا نفكر في كيفية عمل ذلك في Compose، إذا تخيلت أنك تنظر أمامك إلى زر صغير، وإذا كنت تحرك المؤشر لمحاولة لمسه، فلن تكون هذه تجربة رائعة، أليس كذلك؟

  • إذًا، كيف سندعمها؟

  • هذا هو شكل واجهة مستخدم Glasses للتذكير.

  • وقد تم تصميمها بشكل فريد لعرض المحتوى بسلاسة على مجال الرؤية.

  • لكنّ الأمر بسيط جدًا، ونحن نحاول الحفاظ على بساطته.

  • وإذا نظرنا إلى مثال هنا، يمكنك أن ترى أن هذه قائمة.

  • وهي تركّز على عنصر معيّن في الوسط.

  • ما الذي نريد فعله حقًا؟

  • حسنًا، نريد تحديدها، أو ربما الانتقال إلى السابقة أو التالية.

  • لا نحتاج بالضرورة إلى موقع على الشاشة.

  • وهذا الخيار أكثر تعقيدًا بعض الشيء، ولكن يمكنك أن ترى أن هناك ثلاثة خيارات أساسية: يمكنك إنهاء المكالمة، أو إعادة الصوت، أو إيقاف الفيديو.

  • ولكن في الواقع، عليك اختيارها أو الانتقال إلى أخرى واختيارها.

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

  • هذا هو شكلها الفعلي.

  • على سبيل المثال، يمكنكم رؤية كيف أتنقّل بين العناصر على واجهة مستخدم Glasses، وهذا ما نريده.

  • ويمكنك النقر عليها.

  • لذا، نظرنا إلى Android لنرى ما هو متاح ويمكننا استخدامه لدعم ذلك.

  • وإذا كان لديكم أي سؤال، عذرًا.

  • هذا يتخطى هنا.

  • لنعود إلى هذه النقطة.

  • لقد بدأنا

  • حسنًا.

  • لذا، إذا استخدمتم أحد هذه الأجهزة وربطتموه بجهاز Android، ستعرفون أنّه يمكنكم الانتقال بسهولة من عناصر واجهة المستخدم من جهاز إلى آخر.

  • على سبيل المثال، باستخدام جهاز التحكّم عن بُعد، يمكنك الاطّلاع على ما يمكنك فعله إذا تخيّلت أو ربطته بـ Google TV، ولديك شبكة كاملة من الأفلام، يمكنك الضغط على أزرار الأسهم للانتقال إلى الفيلم الذي تريده، ويمكنك الضغط على الزر لاختياره، وهو زر الموافقة.

  • إذا كان لديك قائمة ألعاب ولديك وحدة التحكّم هذه، يمكنك استخدام لوحة التحكّم أو عصا التحكّم للانتقال إلى عنصر في واجهة المستخدم، لنفترض، في قائمة، وتحديده.

  • وينطبق الأمر نفسه على لوحة المفاتيح.

  • لذا، يتطابق هذا النوع مع ما نريده في Compose.

  • ومرة أخرى، إليك العناصر، حتى تتمكن من الحصول على فكرة عما تبدو عليه.

  • وإذا عدنا إلى الوراء قليلاً، سنجد أنّ هذا النوع من لوحات اللمس كان مقترنًا بجهاز تلفزيون، وكان يستخدم ما يُعرف باسم "التنقّل باللمس".

  • والفكرة هنا هي أنّ إحداثيات x وy لا يتم لمسها على الإطلاق ولا ترتبط بالشاشة على الإطلاق.

  • ويتم استخدامها فقط لرصد الإيماءات.

  • لذا، ما تفعله هناك هو أنه إذا قمت بالتمرير لليمين، يمكنك أن تتخيل أن هذا يشبه الضغط على "أعجبني"، السهم للانتقال.

  • إذا مرّرت سريعًا لليسار، ستنتقل إلى اليسار وإلى الأعلى وإلى الأسفل.

  • لذا، يمنحنا هذا النوع في الغالب ما نريده.

  • يمكنك استخدام x وy فقط لتتبُّع السرعة من أجل معرفة عمليات السحب والتمرير السريع وما إلى ذلك، وهذا يمنحنا معظم ما نريد البدء به في Compose.

  • هذا ليس دقيقًا لأنّه مرتبط بالتلفزيون، لذا علينا التأكّد من عدم اتّباع مسار التلفزيون، وعدم إفساد أجهزة التلفزيون لاحقًا، لذا علينا التأكّد من تقسيمها، ولكن هذا أمر سنتطرّق إليه بعد قليل.

  • ولكنها تمنحنا نوعًا من نقطة البداية.

  • لنتمكّن من استخدام هذا النوع من التنقّل باللمس.

تصميم واجهة برمجة التطبيقات للإدخال غير المباشر وأشكال الأجهزة الجديدة

  • حسنًا.

  • كيف سيبدو تصميم واجهة برمجة التطبيقات؟

  • في Compose، الإدخال الأكثر شيوعًا هو اللمس، أليس كذلك؟

  • لذلك، لدينا الكثير من العمل حول ذلك.

  • لذلك، كان علينا معرفة كيف يرتبط ذلك.

  • للتذكير، إليكم شكل PointerEvent.

  • يمكنك أن ترى هنا أن لديك النوع وهو نوع الحركة لأسفل في الأعلى، ثم لديك PointerInputChange وهو مجرد قائمة يمكنك التفكير فيها كأصابع.

  • إذا كان لديك ثلاثة أصابع لأسفل، فسيكون لديك ثلاثة أشياء في القائمة.

  • لذلك أردنا أن يبدو كثيرًا مثل هذا.

  • هذه ليست وحدة واجهة مستخدم، وهي وحدة أقل بكثير مما يستخدمه معظم المطورين عند التعامل مع الإدخال.

  • وستكون طبقة فوق طبقة الأساس.

  • وهذا هو المكان الذي توجد فيه جميع أدوات الكشف عن الإيماءات.

  • لأنّكم كمطوّرين، لا تهتمون بكل هذه التفاصيل الدقيقة.

  • تريد فقط معرفة ما إذا حدث سحب، أو حدثت نقرة، أو حدث تمرير سريع، ثم تقوم بإجراءك بناءً على ذلك.

  • لدينا مجموعة من الرموز التي تنفّذ هذه المهمة نيابةً عنك، وتستند إلى هذه الميزات في الخلفية.

  • لذلك، نبدأ في التفكير، حسنًا، علينا أن نجعل الأمر سهلاً قدر الإمكان حتى يتمكن الكود الموجود في الأساس من دعم هذه الأنواع الجديدة بسهولة.

  • لذلك، أنشأنا نوعًا جديدًا يبدو متشابهًا إلى حد كبير.

  • ويُطلق عليها اسم "غير مباشرة".

  • والمعنى غير المباشر هو أنّه ليس لمسة مباشرة على الشاشة.

  • لذا، يتيح لك معرفة أنّ هذا قادم، ولا تفكّر في ذلك من وجهة نظر اللمس.

  • لنفكّر في أنّنا نتعامل مع الإيماءات هنا ونتنقّل بين عناصر في واجهة المستخدم.

  • النوع هو نفسه.

  • تحتوي على قائمة بالأصابع.

  • وهو IndirectPointerInputChange في هذه الحالة.

  • لذا، نفّذنا هذه الخطوات، وحقّقنا نتائج رائعة.

  • ولكن بعد ذلك كان هناك عنصر آخر صادفناه يسمى محور الحركة الاتجاهي الأساسي.

  • هذا كثير، لكنه سيكون منطقيًا عندما أشرحه، لأنّ طريقة حدوثه كانت مثيرة للاهتمام.

  • فكيف حدث ذلك؟

  • مرة أخرى، لديك لوحة اللمس هذه على جانب رأسك، وهي عريضة وقصيرة.

  • لذلك، في هذه المرحلة، كانت لدينا فكرة مثل، حسنًا، لدينا التنقل باللمس.

  • لذا، يمكننا التمرير سريعًا لليسار والتمرير سريعًا لليمين.

  • وهذا يعمل بشكل جيد.

  • ولكن إذا فكرت في كيفية وجود لوحات اللمس الآن، أليس كذلك؟

  • عندما تذهب إلى اليسار أو اليمين، ستذهب إلى اليسار أو اليمين على لوحة التتبع باستخدام محتوى واجهة المستخدم.

  • إذا انتقلت للأعلى أو للأسفل، سيتم التمرير في القائمة للأعلى أو للأسفل.

  • لذا إذا حاولت القيام بذلك باستخدام النظارات، فهذا ليس سيئًا للغاية، أليس كذلك؟

  • ولكن إذا كنت تفعل ذلك، فلن تكون تجربة رائعة.

  • وجدنا أنّ الحركة الأكثر سلاسة هي النقر، ولكن من خلال التمرير السريع على جانب النظارة.

  • لذلك أجرينا مجموعة من الدراسات مع المستخدمين، وهم يتوقعون في الواقع أنه عند التمرير أفقيًا، فإنك لا تنقل المحتوى أفقيًا في الواقع، بل تنقله عموديًا.

  • ويمكنك التفكير في هذا الأمر إذا جرّبته في ذهنك، مثلاً عند التمرير السريع هنا، إذا كانت لديك قائمة أمامك، مثل الانتقال للأمام، يمكنك الانتقال للأمام من خلال القائمة أو يمكنك الرجوع من خلال القائمة.

  • لذا، نحن نأخذ مدخلات ثنائية الأبعاد ونحاول التفكير فيها بطريقة أحادية البُعد.

  • ومن هنا جاءت فكرة "محور الحركة الأساسي" (Primary Motion Axis) التي تتيح لك التمرير أفقيًا، فيتحرّك المحتوى عموديًا.

  • لذلك، يمكنك تحديد ذلك على الجهاز نفسه.

  • يمكنك أن تقول، مرحبًا، مثل هذه ليست لوحة تتبع عادية.

  • أريد أن يكون الاتجاه الأساسي أفقيًا، وهذا هو المحور الرئيسي والوحيد الذي أريد اتباعه.

  • وهذا هو مصدر هذه الفكرة.

  • حسنًا

  • لذا، نحن نشعر بالرضا حيال ذلك.

  • يتولّى Compose معالجة كل شيء.

  • ولديكم هذا التنفيذ العمودي الرائع.

  • يمكنك التفكير في هذا الأمر.

  • لذا، كيف قمنا بتعيينها الآن لديك مع التعريف الأفقي، لدينا فقط للأمام والخلف.

  • ليس لدينا أعلى وأسفل ويسار ويمين.

  • لدينا فقط الأمام والخلف.

  • ويمكننا بعد ذلك ربط ذلك إذا كنت على دراية بالتركيز مع التالي والسابق.

  • لذا، ننتقل الآن إلى التالي والسابق.

  • وهذا يعمل بشكل رائع مع الفيديوهات العمودية.

  • وهو أيضًا أجمل.

  • يمكن استخدامه مع الفيديوهات الأفقية على الفور، أليس كذلك؟

  • لأنّك تركّز على مجموعة من العناصر فقط.

  • ما عليك سوى الانتقال إلى التالي أو السابق، وستعمل الميزة مع كليهما.

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

  • لذا، كان علينا الآن إعادة هذا المفهوم إلى المنصة والقول، حسنًا، نحتاج إلى تحديد هذا في المنصة حتى نتمكن من توحيده ويمكن لهذه الأجهزة نفسها تحديده، ومن ثم يمكن لـ Compose دعمه بالكامل على طول الطريق.

  • لذا، هناك حالة عملنا فيها في Compose وتقدمنا في Compose وعملنا بشكل عكسي، وأحضرناها معنا.

  • وهذا بالضبط ما فعلناه.

  • لقد أنشأنا واجهة برمجة التطبيقات الجديدة هذه لكي تتمكّن من تحديد ذلك، أو يمكن للجهاز تحديد ذلك.

    InputDevice.ViewBehavior
    .getPrimaryDirectionalMotionAxis()
  • وإذا تذكرتم ما قلته سابقًا عن الإدخال باللمس، أو التنقّل باللمس، كنت أتحدث عن أنّه مخصّص بشكل أساسي لأجهزة التلفزيون.

  • لم نكن نريد إيقاف عمليات التنفيذ هذه.

  • لذلك، أضفنا حقلاً آخر لتحديد هذا النوع الجديد من إدخال الزجاج.

  • حسنًا

  • إذًا، كيف يمكنكم الاستفادة من ذلك؟

  • هذا إصدار أكثر تحديدًا مما تحصل عليه كأداة رصد الإيماءات.

    // Glimmer Library
    Box(
        modifier =
            Modifier.fillMaxSize()
                .onIndirectPointerGesture(
                    enabled = true,
                    onSwipeForward = { /* onSwipeForward */ },
                    onSwipeBackward = { /* onSwipeBackward */ },
                    onClick = { /* onClick */ },
                )
                .focusTarget()
    ) {
        // App()
  • إذا كنت تستخدم Foundation، ستعمل هذه الميزة تلقائيًا مع أدوات رصد الإيماءات.

  • لقد أجرينا التغييرات اللازمة، لذا ليس عليك اتّخاذ أي إجراء.

  • ولكن إذا أردت إنشاء تطبيق للنظارات، يمكنك استخدام مكتبة Glimmer، ولدينا أداة خاصة لرصد الإيماءات، والتي تمنحك فقط، كما تعلم، حتى لا تضطر إلى دمجها، يمكنك استخدام هذه الأداة فقط، والحصول على النقرة، والإطار الأمامي، والخلفي.

اختبار إدخال المؤشر غير المباشر

  • حسنًا

  • لقد تناولنا طريقة عمل واجهة برمجة التطبيقات نفسها، في المنصة وفي Compose.

  • ماذا عن الاختبار؟

  • هذا هو شكل اللمس عندما تريد إجراء اختبار اللمس.

    @Test
    clickTest {
    
    rule.onNodeWithTag(defaultTag)
    .performTouchInput
    { click(center) }
    
    }
  • ما عليك سوى قول performTouchInput ثم تنفيذ إيماءة.

  • هناك الكثير من الإيماءات المختلفة، ونحن نجمع كل الأحداث الصغيرة لإنشاء الإيماءة.

  • كيف يتم ذلك على مستوى عالٍ؟

  • حسنًا، لديك وحدة Compose UI، ولديك وحدة UI Testing وهما يتواصلان نوعًا ما، على الأقل من حيث الإدخال، باستخدام MotionEvents.

  • تستخدم وحدة اختبار واجهة المستخدم أيضًا آلية قائمة انتظار، وتستند جميعها إلى MotionEvent.

  • لماذا أقول لك هذا؟

  • لأنه لا يمكنك إنشاء MotionEvent برمجيًا مثل حدث إدخال غير مباشر، وكان ذلك رائعًا بالنسبة لي لأن ذلك يعني أنه كان علي إعادة كتابة جميع الاتصالات بين الوحدات ونظام قائمة الانتظار.

  • لكنني فعلتها.

  • كيف يمكنكم الاستفادة من ذلك؟

  • إذا كنت معتادًا على اختبار الإدخال باللمس، فهذا ما ستحصل عليه في الإدخال غير المباشر.

  • ما عليك سوى إضافة كلمة "غير مباشر" إلى الاسم.

    @Test
    clickTest {
    
    rule.onNodeWithTag(defaultTag)
    // .performTouchInput
    .performIndirectPointerInput (
    IndirectPointerEventPrimaryDirectionalMotionAxis.X,
    inputDeviceSize,
    )
    { click(center) }
    }
  • الآن، لقد فعلت ذلك، وأنا أمرر بعض المعلمات الإضافية.

  • من المحتمل أن تتعرّف على الأول، وهو المكان الذي تحدد فيه المحور الأفقي.

  • وسيكون حجم الإدخال هو حجم لوحة اللمس.

  • هذا لا يغيّر الكثير.

  • لذا، إذا نظرت إلى الرمز المصدر، يمكنك أن ترى أنني كتبت للتو دالة امتداد وتبدو هكذا.

    @Test
    clickTest {
    
    rule.onNodeWithTag(defaultTag)
    // .performTouchInput
    .performIndirectPointerInput
    { click(center) }
    
    }
  • حسنًا

  • هذا يمنحكم فكرة عن كل العمل الذي قمنا به فقط لدعم لوحات اللمس على الإطارات أو على النظارات الذكية.

  • ولكننا نقوم بهذه التمارين، وأنا أقوم بهذه التمارين لتتبع العين واليد أيضًا، لذلك هناك الكثير في المستقبل.

  • لذا، من وجهة نظر عامة، ما الذي أريدك أن تتذكره من الجزء الخاص بي؟

الملخّص والنقاط الرئيسية

  • لذا، سواء كنت تستخدم لوحة لمس مع هاتف أو نظارات أو أي جهاز إدخال آخر، فإننا نفكر في ذلك ونحاول القيام بالعمل في وقت مبكر.

  • لذلك، إذا كنت تستخدم أدوات رصد الإيماءات في Foundation، ستعمل هذه الميزة تلقائيًا.

  • هذه هي النقطة الأولى.

  • ثانيًا، Compose هي حزمة أدوات واجهة المستخدم الأساسية، وسنواصل استكشاف أنواع الإدخال الجديدة ودعمها، سواء في المنصة أو في حزمة الأدوات، وهو ما فعلته هنا.

  • وبعد ذلك، من الواضح أنّني أريد الحفاظ على واجهة ونموذج مألوفَين لاختبار واجهات برمجة التطبيقات، ليكون من السهل عليك نقل هذه الأنواع الجديدة واختبارها.

  • شكرًا لك، وآمل أن تكون هذه المعلومات مفيدة.

الرمز

مقتطفات الرمز البرمجي

مقتطف Kotlin في الدقيقة 15:47 في فيديو "تحديثات لوحة اللمس والإيماءات بعدة أصابع"
onNodeWithContentDescription("Photo")
    .performTrackpadInput {
        click(center)

        pan(Offset(0f, 100f))

        scale(0.9f)
    }
مقتطف Kotlin في الدقيقة 25:08 في فيديو "تصميم واجهة برمجة التطبيقات للإدخال غير المباشر وعوامل الشكل الجديدة"
InputDevice.ViewBehavior
.getPrimaryDirectionalMotionAxis()
مقتطف Kotlin في الدقيقة 25:32 في فيديو "تصميم واجهة برمجة التطبيقات للإدخال غير المباشر وأشكال الأجهزة الجديدة"
// Glimmer Library
Box(
    modifier =
        Modifier.fillMaxSize()
            .onIndirectPointerGesture(
                enabled = true,
                onSwipeForward = { /* onSwipeForward */ },
                onSwipeBackward = { /* onSwipeBackward */ },
                onClick = { /* onClick */ },
            )
            .focusTarget()
) {
    // App()
مقتطف Kotlin في الدقيقة 26:11 في "اختبار إدخال المؤشر غير المباشر"
@Test
clickTest {

rule.onNodeWithTag(defaultTag)
.performTouchInput
{ click(center) }

}
مقتطف Kotlin في الدقيقة 27:08 في "اختبار إدخال المؤشر غير المباشر"
@Test
clickTest {

rule.onNodeWithTag(defaultTag)
// .performTouchInput
.performIndirectPointerInput (
IndirectPointerEventPrimaryDirectionalMotionAxis.X,
inputDeviceSize,
)
{ click(center) }
}
مقتطف Kotlin في الدقيقة 27:23 في "اختبار إدخال المؤشر غير المباشر"
@Test
clickTest {

rule.onNodeWithTag(defaultTag)
// .performTouchInput
.performIndirectPointerInput
{ click(center) }

}