תשתית לאפליקציות

הרחבת האינטראקציות של הקלט מעבר למסך המגע באמצעות Jetpack Compose

רמקולים

  • אלכס וניו

    מהנדס תוכנה

  • ג'רמי ווקר

    מהנדס תוכנה ב-Compose

מידע כללי

מידע על ההרצאה

ככל שגורמי צורה חדשים מתפתחים מעבר למסך המגע, מערכת הקלט של Compose מתפתחת כדי לעמוד באתגר. הארכיטקטורה השכבתית שלו מספקת את הבסיס לתמיכה חלקה בכל דבר, החל מתנועות מתקדמות של משטח המגע ועד לקלט חומרה חדשני.

בעזרת פירוט ברור של הארכיטקטורה ודוגמאות קוד מעשיות, תוכלו לעקוב אחרי מחזור החיים של אירוע קלט, החל משכבת הפלטפורמה של מערכת ההפעלה ועד לגלאי התנועות ורכיבי ממשק המשתמש של Compose. תלמדו איך לעקוב אחרי אירועים בכל המחסנית, להחיל דפוסי ארכיטקטורה מרכזיים על סוגי קלט חדשים ולנפות באגים באינטראקציות מורכבות של תנועות באפליקציות שלכם.

סיכום

  • ‫Jetpack Compose מעבד אירועי סמן נכנסים ב-Android באמצעות צינור עיבוד נתונים עם שישה שלבים. הצינור הזה מתחזק עץ של נתיבי היט ושולח אירועים בשלושה מעברים בין צומתי ממשק המשתמש.
  • קלט ממשטח המגע ב-Compose ממופה עכשיו לאירועי עכבר במקום למגע. ‫Compose גם ממיר אירועים של אצבעות מדומות בפלטפורמה לאירועים ייעודיים של סמן הזזה ושינוי קנה מידה.
  • משטחי המגע של משקפיים חכמים מציגים סוגי קלט עקיפים במקום נגיעות בקואורדינטות של המסך. הם משתמשים בציר תנועה כיווני ראשי כדי למפות החלקות אופקיות לניווט אנכי לפריט הבא או הקודם.
  • התמיכה בבדיקות הורחבה מעבר למגע, עם שיטות חדשות כמו performTrackpadInput. ממשקי ה-API האלה מספקים פלטפורמות בדיקה מוכרות כדי לוודא שהאפליקציות פועלות כמו שצריך במכשירים ללא מסך מגע.

Gemini Notebook

נושאים

  • jetpack-compose
  • pointer-input
  • משטחי מגע
  • תנועות
  • משקפיים חכמים
  • נגישות

תמליל

הורדת התמליל

מבוא ופילוסופיית הקלט

  • שלום לכולם,

  • אנחנו מקווים שנהנית במפגש של מפתחי Android, ותודה שהשתתפת בשיחה שלנו.

  • אני אלכס וניו, מהנדס תוכנה בצוות Compose, ואיתי נמצא ג'רמי, גם הוא מהנדס תוכנה בצוות Compose.

  • שנינו עובדים על שיפור מערכת הקלט של Jetpack Compose, והיום נדבר על האופן שבו הרחבנו אותה מעבר למסך המגע כדי שיהיה קל יותר לתמוך בקלט ללא מגע באופן מיידי.

  • באופן כללי, קלט הוא כל דרך שבה משתמש מקיים אינטראקציה עם האפליקציה.

  • מסכי מגע הם כנראה הדבר הראשון שחושבים עליו כשמדובר באפליקציה לנייד, אבל הם בהחלט לא סוג הקלט היחיד.

  • סוגים אחרים של קלט כוללים מקלדות פיזיות, עכברים, משטחי מגע, עטי סטיילוס, שלטים ומכשירים עם חוגה.

  • אנחנו רואים גם סוגים חדשים של קלט, כמו משטחי מגע במשקפיים חכמים, מעקב עיניים וידיים ב-XR ותנועות של האצבעות ופרק כף היד ב-Wear.

  • ואם אתם רוצים להרחיב עוד יותר, יש גם סוגים אחרים של קלט: דברים כמו עורכי שיטות קלט (IME) של תוכנה לכלי לעריכת טקסט, מיקרופונים, מצלמות וחיישנים אחרים של המכשיר.

  • אתם צריכים לצפות לכך שהמשתמשים יפעילו את האפליקציות שלכם באמצעות מגוון רחב של סוגי קלט.

  • אחת הסיבות לכך היא המגוון ההולך וגדל של מכשירים שאפליקציות פועלות בהם, והיכולת ההולכת וגדלה של המכשירים האלה להתאים את עצמם.

  • לגורמי צורה של מכשירים יכולה להיות שיטת קלט נפוצה, אבל חשוב לא להניח שגורם צורה מסוים של מכשיר מרמז על שימוש בסוג קלט מסוים.

  • למרות שב-Chromebook יש משטח מגע ומקלדת מובנים, סוגי הקלט האלה לא ייחודיים לגורם הצורה הזה.

  • חשוב לזכור שמשתמשים במקלדות פיזיות, במשטחי מגע ובעכברים גם בטלפונים, בטאבלטים ובטלוויזיות, ובחלק מהטלוויזיות יש אפילו מסכי מגע.

  • סיבה חשובה נוספת לצפות לסוגי קלט מרובים ולתמוך בהם היא נגישות.

  • כדי להפוך את האפליקציות לנגישות ונוחות יותר לשימוש, חשוב לאפשר למשתמשים להשתמש במכשיר לקליטת נתונים לבחירתם ולהחליף בין מכשירים לקליטת נתונים לפי הצורך.

  • בהתאם לסמכות השיפוט, יכול להיות שתידרשו על פי חוק להגדיר התנהגות ספציפית ותואמת באפליקציה לסוגי הקלט האלה, במיוחד בכל מה שקשור למקלדות ולעכברים.

  • בקיצור, כדאי להוסיף תמיכה בסוגי קלט שונים בלי קשר לגורם הצורה.

  • אז בואו נראה איך Jetpack Compose עוזר לאפליקציות שלכם לענות על הצרכים האלה של המשתמשים.

  • הפילוסופיה של Compose, באופן כללי, היא שסוגי הקלט השונים האלה צריכים לפעול כראוי באופן מיידי עם רכיבים סטנדרטיים, בלי שתצטרכו לעשות משהו מיוחד.

  • אחרי זה, אתם צריכים את הכלים שיאפשרו לכם להתאים אישית ולשנות את ההתנהגות לפי הצורך.

  • לכן, מעבר לסוגי הקלט הראשוניים האלה, ככל שסוגי קלט נוספים נתמכים על ידי הפלטפורמה הבסיסית, אנחנו שואפים לבנות תמיכה בהם ברכיבי Compose, ולשפר את ההתנהגות של האפליקציות שלכם רק על ידי עדכון הגרסה של Compose.

  • בשיחה הזו נבחן שתי דוגמאות כאלה, שתיהן נוספו לאחרונה ל-Compose.

  • במקרה, שניהם מכשירים דמויי משטח מגע, אבל כפי שתראו, משתמשים בהם בדרכים שונות מאוד.

  • אז נתחיל עם עבודה שנעשתה לאחרונה על משטחי מגע של מחשבים ניידים.

  • משטח מגע הוא מכשיר לקליטת נתונים שמבוסס על סמן.

  • באופן ספציפי יותר, כשמשתמשים במשטח מגע, מקבלים קואורדינטות דו-ממדיות.

  • אבל בניגוד למסך מגע, הקואורדינטות של האצבעות במשטח המגע לא נמצאות ישירות במרחב הקואורדינטות של ממשק המשתמש.

  • במקום זאת, במשטח מגע כמו של מחשב נייד, יש תרגום לשליטה בסמן.

  • התרגום הזה כולל המרות מסובכות של פלטפורמות, כמו האצת סמן ודחיית מגע בכף היד.

  • לכן, מה שמקבלים בצד של Compose הוא מצביע שתואם למיקום הסמן, שבדרך כלל פועל כמו קלט עכבר.

  • מפלטפורמת Android אנחנו מקבלים מידע על ריחוף, קליקים כולל קליקים משניים או שלישיים, וכל המידע הזה נכלל ב-MotionEvents.

  • כדי להבין איך שיפרנו את ההתנהגות בגרסאות העדכניות של Compose, נתחיל בסקירה כללית של האופן שבו Compose מטפל באירועי הסמן האלה.

