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

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

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

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

  • 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" ... />

در این شرایط، شیء View باید ویژگی android:labelFor خود را روی شناسه عنصر 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 ، هدف آن را برای سرویس‌های دسترسی مشخص کنید.

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

عناوین پنل دسترسی‌پذیری

در اندروید ۹ (سطح API 28) و بالاتر، می‌توانید عناوین سازگار با دسترسی‌پذیری را برای پنل‌های صفحه نمایش ارائه دهید. برای اهداف دسترسی‌پذیری، یک پنل بخشی بصری متمایز از یک پنجره است، مانند محتویات یک قطعه. برای اینکه سرویس‌های دسترسی‌پذیری بتوانند رفتار شبه‌پنجره‌ای یک پنل را درک کنند، عناوین توصیفی به پنل‌های برنامه خود بدهید. سپس سرویس‌های دسترسی‌پذیری می‌توانند اطلاعات جزئی‌تری را در هنگام تغییر ظاهر یا محتوای یک پنل به کاربران ارائه دهند.

برای مشخص کردن عنوان یک صفحه، از ویژگی 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، Voice Access یا Switch Access برای تکمیل برخی از جریان‌های کاربری درون برنامه به روش‌های جایگزینی نیاز داشته باشد. برای اقدامات مرتبط با حرکاتی مانند کشیدن و رها کردن یا کشیدن انگشت، برنامه شما می‌تواند اقدامات را به روشی که برای کاربران سرویس‌های دسترسی قابل دسترسی است، نمایش دهد.

با استفاده از اقدامات دسترسی ، برنامه می‌تواند راه‌های جایگزینی برای تکمیل یک اقدام در اختیار کاربران قرار دهد.

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

کاتلین

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 actions 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 ارث‌بری کند. به این ترتیب، چارچوب دسترسی‌پذیری اندروید اکثر قابلیت‌های دسترسی‌پذیری مورد نیاز کلاس TriSwitch را فراهم می‌کند:

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

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

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

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

یک دستورالعمل کلی این است که برای هر فراخوانی مبتنی بر view که لغو می‌کنید، باید با لغو 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;
    }
}

منابع اضافی

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

کدلبز

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