آسان‌تر کردن دسترسی به نماهای سفارشی (نماها)

مفاهیم و پیاده‌سازی Jetpack Compose

اگر برنامه‌تان به مؤلفه نمای سفارشی نیاز دارد، باید نما را دسترس‌پذیرتر کنید. مراحل زیر می‌تواند دسترس‌پذیری نمای سفارشی شما را بهبود بخشد، همان‌طور که در این صفحه شرح داده شده است:

  • کلیک‌های کنترل‌کننده جهت‌دار را مدیریت کنید.
  • روش‌های میانای برنامه‌سازی کاربردی دسترس‌پذیری را پیاده‌سازی کنید.
  • ارسال AccessibilityEvent شیء مختص نمای سفارشی شما.
  • AccessibilityEvent و AccessibilityNodeInfo را برای نمای خود پر کنید.

مدیریت کلیک‌های کنترل‌کننده جهت‌دار

در اکثر دستگاه‌ها، کلیک کردن روی یک نما بااستفاده از کنترل‌کننده جهت‌دار، KeyEvent با KEYCODE_DPAD_CENTER به نمایی که درحال‌حاضر در کانون توجه است ارسال می‌کند. همه نماهای استاندارد Android KEYCODE_DPAD_CENTER را به‌درستی مدیریت می‌کنند. هنگام ساختن کنترل View سفارشی، مطمئن شوید این رویداد همان تأثیر تک‌ضرب زدن روی نما در صفحه لمسی را دارد.

کنترل سفارشی شما باید با رویداد KEYCODE_ENTER همانند KEYCODE_DPAD_CENTER رفتار کند. این کار تعامل با صفحه‌کلید کامل را برای کاربران آسان‌تر می‌کند.

پیاده‌سازی روش‌های میانای برنامه‌سازی کاربردی دسترس‌پذیری

رویدادهای دسترس‌پذیری پیام‌هایی درباره تعاملات کاربران با مؤلفه‌های میانای دیداری برنامه شما هستند. این پیام‌ها توسط سرویس‌های دسترس‌پذیری مدیریت می‌شوند که از اطلاعات موجود در این رویدادها برای تولید بازخورد و پیام‌واره‌های تکمیلی استفاده می‌کنند. روش‌های دسترس‌پذیری بخشی از کلاس‌های View و View.AccessibilityDelegate هستند. روش‌ها به‌صورت زیر است:

dispatchPopulateAccessibilityEvent()
وقتی نمای سفارشی شما رویداد دسترس‌پذیری ایجاد می‌کند، سیستم این روش را فراخوانی می‌کند. پیاده‌سازی پیش‌فرض این روش، onPopulateAccessibilityEvent() را برای این نما و سپس روش dispatchPopulateAccessibilityEvent() را برای هر فرزند این نما فراخوانی می‌کند.
onInitializeAccessibilityEvent()
سیستم این روش را برای دریافت اطلاعات بیشتر درباره وضعیت نما فراتر از محتوای نوشتاری فراخوانی می‌کند. اگر نمای سفارشی شما کنترل تعاملی فراتر از TextView یا Button ساده ارائه می‌دهد، این روش را لغو کنید و اطلاعات اضافی درباره نمای خود را (مانند نوع فیلد گذرواژه، نوع چارگوش انتخاب، یا وضعیت‌هایی که تعامل یا بازخورد کاربر را در رویداد ارائه می‌دهند) بااستفاده از این روش تنظیم کنید. اگر این روش را لغو می‌کنید، پیاده‌سازی super آن را فراخوانی کنید و فقط ویژگی‌هایی را اصلاح کنید که توسط کلاس super تنظیم نشده‌اند.
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() را برای گزارش این رویداد نشان می‌دهد.

کاتلین

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

جاوا

@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 که توسط این روش پر شده‌اند استفاده می‌کنند تا سلسله‌مراتب نمایشی را که پس‌از دریافت رویداد دسترس‌پذیری ایجاد می‌شود بررسی کنند و بازخورد مناسب را به کاربران ارائه دهند.

نمونه کد زیر نشان می‌دهد که چگونه این سه روش را در نمای خود لغو کنید:

کاتلین

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
    }
}

جاوا

@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);
    }
}

می‌توانید این روش‌ها را مستقیماً در کلاس نمای سفارشی‌تان پیاده‌سازی کنید.

ارائه بافت دسترس‌پذیری سفارشی‌شده

سرویس‌های دسترس‌پذیری می‌توانند سلسله‌مراتب نمای حاوی مؤلفه میانای کاربر را که رویداد دسترس‌پذیری تولید می‌کند بازرسی کنند. این کار به سرویس‌های دسترس‌پذیری امکان می‌دهد اطلاعات زمینه‌ای غنی‌تری برای کمک به کاربران ارائه دهند.

مواردی وجود دارد که سرویس‌های دسترس‌پذیری نمی‌توانند اطلاعات کافی را از سلسله‌مراتب نما دریافت کنند. نمونه‌ای از این مورد، کنترل واسط سفارشی است که دو یا چند ناحیه کلیک‌کردنی جداگانه دارد، مثل کنترل تقویم. در این مورد، سرویس‌ها نمی‌توانند اطلاعات کافی دریافت کنند زیرا زیربخش‌های کلیک‌کردنی بخشی از سلسله‌مراتب نمای نیستند.