ששת השלבים של הטיפול בקלט של המצביע

  • אחלק את התהליך לשישה שלבים שונים, ונעבור על כולם לפי הסדר.

  • השלב הראשון הוא העברת MotionEvents לפיתוח נייטיב.

  • אז Compose הוא בעצם רק תצוגה: תצוגה ממש מסובכת שעושה הרבה, אבל היא רק תצוגה, מה שאומר שהיא צריכה לעבוד בתוך מערכת Android הכוללת לקלט של מצביע.

  • לכן, ב-Compose, זה אומר ש-Compose, כשהוא נמצא בתוך חלון רגיל בתוך פעילות רגילה, מקבל MotionEvents: ה-MotionEvents של פלטפורמת Android.

  • ואז Compose צריך לטפל בהם ולעשות את כל מה שרוצים לעשות ב-Compose לגבי קלט של סמן.

  • השלב הראשון הוא לתרגם את ה-MotionEvents האלה לייצוג פנימי ב-Compose שלא תלוי בפלטפורמה, שנקרא PointerInputEvent.

  • זה סוג פנימי, אז לא תראו אותו ישירות.

  • אם אתם רוצים לראות את כל הפרטים של מה שקורה כאן, אתם יכולים להסתכל על MotionEventAdapter, אבל אנחנו נדלג על הפרטים האלה כאן.

  • אז עכשיו יש לנו ייצוג של MotionEvent ב-Compose שלא תלוי בפלטפורמה.

  • בנוסף, מתברר שהקלט בדרך כלל מתרחש לאורך זמן.

  • אתם מקבלים הרבה אירועי קלט אחד אחרי השני, אז כדאי לדעת מה קורה לאורך זמן.

  • דברים כמו תנועות, מעקב אחר מהירות, זיהוי שיפוע, כל אלה צריכים להבין מה קורה בכל נקודת זמן.

  • לכן, השלב הבא בצינור עיבוד הנתונים הזה הוא להוסיף את המידע הזה על הזמן, והוא יבצע עיבוד מקדים על סמך אירועי מצביע עוקבים.

  • בקוד, אפשר לראות את זה כפעולה שלוקחת את ה-PointerInputEvent הנוכחי, משלבת אותו עם מה שקרה עם הקודם, ואז אנחנו יוצרים InternalPointerEvent שמתחשב בזמן.

  • זהו סוג פנימי נוסף, כפי שהשם מרמז, אבל אפשר לחשוב על זה כמפה של מזהי מצביעים שונים לשינויים ב-PointerInputChanges, ו-PointerInputChanges הוא סוג ציבורי.

  • זה המופע הראשון.

  • משהו שאולי תשימו לב אליו כשחושבים על מזהי סמן שונים הוא שעכשיו, כשאנחנו מודעים למעקב אחר הזמן, אנחנו יכולים גם לעקוב אחרי מתי מזהי הסמן מגיעים והולכים.

  • יש יותר מאחת.

  • אם יש לכם כמה אצבעות על מסך מגע, יכול להיות שיהיו לכם כמה סמנים בו-זמנית שמופיעים ונעלמים, ואנחנו צריכים לטפל בזה.

  • שוב, אם אתם רוצים להתעמק בפרטים, אתם יכולים לעיין ב-PointerInputChangeEventProducer, שעושה את כל זה, אבל אנחנו נדלג על זה.

  • יש לנו עוד הרבה דברים לעבור עליהם.

  • אז עכשיו יש לנו ייצוג של הקלט ב-Compose, והוא כולל מידע על הזמן.

  • השלב הבא הוא להחליט מה לעשות עם האירוע הזה.

  • לאן לשלוח אותו?

  • וזה מוביל לבדיקת Hit Path.

  • אז בואו נדמיין מצב שבו נשלח MotionEvent, וה-ComposeView הזה ממלא את כל החלון, אולי את כל המסך.

  • אנחנו צריכים לדעת לאן לשלוח אותו.

  • מכיוון ש-Compose הוא רק תצוגה, אבל יש לנו המון דברים בתוך Compose, הרבה צמתים שונים, לחצנים, דברים שאתם יוצרים איתם אינטראקציה, אנחנו צריכים לעשות ב-Compose את כל העבודה של להבין לאן לשלוח אירוע שקורה במיקום ספציפי, ולהבין איזה צומת באמת מעוניין בו.

  • לצמתי הפריסה האלה יש קואורדינטות, גבולות וצורות, אז אנחנו צריכים להבין על איזה מהם, אם בכלל, המשתמש לחץ.

  • אבל גם זו לא התצוגה הכללית ביותר.

  • מסתבר שאנחנו אוהבים לשים דברים אינטראקטיביים בתוך דברים אינטראקטיביים אחרים, למשל כפתור בתוך רשימה.

  • מסתבר שאנחנו עושים את זה הרבה.

  • לכן, המקרה הכללי המלא כאן הוא שאירוע קלט ספציפי יוצר נתיב פגיעה מלא שעובר דרך כל צומת שניתן לקיים איתו אינטראקציה, שנפגע על ידי הקואורדינטות של אירוע הקלט.

  • אז במקרה הזה, יש את שלוש הרמות האלה בהיררכיית ההטמעה.

  • אז יש לנו נתיב היטים, אבל גם זה לא מספיק.

  • זוכרים שאמרתי שיש לנו כמה אירועי קלט של סמן, כמה סמנים שעשויים להיות למטה?

  • אנחנו צריכים עץ מלא של נתיבי היטים.

  • כשמשתמשים בכמה מצביעים, אין רק נתיב לחיצה אחד.

  • אז זה השלב הבא, יצירה ועדכון של עץ נתיב ההיטים הזה.

  • אז זה מבט מפורט יותר על מה שקורה.

  • בצד ימין, יש לנו צמתים שמוצגים באופן חזותי, ובפינה הימנית התחתונה, אפשר לראות את הייצוג ההיררכי, אילו צמתים נמצאים בתוך צמתים אחרים, אילו מהם הם אבות קדמונים או צאצאים זה של זה.

  • כשהסמן יורד, אנחנו עוקבים אחרי נתיב ההקשה, הנתיב המלא בעץ, ואז אנחנו מקבלים נתיב הקשה לסמן הספציפי הזה.

  • נתיב ההיטים הזה הופך לעץ כשמקבלים את המצביע השני.

  • יכול להיות שהיא תצטלב עם חלק מהצמתים של הרשת השנייה, אבל יכול להיות שהיא תקיים אינטראקציה גם עם צמתים אחרים.

  • אז בצד שמאל, זהו עץ נתיב ההיטים שאנחנו מתחזקים עכשיו.

  • מצביעים יכולים להופיע ולהיעלם, אז אנחנו צריכים לתחזק את זה בעצמנו לאורך זמן כדי לוודא שאנחנו מקבלים את הרשימה המלאה של הצמתים שמעוניינים בכל אירוע שמתרחש כרגע.

  • טוב.

  • אז עכשיו יש לנו עץ נתיבי היט שאנחנו מתחזקים לאירועי הקלט האלה, מה שאומר שאנחנו עוברים לשלב הבא.

  • עכשיו אנחנו יודעים לאן לשלוח את אירועי הקלט של המצביע, ואילו צמתים מעוניינים בכל אירוע ספציפי.

  • עכשיו, כשאנחנו יודעים את זה, אנחנו יכולים לבצע טרנספורמציה של הקואורדינטות.

  • מסתבר שזה ממש נחמד אם הצומת מקבל אירועי קלט בתוך מרחב הקואורדינטות המקומי שלו, כך שלא צריך לעשות הרבה מתמטיקה של קואורדינטות בעצמך.

  • אז אחרי שאנחנו יודעים את זה, אנחנו יכולים לקחת את אירוע הקלט של המצביע, לשלב אותו עם הקואורדינטות של הצומת הרלוונטי, ואז זה יוצר סוף סוף את ה-PointerEvent הציבורי שיישלח לכולכם ולמשנה הקלט של המצביע.

  • וזה הדבר האחרון בשלב שלנו, שלב 6, סוף סוף שולחים את האירוע.

  • זהו אירוע קלט של מצביע, והוא נשלח לצומת כדי לעשות איתו מה שרוצים.

  • אנחנו עושים את זה בשלושה שלבים שונים.

  • יש לנו סבב ראשוני, סבב עיקרי וסבב סופי.

  • הסיבה שיש שלושה שלבים שונים היא שהם מאפשרים לצמתים לתקשר זה עם זה ולהעביר מידע על חלקים ספציפיים של אירועי קלט ספציפיים של מצביע, ואז לצרוך חלקים שונים שלהם כחלק מהשליחה.

  • אז המעבר הראשון, המעבר הראשוני, עובר מהאב הקדמון לצאצא.

  • דוגמה למצב שבו זה שימושי: אם צומת פריסה חיצוני כלשהו הוא קונטיינר לגלילה, והוא יודע שהוא התחיל לגלול, והוא רוצה למנוע מכל אירועי המצביע להגיע לצאצאים שלו.

  • כך, בסבב הראשון, הוא יכול לצרוך אותו, ואז הצאצאים לא יקבלו יותר אירועים.

  • המעבר השני הוא המעבר הראשי.

  • וזה היחיד שעובר מצאצא לאב קדמון.

  • כמו שהשם מרמז, זהו המעבר הראשי לביצוע פעולות.

  • לדוגמה, זה המקום שבו צאצא יצרוך אירוע למטה, יתחיל לטפל בקליק, ואז תהיה לו הזדמנות לעשות זאת לפני כל אחד מההורים שלו אם יש לכם רכיבים קליקביליים בתוך רכיבים קליקביליים.

  • והשלב האחרון, השלב הסופי, נראה כמו השלב הראשוני, שבו הוא עובר מהאב הקדמון לצאצא.

  • כאן רכיב צאצא יכול להודיע לרכיב הורה שההורה צרך חלק מאירוע.

  • לכן, אם הילד התחיל לטפל בקליק, אבל ההורה התחיל לגלול, הילד יכול לראות שההורה עשה את זה, ואז הילד יכול לבטל את הקליק שהוא התחיל לטפל בו.

  • אז אלה שלושת הסבבים ואלה ששת השלבים.

  • זה הבסיס לכל מה שקשור למערכת הקלט של המצביע.

  • כל הטיפול בתנועות, הגלילה, הגרירה, הלחיצה וכל קלט מותאם אישית של מצביע מבוססים על הבסיס הזה.

עדכונים במשטח המגע ותנועות עם כמה אצבעות

  • אחרי כל זה, עכשיו אנחנו יכולים לדבר קצת על הפרטים הספציפיים של האופן שבו שיפרנו את התמיכה בהתנהגות של משטח המגע ב-Compose.

  • לכן, לפני גרסה 1.12 של Compose, אירועי קלט של משטח מגע דווחו כסוג סמן של מגע, כי אצבע יוצרת אינטראקציה פיזית עם משטח מגע כדי לשלוט בסמן.

  • עם זאת, המשתמשים מצפים שסוג הקלט הזה יפעל בצורה דומה יותר לעכבר פיזי.

  • לכן, שינינו את סוג המצביע מ-PointerType.Touch ל-PointerType.Mouse.

  • השינוי הזה יאפשר למשתמשים ליהנות מחוויית שימוש עקבית יותר בעכבר ובמשטח המגע בכל רכיב שמתייחס למידע הזה.

  • דוגמה לכך היא ההתנהגות של הסבר קצר: סביר להניח שתרצו שתהיה אותה התנהגות של הסבר קצר במשטח מגע ובעכבר.

  • השינוי הגדול השני שביצענו במשטח המגע קשור לתנועות עם כמה אצבעות שאפשר לבצע במשטח המגע.

  • בעבר, פלטפורמת Android העבירה ל-Compose את הקונספט החכם אבל קצת מפחיד של אצבעות מזויפות.

  • בואו נראה מה זה אומר.

  • כשמשתמשים בשתי אצבעות על משטח מגע כדי להזיז את התצוגה, מערכת Android יוצרת אצבע מזויפת אחת של מסך מגע שיורדת למקום שבו נמצא הסמן.

  • לאחר מכן, כשמזיזים את האצבעות על משטח המגע, האצבע המדומה על מסך המגע זזה במרחק ובכיוון הנכונים כדי לחקות את הפעולה הרצויה, ואז היא משתחררת.

  • לכן, הזזה של שתי אצבעות כלפי מעלה על משטח המגע מתורגמת לאצבע אחת מדומה על מסך המגע, שגם זזה כלפי מעלה, וזה גורם לגלילה כלפי מטה.

  • אם אתם משתמשים בתכונה של גלילה הפוכה במשטח המגע, כמוני, אתם רוצים שהגלילה תהיה בכיוון ההפוך.

  • כדי להשיג את זה, צריך להפוך את הכיוון של האצבע המזויפת.

  • אז עכשיו, שתי אצבעות על משטח המגע שזזות כלפי מעלה מתורגמות לאצבע מזויפת אחת על מסך המגע שזזה כלפי מטה, מה שגורם לגלילה כלפי מעלה, ההפך ממה שהיה לנו קודם.

  • הגדלת קנה המידה פועלת באופן דומה.

  • כשמשתמשים בשתי אצבעות על משטח מגע כדי לשנות את הגודל, Android יוצר שתי אצבעות מדומות במסך המגע שיורדות למטה, ואז מתקרבות או מתרחקות, בהתאם לתנועה, כדי לחקות את ההתנהגות הרצויה של הגדלה או הקטנה של התצוגה.

  • לכן, החל מגרסה 1.12 של Compose להזזה וגרסה 1.13 של Compose לשינוי קנה מידה, אנחנו מיירטים את האצבעות המזויפות האלה.

  • אנחנו לא שולחים אותם כפי שהם מתקבלים מהפלטפורמה, אלא מפרשים אותם מחדש לסוגים חדשים של אירועי סמן: PanStart,‏ PanMove ו-PanEnd להזזה, ו-ScaleStart,‏ ScaleChange ו-ScaleEnd לשינוי קנה מידה.

  • ואנחנו עושים את זה בשלב המרת האירועים מ-Android ל-Compose שדיברתי עליו קודם.

  • לכן, אנחנו ממירים אירועי Fake Finger לסוגים המתאימים שלהם, ואז שולחים את התנועות האלה שמוגדרות בפלטפורמה ל-Hit Path שתואם לסמן.

  • בואו נראה למה זה חשוב.

  • למה אנחנו עושים את זה בכלל?

  • נניח שהתיבה הזו היא תמונה יפה שאנחנו רוצים להגדיל.

  • כשמשתמשים בשתי אצבעות על משטח מגע כדי לשנות את קנה המידה, יכול להיות שאחת משתי האצבעות המדומות תפספס את הרכיב.

  • נתיב ההיט לא יחצה את התמונה, והוא יתחיל מחוץ לגבולות.

  • אז במקום לשנות את הגודל, יכול להיות שהתמונה תיגלל.

  • אם יש עוד רכיב שאפשר לגלול מחוץ לתמונה, יכול להיות שגם הוא יתחיל להיגלל.

  • זה לא מה שהמשתמש רצה.

  • הם רצו להתרחב.

  • אז מה שאנחנו עושים עכשיו, במקום לשלוח את האצבעות המזויפות האלה, אנחנו שולחים את תנועת שינוי קנה המידה רק לאלמנט שמתחת למיקום הסמן, מה שמוביל להתנהגות הנכונה.

  • לא משנה איפה הסמן נמצא או כמה הוא קרוב לקצה של הרכיב שרוצים לשנות את הגודל שלו, עכשיו הגודל ישתנה בצורה נכונה.

  • אז החדשות הטובות לכולכם הן שאירועי הקלט האלה, שפורשו מחדש, משמשים כברירת מחדל ברכיבים כמו scrollable ו-transformable כדי לספק התנהגות נאמנה יותר לקלט של משטח המגע אם אתם משתמשים באבני הבניין הבסיסיות האלה.

  • אם אתם משתמשים ברכיבים שמבוססים עליהם, אתם יכולים פשוט לעדכן את Compose לגרסה העדכנית כדי ליהנות מהשיפורים.

  • אם אתם כותבים משני קלט מותאמים אישית של מצביע, כדאי לכם לבדוק את ממשקי ה-API החדשים האלה ולוודא שאתם תומכים בהם כדי לספק למשתמשים את חוויית השימוש הטובה ביותר בכל מכשירי הקלט השונים האלה.

  • וכמובן, הוספנו גם ממשקי API לבדיקה, כדי שתוכלו לבדוק שהאפליקציה מטפלת בכל סוגי הקלט השונים האלה בצורה נכונה.

  • לכן, performTrackpadInput דומה מאוד לממשקי ה-API הקיימים לבדיקה של עכבר ומגע.

    onNodeWithContentDescription("Photo")
        .performTrackpadInput {
            click(center)
    
            pan(Offset(0f, 100f))
    
            scale(0.9f)
        }
  • בעזרת כל אלה, אתם יכולים לוודא שהאפליקציה פועלת כמו שצריך, לא משנה באיזה מכשיר לקליטת נתונים המשתמש משתמש במכשיר שלכם.

  • אני מקווה שעכשיו אתם מבינים טוב יותר איך אירוע של סמן עובר כל הדרך מהאצבע על לוח המגע ועד לאפליקציה.

  • עכשיו אעביר את רשות הדיבור לג'רמי, שידבר על דרך שונה מאוד שבה משתמשים במשטחי מגע.

