שיפור הנגישות של תצוגות בהתאמה אישית

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

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

טיפול בקליקים בבקר כיווני

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

הפקד המותאם אישית חייב לטפל KEYCODE_ENTER לאירוע זהה ל-KEYCODE_DPAD_CENTER. כך מתאפשרת אינטראקציה עם מקלדת מלאה למשתמשים.

הטמעת שיטות של Accessibility API

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

dispatchPopulateAccessibilityEvent()
המערכת מפעילה את השיטה הזו כשתצוגה בהתאמה אישית יוצרת אירוע נגישות. ברירת המחדל הטמעת השיטה הזו מפעילה את onPopulateAccessibilityEvent() לתצוגה הזו ואז את השיטה dispatchPopulateAccessibilityEvent() לכל צאצא צפייה.
onInitializeAccessibilityEvent()
המערכת קוראת לשיטה הזו כדי לקבל מידע נוסף על מצב התצוגה מעבר ל- תוכן הטקסט. אם התצוגה המותאמת אישית מספקת שליטה אינטראקטיבית מעבר TextView או Button, שינוי השיטה הזו ומגדירים מידע נוסף לגבי התצוגה שלכם, כגון סוג שדה סיסמה, תיבת סימון. או מצבים שמספקים אינטראקציה עם משתמש או משוב לגבי האירוע – באמצעות . אם משנים את השיטה הזו, צריך לבצע קריאה לסופר-הטמעה שלה ולשנות רק את המאפיינים שלא מוגדרים על ידי מחלקת העל.
onInitializeAccessibilityNodeInfo()
השיטה הזו מספקת שירותי נגישות עם מידע על מצב התצוגה. להטמעה של View שמוגדרת כברירת מחדל יש קבוצה רגילה של מאפייני תצוגה מפורטת, אבל אם תצוגה מותאמת אישית מספקת שליטה אינטראקטיבית מעבר ל-TextView או Button, שינוי השיטה הזו והגדרת המידע הנוסף לגבי התצוגה באובייקט AccessibilityNodeInfo שמטופל באמצעות השיטה הזו.
onPopulateAccessibilityEvent()
השיטה הזו מגדירה את הנחיית הטקסט הנאמרת של AccessibilityEvent עבור צפייה. היא נקראת גם אם התצוגה היא צאצא של תצוגה שיוצרת נגישות אירוע.
onRequestSendAccessibilityEvent()
המערכת מפעילה את השיטה הזו כשבתצוגה המפורטת שלכם יוצרת AccessibilityEvent. השלב הזה מאפשר לתצוגה של ההורה לתקן את הנגישות אירוע עם מידע נוסף. יש ליישם את השיטה הזו רק אם התצוגה המפורטת המותאמת אישית יכולה ואם תצוגת ההורה יכולה לספק מידע על ההקשר לנגישות, אירוע שמועיל לשירותי הנגישות.
sendAccessibilityEvent()
המערכת מפעילה את השיטה הזו כשמשתמש מבצע פעולה בתצוגה מפורטת. האירוע מסווג עם סוג של פעולת משתמש, למשל TYPE_VIEW_CLICKED. באופן כללי, צריך לשלוח AccessibilityEvent בכל פעם שהתוכן של התצוגה המותאמת אישית משתנה.
sendAccessibilityEventUnchecked()
השיטה הזו משמשת כאשר קוד השיחה צריך לשלוט ישירות בבדיקה של מופעלת במכשיר נגישות (AccessibilityManager.isEnabled()). אם מטמיעים את השיטה הזו, צריך לבצע את הקריאה כאילו הנגישות מופעלת, ללא קשר בהגדרת המערכת. בדרך כלל אין צורך ליישם את השיטה הזו לתצוגה מפורטת מותאמת אישית.

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

לכל הפחות, צריך להטמיע את שיטות הנגישות הבאות עבור המחלקה של התצוגה המפורטת בהתאמה אישית:

  • dispatchPopulateAccessibilityEvent()
  • onInitializeAccessibilityEvent()
  • onInitializeAccessibilityNodeInfo()
  • onPopulateAccessibilityEvent()

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

שליחת אירועי נגישות

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

באופן כללי, צריך לשלוח AccessibilityEvent בכל פעם שהתוכן של הצגת השינויים. לדוגמה, אם אתם מטמיעים פס הזזה בהתאמה אישית שמאפשר למשתמש לבחור בערך מספרי באמצעות הקשה על מקש החץ שמאלה או על מקש החץ ימינה, בתצוגה המותאמת אישית חייב להופיע אירוע של TYPE_VIEW_TEXT_CHANGED בכל פעם שהערך של פס ההזזה משתנה. דוגמת הקוד הבאה מדגימה את השימוש שיטה אחת (sendAccessibilityEvent()) לדיווח על האירוע הזה.

