اصول بهبود دسترس‌پذیری برنامه (نماها)

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

برای کمک به کاربرانی که نیازهای دسترس‌پذیری دارند، چارچوب Android به شما امکان می‌دهد سرویس دسترس‌پذیری‌ای ایجاد کنید که بتواند محتوای برنامه‌ها را به کاربران ارائه دهد و همچنین ازجانب آن‌ها با برنامه‌ها کار کند.

Android چندین سرویس دسترس‌پذیری سیستم ارائه می‌دهد، ازجمله:

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

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

عناصر برچسب

مهم است که برای هر عنصر تعاملی میانای کاربر در برنامه‌تان، برچسب‌های مفید و توصیفی به کاربران ارائه دهید. هر برچسب باید معنی و هدف عنصر خاصی را توضیح دهد. صفحه‌خوان‌هایی مثل TalkBack می‌توانند این برچسب‌ها را برای کاربران بخوانند.

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

عناصر قابل‌ویرایش

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

<!-- The hint text for en-US locale would be
     "Apartment, suite, or building". -->
<EditText
   android:id="@+id/addressLine2"
   android:hint="@string/aptSuiteBuilding" ... />

در این شرایط، مشخصه android:labelFor شیء View باید روی شناسه عنصر EditText تنظیم شود. برای جزئیات بیشتر، بخش زیر را ببینید.

جفت عنصرهایی که یکی دیگری را توصیف می‌کند

معمولاً عنصر EditText دارای شیء View متناظری است که آنچه را کاربران باید در عنصر EditText وارد کنند شرح می‌دهد. می‌توانید این رابطه را با تنظیم مشخصه android:labelFor شیء View نشان دهید.

نمونه‌ای از برچسب‌گذاری چنین جفت عنصری در گزیده زیر نشان داده شده است:

<!-- Label text for en-US locale would be "Username:" -->
<TextView
   android:id="@+id/usernameLabel" ...
   android:text="@string/username"
   android:labelFor="@+id/usernameEntry" />

<EditText
   android:id="@+id/usernameEntry" ... />

<!-- Label text for en-US locale would be "Password:" -->
<TextView
   android:id="@+id/passwordLabel" ...
   android:text="@string/password
   android:labelFor="@+id/passwordEntry" />

<EditText
   android:id="@+id/passwordEntry"
   android:inputType="textPassword" ... />

عناصر موجود در مجموعه

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

به‌ویژه، نوشتار یا اطلاعات زمینه‌ای بیشتری در عناصر موجود در چیدمان‌های مجدداً استفاده‌شده (مثل اشیای RecyclerView) اضافه کنید تا هر عنصر فرزند به‌طور منحصربه‌فردی شناسایی شود.

برای انجام این کار، شرح محتوا را به‌عنوان بخشی از پیاده‌سازی آداپتور خود تنظیم کنید، همان‌طور که در تکه‌کد زیر نشان داده شده است:

کاتلین

data class MovieRating(val title: String, val starRating: Integer)

class MyMovieRatingsAdapter(private val myData: Array<MovieRating>):
        RecyclerView.Adapter<MyMovieRatingsAdapter.MyRatingViewHolder>() {

    class MyRatingViewHolder(val ratingView: ImageView) :
            RecyclerView.ViewHolder(ratingView)

    override fun onBindViewHolder(holder: MyRatingViewHolder, position: Int) {
        val ratingData = myData[position]
        holder.ratingView.contentDescription = "Movie ${position}: " +
                "${ratingData.title}, ${ratingData.starRating} stars"
    }
}

جاوا

public class MovieRating {
    private String title;
    private int starRating;
    // ...
    public String getTitle() { return title; }
    public int getStarRating() { return starRating; }
}