מבוא לקלט עקיף

  • תודה.

  • טוב.

  • שלום, אני ג'רמי ווקר.

  • אני עובד הרבה על קלט, וחשבתי שיהיה מעניין לספר לכם על כמה מהדברים החדשים שאנחנו עושים עם קלט ב-Compose ואיך זה משפיע עליכם.

  • ובמיוחד עם סוג הקלט העקיף הזה, שנוצר בגלל קלט חדש שמבוסס על גורם צורה חדש: משקפיים חכמים.

  • אז כשאתם מקבלים קלט ממשקפיים, אתם יכולים לעשות את זה בכמה דרכים: אתם יכולים להסתכל על משהו, אתם יכולים להשתמש בקול שלכם, אתם יכולים להשתמש בתנועות, אבל אני הולך לדבר ספציפית על לוח מגע.

  • הן קצת שונות מאלה שאלכס דיבר עליהן, כי הן נמצאות בצד הראש, הן אנכיות, רחבות מאוד וקצרות מאוד.

  • אז כשחשבנו איך זה יעבוד ב-Compose, אם תדמיינו שאתם מסתכלים על כפתור קטן, אם תזיזו את הסמן כדי לנסות לגעת בו, זו לא תהיה חוויה נהדרת, נכון?

  • אז איך אנחנו הולכים לתמוך בזה?

  • ככה נראה ממשק המשתמש של המשקפיים.

  • הוא מעוצב באופן ייחודי כדי להציג תוכן בצורה חלקה בשדה הראייה שלכם.

  • אבל זה מאוד פשוט, אנחנו מנסים לשמור על פשטות.

  • אם נסתכל על הדוגמה הזו, אפשר לראות שזו רשימה.

  • הוא מתמקד באלמנט מסוים במרכז.

  • מה אנחנו באמת רוצים לעשות?

  • אנחנו רוצים לבחור אותו, או אולי לעבור לאחד הקודם או הבא.

  • אנחנו לא בהכרח צריכים מיקום במסך.

  • האפשרות הזו קצת יותר מסובכת, אבל אפשר לראות שיש בעצם שלוש אפשרויות: לנתק, לבטל את ההשתקה או לכבות את הווידאו.

  • אבל בעצם, אתם רוצים לבחור אותו, או לעבור לאלמנט אחר ולבחור אותו.

  • אז אנחנו חושבים שאנחנו לא באמת צריכים למפות ישירות למיקום על המסך, או במקרה הזה, לעיניים שלכם.

  • אז כן, ככה זה נראה בפועל.

  • לדוגמה, אתם יכולים לראות כאן שאני פשוט עובר בין רכיבים, וזה בממשק המשתמש של המשקפיים, וזה מתאים למה שאנחנו רוצים.

  • אפשר ללחוץ עליו.

  • אז בדקנו את Android כדי לראות מה קיים שם שאנחנו יכולים להשתמש בו כדי לתמוך בזה.

  • ואם אתם אי פעם, אם אתם מקבלים, אוי, סליחה.

  • זה דילוג קדימה.

  • אני רוצה לחזור אחורה.

  • זהו.

  • טוב.

  • אז אם אי פעם השתמשתם באחד מהמכשירים האלה וקישרתם אותו למכשיר Android, אתם יודעים שאפשר לעבור בקלות מרכיבים בממשק המשתמש של מכשיר אחד למכשיר השני.

  • לדוגמה, אם אתם משתמשים בשלט רחוק, אתם יכולים לראות שאתם יכולים אם אתם מדמיינים או שקישרתם אותו ל-Google TV, ויש לכם רשת שלמה של סרטים, אתם יכולים ללחוץ על לחצני החיצים כדי לעבור לסרט שאתם רוצים, ואתם יכולים ללחוץ על הלחצן כדי לבחור אותו, הלחצן'אישור'.

  • אם יש לכם תפריט משחק ואתם משתמשים בבקר הזה, אתם יכולים להשתמש בכפתורי החיצים (D-pad) או בג'ויסטיק כדי לעבור לרכיב בממשק המשתמש, נניח בתפריט, ולבחור אותו.

  • אותו דבר לגבי המקלדת.

  • אז זה די תואם למה שאנחנו רוצים ב-Compose.

  • ושוב, הנה הדברים, כדי שתוכלו לקבל מושג איך זה נראה.

  • ואם נחזור קצת יותר אחורה, היה למעשה סוג של לוח מגע שהותאם לטלוויזיה, והוא השתמש במשהו שנקרא "מגע ניווט".

  • והרעיון הוא שקואורדינטות ה-X וה-Y לא קשורות בכלל למסך.

  • הם משמשים רק לזיהוי תנועות.

  • אז מה שעושים שם זה שאם מחליקים ימינה, אפשר לדמיין שזה כמו ללחוץ על לייק, החץ כדי לעבור.

  • אם מחליקים שמאלה, עוברים שמאלה, למעלה ולמטה.

  • אז זה נותן לנו פחות או יותר את מה שאנחנו רוצים.

  • משתמשים ב-X וב-Y רק למעקב אחר מהירות כדי להבין את הגרירות וההחלקות וכו'. אז זה נותן לנו את רוב מה שאנחנו רוצים כדי להתחיל לעבוד ב-Compose.

  • זה לא מדויק כי זה ממופה לטלוויזיה, אז אנחנו צריכים לוודא שאנחנו לא הולכים בדרך של הטלוויזיה, אנחנו לא רוצים לבלגן את הטלוויזיות אחר כך, אז אנחנו צריכים לוודא שאנחנו מפצלים את זה, אבל זה משהו שנגיע אליו עוד מעט.

  • אבל זה נותן לנו נקודת התחלה.

  • כדי שנוכל להשתמש בסוג הזה של ניווט במגע.