Kotlin

override fun onKeyUp(keyCode: Int, event: KeyEvent): Boolean {
    return when(keyCode) {
        KeyEvent.KEYCODE_DPAD_LEFT -> {
            currentValue--
            sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED)
            true
        }
        ...
    }
}

Java

@Override
public boolean onKeyUp (int keyCode, KeyEvent event) {
    if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
        currentValue--;
        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
        return true;
    }
    ...
}

אכלוס אירועי נגישות

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

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

משתמשים ב onPopulateAccessibilityEvent() וגם onInitializeAccessibilityEvent() שיטות לאכלוס או לשינוי של המידע ב-AccessibilityEvent. משתמשים ב שיטה onPopulateAccessibilityEvent() ספציפית להוספה או לשינוי של טקסט תוכן האירוע, שהופך להנחיות קוליות על ידי שירותי נגישות כמו TalkBack. צריך להשתמש בשיטה onInitializeAccessibilityEvent() לאכלוס נוסף מידע על האירוע, כגון מצב הבחירה של התצוגה.

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

הקוד לדוגמה הבא מראה איך לעקוף את שלוש השיטות האלה בתצוגה:

Kotlin

override fun onPopulateAccessibilityEvent(event: AccessibilityEvent?) {
    super.onPopulateAccessibilityEvent(event)
    // Call the super implementation to populate its text for the
    // event. Then, add text not present in a super class.
    // You typically only need to add the text for the custom view.
    if (text?.isNotEmpty() == true) {
        event?.text?.add(text)
    }
}

override fun onInitializeAccessibilityEvent(event: AccessibilityEvent?) {
    super.onInitializeAccessibilityEvent(event)
    // Call the super implementation to let super classes
    // set appropriate event properties. Then, add the new checked
    // property that is not supported by a super class.
    event?.isChecked = isChecked()
}

override fun onInitializeAccessibilityNodeInfo(info: AccessibilityNodeInfo?) {
    super.onInitializeAccessibilityNodeInfo(info)
    // Call the super implementation to let super classes set
    // appropriate info properties. Then, add the checkable and checked
    // properties that are not supported by a super class.
    info?.isCheckable = true
    info?.isChecked = isChecked()
    // You typically only need to add the text for the custom view.
    if (text?.isNotEmpty() == true) {
        info?.text = text
    }
}

Java

@Override
public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
    super.onPopulateAccessibilityEvent(event);
    // Call the super implementation to populate its text for the
    // event. Then, add the text not present in a super class.
    // You typically only need to add the text for the custom view.
    CharSequence text = getText();
    if (!TextUtils.isEmpty(text)) {
        event.getText().add(text);
    }
}

@Override
public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
    super.onInitializeAccessibilityEvent(event);
    // Call the super implementation to let super classes
    // set appropriate event properties. Then, add the new checked
    // property that is not supported by a super class.
    event.setChecked(isChecked());
}

@Override
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
    super.onInitializeAccessibilityNodeInfo(info);
    // Call the super implementation to let super classes set
    // appropriate info properties. Then, add the checkable and checked
    // properties that are not supported by a super class.
    info.setCheckable(true);
    info.setChecked(isChecked());
    // You typically only need to add the text for the custom view.
    CharSequence text = getText();
    if (!TextUtils.isEmpty(text)) {
        info.setText(text);
    }
}

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

מתן הקשר מותאם אישית לנגישות

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

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

איור 1. תצוגת יומן מותאמת אישית עם רכיבי יום לבחירה.

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

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

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

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

כדי לספק היררכיית תצוגה וירטואלית לתצוגה מפורטת, מבטלים את getAccessibilityNodeProvider() בתצוגה המותאמת אישית או בקבוצת התצוגה המפורטת, ולהחזיר יישום של AccessibilityNodeProvider. אפשר להשתמש בספריית התמיכה כדי להטמיע היררכיית תצוגה וירטואלית, ViewCompat.getAccessibilityNodeProvider() ונספק הטמעה באמצעות AccessibilityNodeProviderCompat.

כדי לפשט את המשימה של מתן מידע לשירותי נגישות וניהול את התמקדות בנגישות, אפשר במקום זאת ExploreByTouchHelper היא מספקת AccessibilityNodeProviderCompat וניתן לצרף אותה כתצוגה מפורטת AccessibilityDelegateCompat בהתקשרות setAccessibilityDelegate. לדוגמה, אפשר להיכנס אל ExploreByTouchHelperActivity המאפיין ExploreByTouchHelper משמש גם בווידג'טים של framework כמו CalendarView, דרך תצוגת צאצא SimpleMonthView.

