لمساعدة المستخدمين الذين لديهم احتياجات متعلّقة بتسهيل الاستخدام، يتيح لك إطار عمل 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
الكائنات، حتى يتم تحديد كل عنصر فرعي بشكلٍ فريد.
لإجراء ذلك، اضبط وصف المحتوى كجزء من عملية تنفيذ المحوّل، كما هو موضّح في مقتطف الرمز التالي:
Kotlin
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" } }
Java
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. بهذه الطريقة، يمكن لخدمات تسهيل الاستخدام عرض أوصاف محتوى العناصر الداخلية، واحدًا تلو الآخر، في إعلان واحد.
يساعد هذا التجميع للعناصر ذات الصلة مستخدمي التكنولوجيا المساعدة في اكتشاف المعلومات على الشاشة بكفاءة أكبر.
android:focusable
يحتوي المقتطف التالي على أجزاء من المحتوى ذات صلة ببعضها البعض، لذا تم ضبط السمة android:screenReaderFocusable لعنصر الحاوية، وهو مثيل من ConstraintLayout، على true، وتم ضبط السمة android:focusable لكل عنصر من عناصر TextView الداخلية على 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 (مستوى واجهة برمجة التطبيقات 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 أو الوصول الصوتي، أو الوصول عبر مفتاح تحكّم إلى طرق بديلة لإكمال مسارات مستخدم معيّنة داخل التطبيق. بالنسبة إلى الإجراءات المرتبطة بالإيماءات، مثل السحب والإفلات أو التمرير السريع، يمكن لتطبيقك عرض الإجراءات بطريقة يمكن لمستخدمي خدمات تسهيل الاستخدام الوصول إليها.
باستخدام إجراءات تسهيل الاستخدام، يمكن للتطبيق توفير طرق بديلة للمستخدمين لإكمال إجراء معيّن.
على سبيل المثال، إذا كان تطبيقك يسمح للمستخدمين بالتمرير سريعًا على عنصر، يمكنك أيضًا عرض هذه الوظيفة من خلال إجراء تسهيل استخدام مخصّص، مثل ما يلي:
Kotlin
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 }
Java
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 )
Java
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 المقابل:
Kotlin
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 } }
Java
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; } }
مراجع إضافية
لمزيد من المعلومات حول جعل تطبيقك أكثر سهولة في الاستخدام، اطّلِع على المراجع الإضافية التالية: