تسهيل الوصول إلى طرق العرض المخصّصة

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

  • تعامل مع نقرات وحدة التحكّم الاتجاهات.
  • تنفيذ طرق واجهة برمجة التطبيقات Accessibility 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 عن "التقويم" أو "تقويم نيسان (أبريل)"، ولا يعرف المستخدم اليوم الذي تم اختياره.

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

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

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

لتوفير تسلسل هرمي افتراضي لطريقة العرض، يمكنك إلغاء طريقة getAccessibilityNodeProvider() في طريقة العرض المخصّصة أو مجموعة المشاهدة المخصّصة وعرض تنفيذ AccessibilityNodeProvider. يمكنك تنفيذ تسلسل هرمي لطريقة العرض الافتراضية باستخدام Support Library مع طريقة ViewCompat.getAccessibilityNodeProvider() وتقديم عملية تنفيذ من خلال AccessibilityNodeProviderCompat.

لتبسيط مهمة توفير المعلومات لخدمات تسهيل الاستخدام وإدارة التركيز على تسهيل الاستخدام، يمكنك بدلاً من ذلك استخدام ExploreByTouchHelper. وهو يوفر AccessibilityNodeProviderCompat ويمكن إرفاقه كمشاهدة AccessibilityDelegateCompat من خلال طلب الرقم setAccessibilityDelegate. على سبيل المثال، يمكنك مراجعة ExploreByTouchHelperActivity. يتم أيضًا استخدام ExploreByTouchHelper من خلال التطبيقات المصغّرة لأُطر العمل، مثل CalendarView، من خلال عرضها الفرعي SimpleMonthView.

التعامل مع أحداث اللمس المخصّصة

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

تحديد الإجراءات المستندة إلى النقرات

إذا كانت الأداة تستخدم الواجهة OnClickListener أو OnLongClickListener، سيعالج النظام الإجراءَين ACTION_CLICK و ACTION_LONG_CLICK نيابةً عنك. إذا كان تطبيقك يستخدم أداة أكثر تخصيصًا تعتمد على واجهة OnTouchListener، حدِّد معالجات مخصّصة لإجراءات تسهيل الاستخدام المستندة إلى النقر. لإجراء ذلك، عليك استدعاء الإجراء 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) لاكتشاف حدثَي 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() لإنشاء حدث تسهيل الاستخدام وتوفير نقطة دخول لخدمات تسهيل الاستخدام للتصرف نيابةً عن مستخدم ينفّذ حدث النقر المخصّص.