شکل ۱. نمای تقویم سفارشی با عناصر روز قابل‌انتخاب.

در مثال شکل ۱، کل تقویم به‌عنوان یک نمای واحد پیاده‌سازی شده است، بنابراین سرویس‌های دسترس‌پذیری اطلاعات کافی درباره محتوای نما و انتخاب کاربر در نما دریافت نمی‌کنند، مگراینکه توسعه‌دهنده اطلاعات بیشتری ارائه دهد. برای مثال، اگر کاربری روی روزی با برچسب ۱۷ کلیک کند، چارچوب دسترس‌پذیری فقط اطلاعات توضیحی را برای کل کنترل تقویم دریافت می‌کند. در این حالت، سرویس دسترس‌پذیری TalkBack «تقویم» یا «تقویم آوریل» را اعلام می‌کند و کاربر نمی‌داند کدام روز انتخاب شده است.

برای ارائه اطلاعات بافت کافی برای خدمات دسترس‌پذیری در شرایطی مانند این، چارچوب راهی برای مشخص کردن سلسله‌مراتب نمای مجازی ارائه می‌دهد. سلسله‌مراتب نمای مجازی روشی برای توسعه‌دهندگان نرم‌افزار است تا سلسله‌مراتب نمای مکمل را برای سرویس‌های دسترس‌پذیری ارائه دهند که با اطلاعات روی صفحه مطابقت بیشتری دارد. این رویکرد به سرویس‌های دسترس‌پذیری امکان می‌دهد اطلاعات بافت مفیدتری به کاربران ارائه دهند.

موقعیت دیگری که ممکن است به سلسله‌مراتب نمای مجازی نیاز باشد، واسط کاربری است که حاوی مجموعه‌ای از کنترل‌های View با عملکردهای بسیار مرتبط است، که در آن کنش روی یک کنترل بر محتوای یک یا چند عنصر تأثیر می‌گذارد—مانند انتخابگر عدد با دکمه‌های بالا و پایین جداگانه. در این حالت، سرویس‌های دسترس‌پذیری نمی‌توانند اطلاعات کافی دریافت کنند زیرا کنشی در یک کنترل، محتوای کنترل دیگری را تغییر می‌دهد و ممکن است رابطه این کنترل‌ها برای سرویس آشکار نباشد.

برای مدیریت این وضعیت، کنترل‌های مرتبط را با نمای دربرگیرنده گروه‌بندی کنید و سلسله‌مراتب نمای مجازی از این دربرگیرنده ارائه دهید تا اطلاعات و رفتار ارائه‌شده توسط کنترل‌ها را به‌وضوح نشان دهد.

برای ارائه سلسله‌مراتب نمای مجازی برای یک نما، روش getAccessibilityNodeProvider() را در نما یا گروه نمای سفارشی خود لغو کنید و پیاده‌سازی AccessibilityNodeProvider را برگردانید. می‌توانید بااستفاده از «کتابخانه پشتیبانی» با روش ViewCompat.getAccessibilityNodeProvider() یک سلسله‌مراتب نمای مجازی پیاده‌سازی کنید و پیاده‌سازی را با AccessibilityNodeProviderCompat ارائه دهید.

برای ساده کردن وظیفه ارائه اطلاعات به سرویس‌های دسترس‌پذیری و مدیریت تمرکز دسترس‌پذیری، می‌توانید به‌جای آن ExploreByTouchHelper را پیاده‌سازی کنید. این کلاس یک AccessibilityNodeProviderCompat ارائه می‌دهد و می‌تواند با فراخوانی setAccessibilityDelegate به‌عنوان AccessibilityDelegateCompat یک نما پیوست شود. برای نمونه، ExploreByTouchHelperActivity را ببینید. ‫ExploreByTouchHelper همچنین توسط ابزاره‌های چارچوب مانند CalendarView، ازطریق نمای فرزند SimpleMonthView استفاده می‌شود.

مدیریت رویدادهای لمسی سفارشی

کنترل‌های نمای سفارشی ممکن است به رفتار رویداد لمسی غیراستاندارد نیاز داشته باشند، همان‌طور که در مثال‌های زیر نشان داده شده است.

تعریف کردن کنش‌های مبتنی بر کلیک

اگر ابزاره شما از میانای OnClickListener یا OnLongClickListener استفاده می‌کند، سیستم کنش‌های ACTION_CLICK و ACTION_LONG_CLICK را برای شما مدیریت می‌کند. اگر برنامه‌تان از ابزاره سفارشی‌سازی‌شده‌تری استفاده می‌کند که به میانای OnTouchListener متکی است، گرداننده‌های سفارشی برای کنش‌های دسترس‌پذیری مبتنی بر کلیک تعریف کنید. برای انجام این کار، replaceAccessibilityAction() را برای هر کنش فراخوانی کنید، همان‌طور که در تکه‌کد زیر نشان داده شده است:

کاتلین

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()
    }
}

جاوا

@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() شما را فراخوانی کند. نمونه کد زیر این الگو را نشان می‌دهد.

کاتلین

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
    }
}

جاوا

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() برای تولید رویداد دسترس‌پذیری و ارائه نقطه ورودی برای سرویس‌های دسترس‌پذیری انجام می‌شود تا به‌جای کاربری که رویداد کلیک سفارشی را انجام می‌دهد عمل کند.