public class MyMovieRatingsAdapter
        extends RecyclerView.Adapter<MyAdapter.MyRatingViewHolder> {
    private MovieRating[] myData;


    public static class MyRatingViewHolder extends RecyclerView.ViewHolder {
        public ImageView ratingView;
        public MyRatingViewHolder(ImageView iv) {
            super(iv);
            ratingView = iv;
        }
    }

    @Override
    public void onBindViewHolder(MyRatingViewHolder holder, int position) {
        MovieRating ratingData = myData[position];
        holder.ratingView.setContentDescription("Movie " + position + ": " +
                ratingData.getTitle() + ", " + ratingData.getStarRating() +
                " stars")
    }
}

گروه‌های محتوای مرتبط

اگر برنامه‌تان چند عنصر میانای کاربر را نمایش می‌دهد که گروهی طبیعی را تشکیل می‌دهند، مثل جزئیات آهنگ یا ویژگی‌های پیام، این عناصر را در یک محفظه قرار دهید که معمولاً زیرکلاسی از ViewGroup است. مشخصه android:screenReaderFocusable شیء دربرگیرنده را روی true و مشخصه android:focusable هر شیء داخلی را روی false تنظیم کنید. به‌این‌ترتیب، سرویس‌های دسترس‌پذیری می‌توانند شرح محتوای عناصر داخلی را یکی پس‌از دیگری در یک اعلامیه ارائه دهند. این ادغام عناصر مرتبط به کاربران فناوری کمکی کمک می‌کند اطلاعات روی صفحه را به‌طور مؤثرتری کاوش کنند.

قطعه کد زیر حاوی بخش‌هایی از محتوا است که به یکدیگر مرتبط هستند، بنابراین عنصر دربرگیرنده، نمونه‌ای از ConstraintLayout، دارای ویژگی android:screenReaderFocusable تنظیم‌شده روی true است و عناصر داخلی TextView هرکدام دارای ویژگی android:focusable تنظیم‌شده روی false هستند:

<!-- In response to a single user interaction, accessibility services announce
     both the title and the artist of the song. -->
<ConstraintLayout
    android:id="@+id/song_data_container" ...
    android:screenReaderFocusable="true">

    <TextView
        android:id="@+id/song_title" ...
        android:focusable="false"
        android:text="@string/my_song_title" />
    <TextView
        android:id="@+id/song_artist"
        android:focusable="false"
        android:text="@string/my_songwriter" />
</ConstraintLayout>

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

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

در بافت فهرست یا جدول، صفحه‌خوان ممکن است نوشتار فهرست یا گره‌های نوشتاری فرزند عنصر جدول را ادغام کند. بهتر است از اصلاح این اعلامیه خودداری کنید.

گروه‌های تودرتو

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

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

<!-- In response to a single user interaction, accessibility services
     announce the events for a single stage only. -->
<ConstraintLayout
    android:id="@+id/festival_event_table" ... >
    <ConstraintLayout
        android:id="@+id/stage_a_event_column"
        android:screenReaderFocusable="true">

        <!-- UI elements that describe the events on Stage A. -->

    </ConstraintLayout>
    <ConstraintLayout
        android:id="@+id/stage_b_event_column"
        android:screenReaderFocusable="true">

        <!-- UI elements that describe the events on Stage B. -->

    </ConstraintLayout>
</ConstraintLayout>

سرصفحه‌ها در نوشتار

برخی‌از برنامه‌ها از عنوان‌ها برای خلاصه کردن گروه‌های نوشتاری که در صفحه نشان داده می‌شود استفاده می‌کنند. اگر عنصر View خاصی نشان‌دهنده عنوان باشد، می‌توانید هدف آن را برای سرویس‌های دسترس‌پذیری با تنظیم کردن مشخصه android:accessibilityHeading عنصر روی true مشخص کنید.

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

عنوان‌های قاب دسترس‌پذیری

در Android 9 (سطح میانای برنامه‌سازی کاربردی ۲۸) و بالاتر، می‌توانید عنوان‌های مناسب دسترس‌پذیری برای قاب‌های صفحه ارائه دهید. برای اهداف دسترس‌پذیری، قاب بخشی از پنجره است که ازنظر بصری متمایز است، مثل محتوای یک قطعه. برای اینکه سرویس‌های دسترس‌پذیری بتوانند رفتار پنجره‌مانند قاب را درک کنند، عنوان‌های توصیفی به قاب‌های برنامه‌تان بدهید. سپس سرویس‌های دسترس‌پذیری می‌توانند اطلاعات دقیق‌تری را به کاربران ارائه دهند وقتی ظاهر یا محتوای قاب تغییر می‌کند.

برای مشخص کردن عنوان قاب، از مشخصه android:accessibilityPaneTitle استفاده کنید، همان‌طور که در قطعه کد زیر نشان داده شده است:

<!-- Accessibility services receive announcements about content changes
     that are scoped to either the "shopping cart view" section (top) or
     "browse items" section (bottom) -->
<MyShoppingCartView
     android:id="@+id/shoppingCartContainer"
     android:accessibilityPaneTitle="@string/shoppingCart" ... />

<MyShoppingBrowseView
     android:id="@+id/browseItemsContainer"
     android:accessibilityPaneTitle="@string/browseProducts" ... />

عناصر تزئینی

اگر عنصری در واسط کاربر شما فقط برای فاصله‌گذاری دیداری یا اهداف ظاهر دیداری وجود دارد، ویژگی android:importantForAccessibility آن را روی "no" تنظیم کنید.

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

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

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

کاربر TalkBack، دسترسی صوتی، یا «دسترسی با کلید» ممکن است به روش‌های جایگزینی برای تکمیل جریان‌های کاربری خاصی در برنامه نیاز داشته باشد. برای کنش‌های مرتبط با اشاره‌ها مانند کشیدن و رها کردن یا تند کشیدن، برنامه شما می‌تواند کنش‌ها را به‌گونه‌ای نمایش دهد که برای کاربران سرویس‌های دسترس‌پذیری قابل‌دسترس باشد.

بااستفاده از کنش‌های دسترس‌پذیری، برنامه می‌تواند روش‌های جایگزینی برای تکمیل کنش به کاربران ارائه دهد.

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

کاتلین

ViewCompat.addAccessibilityAction(
    // View to add accessibility action
    itemView,
    // Label surfaced to user by an accessibility service
    getText(R.id.archive)
) { _, _ ->
    // Same method executed when swiping on itemView
    archiveItem()
    true
}

جاوا

ViewCompat.addAccessibilityAction(
    // View to add accessibility action
    itemView,
    // Label surfaced to user by an accessibility service
    getText(R.id.archive),
    (view, arguments) -> {
        // Same method executed when swiping on itemView
        archiveItem();
        return true;
    }
);

With the custom accessibility action implemented, users can access the action through the actions menu.

Make available actions understandable

When a view supports actions such as touch & hold, an accessibility service such as TalkBack announces it as "Double tap and hold to long press."

This generic announcement doesn't give the user any context about what a touch & hold action does.

To make this announcement more descriptive, you can replace the accessibility action’s announcement like so:

Kotlin

ViewCompat.replaceAccessibilityAction(
    // View that contains touch & hold action
    itemView,
    AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_LONG_CLICK,
    // Announcement read by TalkBack to surface this action
    getText(R.string.favorite),
    null
)

جاوا

ViewCompat.replaceAccessibilityAction(
    // View that contains touch & hold action
    itemView,
    AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_LONG_CLICK,
    // Announcement read by TalkBack to surface this action
    getText(R.string.favorite),
    null
);

This results in TalkBack announcing "Double tap and hold to favorite," helping users understand the purpose of the action.

Extend system widgets

Note: When you design your app's UI, use or extend system-provided widgets that are as far down Android's class hierarchy as possible. System-provided widgets that are far down the hierarchy already have most of the accessibility capabilities your app needs. It's easier to extend these system-provided widgets than to create your own from the more generic View, ViewCompat, Canvas, and CanvasCompat classes.

If you must extend View or Canvas directly, which might be necessary for a highly customized experience or a game level, see Make custom views more accessible.

This section uses the example of implementing a special type of Switch called TriSwitch while following best practices around extending system widgets. A TriSwitch object works similarly to a Switch object, except that each instance of TriSwitch allows the user to toggle among three possible states.