עיצוב API לקלט עקיף ולגורמי צורה חדשים

  • אז… אוקיי,

  • איך ייראה עיצוב ה-API?

  • ב-Compose, הקלט הכי כללי או הכי נפוץ הוא מגע, נכון?

  • אז יש לנו הרבה עבודה בנושא הזה.

  • אז היינו צריכים להבין איך זה קשור לזה.

  • תזכורת: ככה נראה PointerEvent.

  • אפשר לראות כאן שיש את הסוג, שהוא סוג התנועה למטה ולמעלה, ואז יש את PointerInputChange, שהוא פשוט רשימה. אפשר לחשוב על זה כאצבעות.

  • אם יש לכם שלוש אצבעות למטה, יהיו לכם שלושה דברים ברשימה.

  • אז רצינו שזה ייראה בערך ככה.

  • עכשיו, זה לא מודול של ממשק משתמש, שהוא מודול הרבה יותר נמוך מזה שרוב המפתחים משתמשים בו כשהם מתעסקים עם קלט.

  • הם יהיו שכבה מעל שכבת הבסיס.

  • שם נמצאים כל גלאי התנועות שלנו.

  • כי כמפתחים, לא אכפת לכם מכל הקלט הקטן הזה.

  • אתם רק רוצים לדעת אם הייתה גרירה, אם הייתה לחיצה, אם הייתה החלקה, ואז אתם מבצעים את הפעולה שלכם על סמך זה.

  • אז יש לנו קוד שעושה את זה בשבילכם, והוא מופעל על ידי אלה מאחורי הקלעים.

  • אז התחלנו לחשוב, אוקיי, אנחנו צריכים להקל כמה שיותר על הקוד שקיים ב-Foundation כדי שיוכל לתמוך בקלות בסוגים החדשים האלה.

  • אז יצרנו סוג חדש שנראה כמעט אותו דבר.

  • היא פשוט נקראת "עקיפה".

  • ובאופן עקיף, כלומר לא בנגיעה ישירה במסך.

  • כך אתם יודעים שזה עומד לקרות, ולא חושבים על זה מנקודת מבט של מגע.

  • חשבו על כך שאנחנו רק מטפלים בתנועות כאן ומנווטים בין רכיבי ממשק המשתמש.

  • הסוג זהה.

  • יש בה רשימה של אצבעות.

  • במקרה הזה, זה IndirectPointerInputChange.

  • אבל עשינו את זה, וזה עבד מצוין.

  • אבל אז נתקלנו בפריט נוסף שנקרא "ציר תנועה כיווני ראשי".

  • זה נשמע קצת מסובך, אבל זה יהיה הגיוני כשאסביר את זה, כי זה די מעניין איך זה קרה.

  • אז איך זה קרה?

  • אז שוב, יש את לוח המגע הזה בצד הראש, והוא רחב וקצר.

  • אז בשלב הזה, היה לנו רעיון, אוקיי, יש לנו ניווט במגע.

  • אז אנחנו יכולים להחליק שמאלה או ימינה.

  • וזה עובד טוב.

  • אבל אם חושבים על משטחי המגע שקיימים היום, נכון?

  • כשאתם זזים שמאלה או ימינה, אתם זזים שמאלה או ימינה בלוח המגע עם התוכן של ממשק המשתמש.

  • אם תעברו למעלה או למטה, תגללו ברשימה למעלה או למטה.

  • אז אם מנסים לעשות את זה עם משקפיים, זה לא כל כך נורא, נכון?

  • אבל אם אתם עושים את זה, החוויה לא כל כך טובה.

  • אז מה שגילינו זה שהתנועה הכי טבעית היא כמובן נגיעות, אבל גם החלקה לאורך הצד של המשקפיים.

  • אז ערכנו כמה מחקרים עם משתמשים, והם באמת מצפים שכאשר מחליקים אופקית, התוכן לא זז אופקית, אלא אנכית.

  • אפשר לחשוב על זה ככה: אם מנסים את זה בראש, כמו כשמחליקים כאן, אם יש רשימה לפניכם, כמו להתקדם, אתם יכולים להתקדם ברשימה או לחזור אחורה ברשימה.

  • אז אנחנו לוקחים קלט דו-ממדי ומנסים לחשוב עליו בצורה חד-ממדית.

  • וכאן נכנס לתמונה הרעיון של ציר התנועה הראשי – אפשר להזיז את האצבע בצורה אופקית והתנועה תהיה אנכית.

  • אז אפשר להגדיר את זה במכשיר עצמו.

  • אפשר להגיד, "היי, זה לא משטח מגע רגיל".

  • אני רוצה שהכיוון הראשי יהיה אופקי, וזה בעצם הציר היחיד שאני רוצה לנוע לאורכו.

  • אז משם זה הגיע.

  • אוקיי.

  • אז אנחנו מרגישים די טוב לגבי זה.

  • ‫Compose מטפל בהכול.

  • ויש לכם את ההטמעה האנכית הנחמדה הזו.

  • אתם יכולים לחשוב על זה.

  • אז איך מיפינו את זה? עכשיו, עם ההגדרה האופקית, יש לנו רק קדימה ואחורה.

  • אין לנו למעלה, למטה, שמאלה, ימינה.

  • יש לנו רק קדימה ואחורה.

  • ואז, אנחנו יכולים למפות את זה אם אתם מכירים את המיקוד עם הבא והקודם.

  • עכשיו אנחנו פשוט עוברים לתמונה הבאה או הקודמת.

  • זה עובד מצוין בסרטונים אנכיים.

  • והוא גם נחמד יותר.

  • היא פועלת עם פריסה אופקית, נכון?

  • כי יש לכם פשוט הרבה פריטים ממוקדים.

  • פשוט עוברים לשקף הבא או הקודם, וזה עובד בשני המקרים.

  • אז זה מקרה עם Compose שבו הגדרנו והבנו את כל זה ב-Compose, אבל היה נחמד מאוד אם המכשיר עצמו היה יכול לומר, כי הוא יודע אם יש לו צורה מוזרה, והוא יודע אם הוא רוצה רק את הציר הראשי הזה, אבל אין לו דרך להגדיר את זה.

  • אז עכשיו היינו צריכים להחזיר את הקונספט הזה לפלטפורמה ולהגיד, אוקיי, אנחנו צריכים להגדיר את זה בפלטפורמה כדי שנוכל ליצור סטנדרטיזציה, והמכשירים האלה יוכלו לציין את זה בעצמם, ואז Compose יוכל לתמוך בזה באופן מלא עד הסוף.

  • אז יש מקרה שבו עבדנו ב-Compose, התקדמנו ב-Compose, חזרנו אחורה והבאנו את זה איתנו.

  • וזה בדיוק מה שעשינו.

  • יצרנו את ה-API החדש הזה כדי שתוכלו לציין את זה, או שהמכשיר יוכל לציין את זה.

    InputDevice.ViewBehavior
    .getPrimaryDirectionalMotionAxis()
  • ואם אתם זוכרים, קודם, כשדיברתי על קלט מגע, או על ניווט במגע, דיברתי על כך שהוא נועד בעיקר לטלוויזיות.

  • לא רצינו לשבש את ההטמעות האלה.

  • לכן, הוספנו עוד שדה כדי לציין את הסוג החדש הזה של קלט זכוכית.

  • אוקיי.

  • אז מה זה אומר מבחינתכם?

  • ובכן, זו גרסה ספציפית יותר של מה שאתם מקבלים כגלאי תנועות.

    // Glimmer Library
    Box(
        modifier =
            Modifier.fillMaxSize()
                .onIndirectPointerGesture(
                    enabled = true,
                    onSwipeForward = { /* onSwipeForward */ },
                    onSwipeBackward = { /* onSwipeBackward */ },
                    onClick = { /* onClick */ },
                )
                .focusTarget()
    ) {
        // App()
  • אם אתם משתמשים ב-Foundation, זה פשוט יעבוד עם גלאי התנועות.

  • אנחנו עשינו את העבודה בשבילכם, אז אתם לא צריכים לעשות כלום.

  • אבל אם אתם רוצים ליצור אפליקציה למשקפיים, אתם יכולים להשתמש בספריית Glimmer שלנו, ואז יש לנו גלאי תנועות מיוחד, שנותן לכם רק את, אתם יודעים, כדי שלא תצטרכו לשלב אותם, אתם יכולים להשתמש רק בזה, לקבל את הקליק, את הפריים קדימה ואת הפריים אחורה.

בדיקת קלט עקיף של מצביע

  • אוקיי.

  • אז דיברנו על אופן הפעולה של ה-API בפלטפורמה וב-Compose.

  • מה לגבי בדיקות?

  • ככה נראית בדיקת המגע.

    @Test
    clickTest {
    
    rule.onNodeWithTag(defaultTag)
    .performTouchInput
    { click(center) }
    
    }
  • פשוט אומרים performTouchInput ואז מבצעים את התנועה.

  • יש הרבה תנועות שונות, ואנחנו בונים את כל האירועים הקטנים כדי שהתנועה תתבצע.

  • איך זה עובד ברמה הכללית?

  • יש את מודול ממשק המשתמש של Compose, יש את מודול בדיקת ממשק המשתמש והם מתקשרים, לפחות מבחינת קלט, באמצעות 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 היא ערכת הכלים הראשית לממשק המשתמש, ונמשיך לחקור ולתמוך בסוגי קלט חדשים, גם בפלטפורמה וגם בערכת הכלים, כמו שעשיתי כאן.

  • וכמובן, כדי לבדוק ממשקי API, אני רוצה לשמור על ממשק ופרדיגמה מוכרים, כדי שיהיה לכם קל ככל האפשר להעביר ולבדוק את הסוגים החדשים האלה.

  • תודה, מקווה שעזרתי.

קוד

קטעי קוד

קטע קוד ב-Kotlin בדקה 15:47 בסרטון "עדכונים של משטח המגע ותנועות עם כמה אצבעות"
onNodeWithContentDescription("Photo")
    .performTrackpadInput {
        click(center)

        pan(Offset(0f, 100f))

        scale(0.9f)
    }
קטע קוד ב-Kotlin בדקה 25:08 בסרטון "API design for indirect input and new form factors"
InputDevice.ViewBehavior
.getPrimaryDirectionalMotionAxis()
קטע קוד ב-Kotlin בדקה 25:32 בסרטון "API design for indirect input and new form factors"
// 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 בסרטון Testing indirect pointer input
@Test
clickTest {

rule.onNodeWithTag(defaultTag)
// .performTouchInput
.performIndirectPointerInput (
IndirectPointerEventPrimaryDirectionalMotionAxis.X,
inputDeviceSize,
)
{ click(center) }
}
קטע קוד ב-Kotlin בדקה 27:23 בסרטון "בדיקת קלט עקיף של מצביע"
@Test
clickTest {

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

}