التعامل مع تغييرات الإدخال

توفّر أجهزة Chromebook للمستخدمين العديد من خيارات الإدخال المختلفة: لوحة المفاتيح والماوس ولوحات اللمس وشاشات اللمس وقلم الشاشة وMIDI وأجهزة التحكّم في الألعاب/أجهزة التحكّم الزرقاء. وهذا يعني أنّ الجهاز نفسه يمكن أن يصبح محطة DJ أو لوحة فنان أو منصة مفضّلة للاعبين لبث ألعاب AAA.

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

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

اكتشاف المستخدمين لأساليب الإدخال المتوافقة

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

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

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

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

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

تصنيف/تصميم واجهة المستخدم لخيارات الإدخال

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

واجهة مستخدم مخصّصة للأجهزة التي تعمل باللمس

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

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

الطلبات الظاهرة على الشاشة

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

  • مرِّر سريعًا إلى…
  • انقر في أي مكان لإغلاق مربع الحوار.
  • التكبير أو التصغير بإصبعَين
  • اضغط على X من أجل…
  • الضغط مع الاستمرار للتفعيل

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

اعتبارات متعلقة باللغات المتعددة

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

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

إدخال لوحة المفاتيح الافتراضية / محرر أسلوب الإدخال

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

التنفيذ

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

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

آلة الحالة ذات الأولوية استنادًا إلى الأحداث التي تم تلقّيها

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

الأولوية

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

يمكن أن يساعد تحديد كل مجموعة من الطلبات على أنّها "حالة إدخال" ثم تحديد أولوياتها في اتخاذ هذا القرار بطريقة متّسقة.

تحدّد أحداث الإدخال التي تم تلقّيها الحالة

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

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

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

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

مثال: لعبة يمكن لعبها باستخدام اللمس ولوحة المفاتيح/الماوس ووحدة التحكّم

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

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

تريد إضافة إمكانية استخدام لوحة المفاتيح ووحدة التحكّم في الألعاب عبر البلوتوث. من أين تبدأ؟

لعبة سباق سيارات مع عناصر تحكّم باللمس

حالات الإدخال

ابدأ بتحديد جميع حالات الإدخال التي قد تعمل بها لعبتك، ثم أدرِج جميع المَعلمات التي تريد تغييرها في كل حالة.

                                                                                                                                                                        
لمسلوحة المفاتيح/الماوسوحدة التحكم في الألعاب
       

التفاعلات مع

     
       

جميع المدخلات

     
       

جميع المدخلات

     
       

جميع المدخلات

     
       

عناصر التحكّم التي تظهر على الشاشة

     
       

- عصا التحكم على الشاشة
- زر النيترو

     
       

- ما مِن عصا تحكّم
- ما مِن زر نيترو

     
       

- ما مِن عصا تحكّم
- ما مِن زر نيترو

     
       

Text

     
       

انقر للحصول على Nitro!

     
       

اضغط على "N" للحصول على Nitro!

     
       

اضغط على "A" للحصول على Nitro!

     
       

البرنامج التعليمي

     
       

صورة لذراع التحكّم في السرعة/الاتجاه

     
       

صورة لمفاتيح الأسهم وWASD للسرعة/الاتجاه

     
       

صورة لوحدة التحكّم في الألعاب للسرعة/الاتجاه

     

تتبَّع حالة "الإدخال النشط"، ثم عدِّل واجهة المستخدم حسب الحاجة استنادًا إلى هذه الحالة.

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

تغييرات الحالة ذات الأولوية

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

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

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

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

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

في ما يلي مخطط حالة وجدول انتقال للمثال. عدِّل الفكرة لتناسب تطبيقك أو لعبتك.

آلة الحالة ذات الأولوية - شاشة تعمل باللمس أو لوحة مفاتيح/ماوس أو ذراع تحكّم في الألعاب

                                                                                                                                        
#1 Touchscreen‫#2 Keyboard‫#3 Gamepad
       

الانتقال إلى #1

     
       

لا ينطبق

     
       

- تم تلقّي إدخال باللمس
- الانتقال فورًا إلى حالة الإدخال باللمس

     
       

- تم تلقّي إدخال باللمس
- الانتقال فورًا إلى حالة الإدخال باللمس

     
       

الانتقال إلى #2

     
       