טיפול באירועי מגע בהתאמה אישית

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

הגדרת פעולות מבוססות-קליקים

אם בווידג'ט נעשה שימוש OnClickListener או OnLongClickListener הוא יטפל ACTION_CLICK וגם ACTION_LONG_CLICK פעולות מסוימות. אם האפליקציה שלך משתמשת בווידג'ט מותאם אישית המסתמך על בממשק של OnTouchListener, להגדיר handlers מותאמים אישית לפעולות הנגישות שמבוססות על קליקים. כדי לעשות זאת, replaceAccessibilityAction() לכל פעולה, כפי שמוצג בקטע הקוד הבא:

Kotlin

override fun onCreate(savedInstanceState: Bundle?) {
    ...

    // Assumes that the widget is designed to select text when tapped, and selects
    // all text when tapped and held. In its strings.xml file, this app sets
    // "select" to "Select" and "select_all" to "Select all".
    ViewCompat.replaceAccessibilityAction(
        binding.textSelectWidget,
        ACTION_CLICK,
        getString(R.string.select)
    ) { view, commandArguments ->
        selectText()
    }

    ViewCompat.replaceAccessibilityAction(
        binding.textSelectWidget,
        ACTION_LONG_CLICK,
        getString(R.string.select_all)
    ) { view, commandArguments ->
        selectAllText()
    }
}

Java

@Override
protected void onCreate(Bundle savedInstanceState) {
    ...

    // Assumes that the widget is designed to select text when tapped, and select
    // all text when tapped and held. In its strings.xml file, this app sets
    // "select" to "Select" and "select_all" to "Select all".
    ViewCompat.replaceAccessibilityAction(
            binding.textSelectWidget,
            ACTION_CLICK,
            getString(R.string.select),
            (view, commandArguments) -> selectText());

    ViewCompat.replaceAccessibilityAction(
            binding.textSelectWidget,
            ACTION_LONG_CLICK,
            getString(R.string.select_all),
            (view, commandArguments) -> selectAllText());
}

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

לפקד בהתאמה אישית יש אפשרות להשתמש בonTouchEvent(MotionEvent) שיטת ה-listener לזיהוי ACTION_DOWN והקבוצה ACTION_UP אירועים ו- שיפעיל אירוע קליק מיוחד. כדי לשמור על תאימות לשירותי נגישות, הקוד מטפל באירוע קליק מותאם אישית זה חייב לבצע את הפעולות הבאות:

  1. צריך ליצור AccessibilityEvent מתאים לפעולת הקליק המפוענחת.
  2. יש להפעיל את שירותי הנגישות כדי לבצע את פעולת הקליק המותאמת אישית עבור משתמשים שלא יכולים להשתמש במסך מגע.

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

Kotlin

class CustomTouchView(context: Context) : View(context) {

    var downTouch = false

    override fun onTouchEvent(event: MotionEvent): Boolean {
        super.onTouchEvent(event)

        // Listening for the down and up touch events.
        return when (event.action) {
            MotionEvent.ACTION_DOWN -> {
                downTouch = true
                true
            }

            MotionEvent.ACTION_UP -> if (downTouch) {
                downTouch = false
                performClick() // Call this method to handle the response and
                // enable accessibility services to
                // perform this action for a user who can't
                // tap the touchscreen.
                true
            } else {
                false
            }

            else -> false  // Return false for other touch events.
        }
    }

    override fun performClick(): Boolean {
        // Calls the super implementation, which generates an AccessibilityEvent
        // and calls the onClick() listener on the view, if any.
        super.performClick()

        // Handle the action for the custom click here.

        return true
    }
}

Java

class CustomTouchView extends View {

    public CustomTouchView(Context context) {
        super(context);
    }

    boolean downTouch = false;

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        super.onTouchEvent(event);

        // Listening for the down and up touch events
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                downTouch = true;
                return true;

            case MotionEvent.ACTION_UP:
                if (downTouch) {
                    downTouch = false;
                    performClick(); // Call this method to handle the response and
                                    // enable accessibility services to
                                    // perform this action for a user who can't
                                    // tap the touchscreen.
                    return true;
                }
        }
        return false; // Return false for other touch events.
    }

    @Override
    public boolean performClick() {
        // Calls the super implementation, which generates an AccessibilityEvent
        // and calls the onClick() listener on the view, if any.
        super.performClick();

        // Handle the action for the custom click here.

        return true;
    }
}

הדפוס הקודם עוזר לוודא שאירוע הקליק בהתאמה אישית תואם לנגישות באמצעות השיטה performClick() כדי ליצור אירוע נגישות לספק נקודת כניסה לשירותי הנגישות לפעול בשם המשתמש שמבצע את ההגדרה אירוע מסוג קליק.