Extend from far down the class hierarchy

The Switch object inherits from several framework UI classes in its hierarchy:

View
↳ TextView
  ↳ Button
    ↳ CompoundButton
      ↳ Switch

بهتر است کلاس جدید TriSwitch مستقیماً از کلاس Switch گسترش یابد. به‌این‌ترتیب، چارچوب دسترس‌پذیری Android بیشتر قابلیت‌های دسترس‌پذیری موردنیاز کلاس TriSwitch را ارائه می‌دهد:

  • کنش‌های دسترس‌پذیری: اطلاعاتی برای سیستم درباره اینکه سرویس‌های دسترس‌پذیری چگونه می‌توانند هر ورودی احتمالی کاربر را که در شیء TriSwitch انجام می‌شود شبیه‌سازی کنند. (از View به ارث رسیده است.)
  • رویدادهای دسترس‌پذیری: اطلاعاتی برای سرویس‌های دسترس‌پذیری درباره هر روش ممکن برای تغییر ظاهر شیء TriSwitch هنگام بازآوری یا به‌روزرسانی صفحه. (از View به ارث رسیده است.)
  • ویژگی‌ها: جزئیات مربوط به هر شیء TriSwitch، مانند محتوای هر نوشتاری که نمایش می‌دهد. (از TextView به ارث رسیده است.)
  • اطلاعات وضعیت: شرح وضعیت فعلی شیء TriSwitch، مثلاً «علامت‌زده» یا «علامت‌نزده». (از CompoundButton به ارث رسیده است.)
  • توضیح نوشتاری وضعیت: توضیح نوشتاری درباره اینکه هر وضعیت نشان‌دهنده چیست. (از Switch به ارث رسیده است.)

این رفتار از Switch و ابرکلاس‌های آن تقریباً همان رفتار برای اشیاء TriSwitch است. بنابراین، پیاده‌سازی شما می‌تواند بر گسترش تعداد حالت‌های ممکن از دو به سه تمرکز کند.

تعریف رویدادهای سفارشی

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

یک دستورالعمل کلی این است که برای هر بازخوانی مبتنی بر نما که لغو می‌کنید، باید کنش دسترس‌پذیری مربوطه را نیز با لغو کردن ViewCompat.replaceAccessibilityAction() بازتعریف کنید. در آزمایش‌های برنامه‌تان، می‌توانید با فراخوانی ViewCompat.performAccessibilityAction()، رفتار این کنش‌های بازتعریف‌شده را اعتبارسنجی کنید.

چگونه این اصل می‌تواند برای اشیاء TriSwitch کار کند

برخلاف شیء Switch معمولی، با تک‌ضرب زدن روی شیء TriSwitch، سه حالت ممکن به‌صورت چرخه‌ای تکرار می‌شود. بنابراین، کنش دسترس‌پذیری ACTION_CLICK مربوطه باید به‌روز شود:

کاتلین

class TriSwitch(context: Context) : Switch(context) {
    // 0, 1, or 2
    var currentState: Int = 0
        private set

    init {
        updateAccessibilityActions()
    }

    private fun updateAccessibilityActions() {
        ViewCompat.replaceAccessibilityAction(this, ACTION_CLICK,
            action-label) {
            view, args -> moveToNextState()
        })
    }

    private fun moveToNextState() {
        currentState = (currentState + 1) % 3
    }
}

جاوا

public class TriSwitch extends Switch {
    // 0, 1, or 2
    private int currentState;

    public int getCurrentState() {
        return currentState;
    }

    public TriSwitch() {
        updateAccessibilityActions();
    }

    private void updateAccessibilityActions() {
        ViewCompat.replaceAccessibilityAction(this, ACTION_CLICK,
            action-label, (view, args) -> moveToNextState());
    }

    private void moveToNextState() {
        currentState = (currentState + 1) % 3;
    }
}

منابع بیشتر

برای کسب اطلاعات بیشتر درباره دسترس‌پذیرتر کردن برنامه‌تان، منابع اضافی زیر را ببینید:

Codelabs

پست‌های وبلاگ