- عدم اللمس لمدة 5 ثوانٍ
- تم تلقّي إدخال من لوحة المفاتيح
- الانتقال إلى حالة الإدخال من لوحة المفاتيح

     
       

لا ينطبق

     
       

- تم تلقّي إدخال من لوحة المفاتيح
(الانتقال فورًا إلى حالة إدخال لوحة المفاتيح)

     
       

الانتقال إلى الترتيب 3

     
       

- عدم اللمس لمدة 5 ثوانٍ
- عدم استخدام لوحة المفاتيح لمدة 5 ثوانٍ
- تلقّي إدخال من جهاز التحكّم في الألعاب
- الانتقال إلى حالة الإدخال من جهاز التحكّم في الألعاب

     
       

- عدم استخدام لوحة المفاتيح لمدة 5 ثوانٍ
- تم تلقّي إدخال من لوحة الألعاب
- الانتقال إلى حالة إدخال لوحة الألعاب

     
       

لا ينطبق

     

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

3- لوحة الألعاب -> 2. لوحة المفاتيح -> 1 لمس

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

أحداث الإدخال

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

أزرار لوحة المفاتيح ووحدة التحكّم

// Drive the state machine based on the received input type
// onKeyDown drives the state machine, but does not trigger game actions
// Both keyboard and game controller events come through as key events
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
    if (event != null) {
        // Check input source
        val outputMessage = when (event.source) {
            SOURCE_KEYBOARD -> {
                MyStateMachine.KeyboardEventReceived()
                "Keyboard event"
            }
            SOURCE_GAMEPAD -> {
                MyStateMachine.ControllerEventReceived()
                "Game controller event"
            }
            else -> "Other key event: ${event.source}"
        }
        // Do something based on source type
        findViewById(R.id.text_message).text = outputMessage
    }
    // Pass event up to system
    return super.onKeyDown(keyCode, event)
}
// Trigger game events based on key release
// Both keyboard and game controller events come through as key events
override fun onKeyUp(keyCode: Int, event: KeyEvent?): Boolean {
   when(keyCode) {
       KeyEvent.KEYCODE_N -> {
           MyStateMachine.keyboardEventReceived()
           engageNitro()
           return true // event handled here, return true
       }
   }
   // If event not handled, pass up to system
   return super.onKeyUp(keyCode, event)
}

ملاحظة: بالنسبة إلى KeyEvents، يمكنك اختيار استخدام onKeyDown() أو onKeyUp(). في هذا المثال، يتم استخدام onKeyDown() للتحكّم في آلة الحالة، بينما يتم استخدام onKeyUp() لتفعيل أحداث اللعبة.

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

اللمس وقلم الشاشة

// Touch and stylus events come through as touch events
override fun onTouchEvent(event: MotionEvent?): Boolean {
   if (event != null) {
       // Get tool type
       val pointerIndex = event.action and ACTION_POINTER_INDEX_MASK shr ACTION_POINTER_INDEX_SHIFT
       val toolType = event.getToolType(pointerIndex)

       // Check tool type
       val outputMessage = when (toolType) {
           TOOL_TYPE_FINGER -> {
               MyStateMachine.TouchEventReceived()
               "Touch event"
           }
           TOOL_TYPE_STYLUS -> {
                MyStateMachine.StylusEventReceived()
               "Stylus event"
           }
           else -> "Other touch event: ${toolType}"
       }

       // Do something based on tool type, return true if event handled
       findViewById(R.id.text_message).text = outputMessage
   }
   // If event not handled, pass up to system
   return super.onGenericMotionEvent(event)
}

الماوس وعصا التحكّم

// Mouse and joystick events come through as generic events
override fun onGenericMotionEvent(event: MotionEvent?): Boolean {
   if (event != null) {
       // Check input source
       val outputMessage = when (event.source) {
           SOURCE_JOYSTICK -> {
                MyStateMachine.ControllerEventReceived()
               "Controller event"
           }
           SOURCE_MOUSE -> {
                MyStateMachine.MouseEventReceived()
               "Mouse event"
           }
           else -> "Other generic event: ${event.source}"
       }
       // Do something based on source type, return true if event handled
       findViewById(R.id.text_message).text = outputMessage
   }
   // If event not handled, pass up to system
   return super.onGenericMotionEvent(event)
}