1. مقدمة
خدمات تسهيل الاستخدام هي ميزة في إطار عمل Android تم تصميمها لتوفير ملاحظات بديلة للمستخدم بشأن التنقّل نيابةً عن التطبيقات المثبَّتة على أجهزة Android. يمكن لخدمة تسهيل الاستخدام التواصل مع المستخدم نيابةً عن التطبيق، مثلاً عن طريق تحويل النص إلى كلام أو تقديم ملاحظات حسّية عندما يمرر المستخدم مؤشر الماوس فوق منطقة مهمة من الشاشة. يعرض لك هذا الدرس التطبيقي حول الترميز كيفية إنشاء خدمة بسيطة جدًا لتسهيل الاستخدام.
ما هي "خدمة تسهيل الاستخدام"؟
تساعد "خدمة تسهيل الاستخدام" المستخدمين الذين يعانون من عجز في استخدام أجهزة Android وتطبيقاته. هي خدمة عريقة وامتيازها تساعد المستخدمين على معالجة المعلومات التي تظهر على الشاشة وتتيح لهم التفاعل بشكل مفيد مع الأجهزة.
أمثلة على خدمات تسهيل الاستخدام الشائعة
- الوصول عبر مفتاح تحكّم: يسمح هذا الخيار لمستخدمي Android الذين يعانون من محدودية في الحركة بالتفاعل مع الأجهزة باستخدام مفتاح تحكّم واحد أو أكثر.
- Voice Access (تجريبي): يسمح لمستخدمي Android الذين يعانون من قيود التنقل بالتحكم في الجهاز باستخدام الأوامر المنطوقة.
- Talkback: هي قارئ شاشة يُستخدم عادةً من قِبل المستخدمين ذوي العجز البصري أو المكفوفين.
إنشاء خدمة لتسهيل الاستخدام
على الرغم من أن Google توفر خدمات مثل "الوصول عبر مفتاح تحكّم" وVoice Access وTalkBack لمستخدمي Android، لا يمكن لهذه الخدمات أن تخدم جميع المستخدمين الذين يعانون من عجز. وبما أنّ العديد من المستخدمين من ذوي الاحتياجات الخاصة لديهم احتياجات فريدة، تتوفّر واجهات برمجة تطبيقات Android لإنشاء خدمات تسهيل الاستخدام، ويمكن للمطوّرين إنشاء خدمات لتسهيل الاستخدام وتوزيعها من خلال "متجر Play".
ما الذي ستنشئه
في هذا الدرس التطبيقي حول الترميز، ستُطوِّر خدمة بسيطة تؤدي إلى بعض الإجراءات المفيدة باستخدام واجهة برمجة التطبيقات Accessibility API. إذا كان بإمكانك كتابة تطبيق أساسي متوافق مع Android، يمكنك تطوير خدمة مشابهة.
واجهة برمجة التطبيقات Accessibility API فعّالة، فرمز الخدمة التي ستنشئها مضمّن في أربعة ملفات فقط، ويستخدم حوالي 200 سطر من الرموز.
المستخدم النهائي
ستنشئ خدمة لمستخدم افتراضي بالخصائص التالية:
- يجد المستخدم صعوبة في الوصول إلى الأزرار الجانبية على الجهاز.
- يواجه المستخدم صعوبة في التمرير أو التمرير السريع.
تفاصيل الخدمة
ستتراكب خدمتك على شريط إجراءات عام على الشاشة. ويمكن للمستخدم لمس الأزرار في هذا الشريط لتنفيذ الإجراءات التالية:
- أطفِئ الجهاز بدون الوصول إلى زر التشغيل الفعلي على جانب الهاتف.
- اضبط مستوى الصوت بدون لمس زرَّي التحكّم بمستوى الصوت على جانب الهاتف.
- تنفيذ إجراءات التمرير بدون التمرير الفعلي
- مرِّر سريعًا بدون الحاجة إلى استخدام إيماءة التمرير السريع.
المتطلبات
يفترض هذا الدرس التطبيقي حول الترميز أنك ستستخدم ما يلي:
- جهاز كمبيوتر يعمل عليه "استوديو Android"
- يشير ذلك المصطلح إلى وحدة طرفية لتنفيذ أوامر واجهة الأوامر البسيطة.
- جهاز يعمل بنظام التشغيل Android 7.0 (Nougat) ومتصل بالكمبيوتر الذي ستستخدمه للتطوير.
لِنبدأ.
2. بدء الإعداد
باستخدام الوحدة الطرفية، أنشئ دليلاً للعمل فيه. التغيير إلى هذا الدليل
تنزيل الرمز
يمكنك استنساخ المستودع الذي يحتوي على الرمز الخاص بهذا الدرس التطبيقي حول الترميز:
git clone https://github.com/android/codelab-android-accessibility.git
يحتوي المستودع على العديد من مشاريع "استوديو Android". باستخدام "استوديو Android"، افتح GlobalActionBarService.
افتح "استوديو Android" بالنقر على رمز "استوديو YouTube":
حدِّد الخيار استيراد مشروع (Eclipse ADT أو Gradle أو غير ذلك):
انتقِل إلى الموقع الجغرافي الذي استنسخت منه المصدر، واختَر GlobalActionBarService.
ثم، باستخدام الطرفية، التغيير إلى الدليل الجذر.
3- فهم رمز البداية
استكشف المشروع الذي فتحته.
تم إنشاء الهيكل الأساسي لخدمة تسهيل الاستخدام من أجلك. تقتصر كل الرموز البرمجية التي ستكتبها في هذا الدرس التطبيقي على الملفات الأربعة التالية:
- app/src/main/AndroidManifest.xml
- app/src/main/res/layout/action_bar.xml
- app/src/main/res/xml/global_action_bar_service.xml
- app/src/main/java/com/example/android/globalactionbarservice/GlobalActionBarService.java
في ما يلي جولة تفصيلية حول محتويات كل ملف.
AndroidManifest.xml
يتضمّن البيان معلومات حول خدمة تسهيل الاستخدام:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.android.globalactionbarservice">
<application>
<service
android:name=".GlobalActionBarService"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
android:exported="true">
<intent-filter>
<action android:name="android.accessibilityservice.AccessibilityService" />
</intent-filter>
<meta-data
android:name="android.accessibilityservice"
android:resource="@xml/global_action_bar_service" />
</service>
</application>
</manifest>
يتم تعريف العناصر الثلاثة التالية المطلوبة في ملف AndroidManifest.xml:
- إذن للربط بخدمة إمكانية وصول:
<service
...
android:permission = "android.permission.BIND_ACCESSIBILITY_SERVICE">
...
</service>
- الغرض AccessibilityService:
<intent-filter>
<action android:name="android.accessibilityservice.AccessibilityService" />
</intent-filter>
- موقع الملف الذي يحتوي على البيانات الوصفية للخدمة التي تقوم بإنشائها:
<meta-data
...
android:resource="@xml/global_action_bar_service" />
</service>
global_action_bar_service.xml
يحتوي هذا الملف على البيانات الوصفية للخدمة.
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
android:accessibilityFeedbackType="feedbackGeneric"
android:accessibilityFlags="flagDefault"
android:canPerformGestures="true"
android:canRetrieveWindowContent="true" />
باستخدام عنصر <accessibility-service>، تم تحديد البيانات الوصفية التالية:
- نوع الملاحظات لهذه الخدمة (يستخدم هذا الدرس التطبيقي حول الترميز دالة feedbackعامة، وهي قيمة تلقائية جيدة).
- علامات إمكانية الوصول الخاصة بالخدمة (يستخدم هذا الدرس التطبيقي حول الترميز علامات تلقائية)
- الإمكانات اللازمة للخدمة:
- لإجراء التمرير السريع، تم ضبط android:canPerformGestures على android:canPerformGestures.
- لاسترداد محتوى النافذة، يتم ضبط android:canRetrieveWindowContent على true.
GlobalActionBarService.java
يتوفّر معظم الرموز البرمجية لخدمة تسهيل الاستخدام في GlobalActionBarService.java. في البداية، يحتوي الملف على الحد الأدنى المطلق من الرموز لخدمة تسهيل الاستخدام:
- يشير ذلك المصطلح إلى فئة توسّع AccessibilityService.
- هناك طريقتان مطلوبتان تم تجاهلهما (يُترك هذا الحقل فارغًا في هذا الدرس التطبيقي حول الترميز).
public class GlobalActionBarService extends AccessibilityService {
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
}
@Override
public void onInterrupt() {
}
}
ستضيف رمزًا إلى هذا الملف أثناء الدرس التطبيقي حول الترميز.
action_bar.xml
تعرض الخدمة واجهة مستخدم بأربعة أزرار، ويحتوي ملف التنسيق action_bar.xml على ترميز لعرض هذه الأزرار:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</LinearLayout>
يحتوي هذا الملف على LinearLayout فارغ في الوقت الحالي. وستتمكَّن من إضافة ترميز للأزرار أثناء الدرس التطبيقي حول الترميز.
تشغيل التطبيق
تأكَّد من اتصال جهاز بالكمبيوتر. اضغط على الرمز الأخضر تشغيل من شريط القائمة باتجاه أعلى الشاشة. من المفترض أن يؤدي هذا الإجراء إلى تشغيل التطبيق الذي تعمل عليه.
انتقِل إلى الإعدادات >. تسهيل الاستخدام: خدمة شريط الإجراءات العالمية مثبَّتة على جهازك.
انقر على خدمة شريط الإجراءات الشاملة وفعِّلها. من المفترض أن يظهر لك مربّع حوار الأذونات التالي:
تطلب خدمة تسهيل الاستخدام إذنًا لمراقبة إجراءات المستخدم واسترداد محتوى النافذة وتنفيذ الإيماءات نيابةً عن المستخدم! عند استخدام خدمة إمكانية وصول تابعة لجهة خارجية، احرص على أن يكون المصدر موثوقًا.
لا يؤدي تشغيل الخدمة إلى إجراء الكثير، لأننا لم نقم بإضافة أي وظائف إلى الآن. لنبدأ في القيام بذلك.
4. إنشاء الأزرار
افتح action_bar.xml في res/layout. إضافة الترميز داخل LinearLayout الفارغ حاليًا:
<LinearLayout ...>
<Button
android:id="@+id/power"
android:text="@string/power"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<Button
android:id="@+id/volume_up"
android:text="@string/volume"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<Button
android:id="@+id/scroll"
android:text="@string/scroll"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<Button
android:id="@+id/swipe"
android:text="@string/swipe"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
يؤدي هذا إلى إنشاء أزرار سيضغط عليها المستخدم لتشغيل الإجراءات على الجهاز.
افتح GlobalActionBarService.java وأضِف متغيّرًا لتخزين تنسيق شريط الإجراءات:
public class GlobalActionBarService extends AccessibilityService {
FrameLayout mLayout;
...
}
أضف الآن طريقة onServiceStarted():
public class GlobalActionBarService extends AccessibilityService {
FrameLayout mLayout;
@Override
protected void onServiceConnected() {
// Create an overlay and display the action bar
WindowManager wm = (WindowManager) getSystemService(WINDOW_SERVICE);
mLayout = new FrameLayout(this);
WindowManager.LayoutParams lp = new WindowManager.LayoutParams();
lp.type = WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
lp.format = PixelFormat.TRANSLUCENT;
lp.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
lp.width = WindowManager.LayoutParams.WRAP_CONTENT;
lp.height = WindowManager.LayoutParams.WRAP_CONTENT;
lp.gravity = Gravity.TOP;
LayoutInflater inflater = LayoutInflater.from(this);
inflater.inflate(R.layout.action_bar, mLayout);
wm.addView(mLayout, lp);
}
}
ينفخ الرمز التصميم ويضيف شريط الإجراءات باتجاه أعلى الشاشة.
يتم تشغيل الإجراء onServiceConnected() عندما تكون الخدمة متصلة. في الوقت الحالي، تمتلك خدمة تسهيل الاستخدام كل الأذونات اللازمة لكي تعمل. إذن المفتاح الذي ستستخدمه هنا هو إذن WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY. يتيح لك هذا الإذن الرسم مباشرةً على الشاشة فوق المحتوى الحالي بدون الحاجة إلى المرور بعملية أذونات معقّدة.
مراحل نشاط "خدمة تسهيل الاستخدام"
وتتم إدارة دورة حياة خدمة تسهيل الاستخدام من خلال النظام حصريًا، وهي تتبع دورة حياة الخدمة المعمول بها.
- تبدأ خدمة تسهيل الاستخدام عندما يفعّل المستخدم الخدمة صراحةً في إعدادات الجهاز.
- بعد ربط النظام بإحدى الخدمات، يستدعي onServiceConnected(). يمكن أن تلغي الخدمات التي تريد إعداد ربط المشاركات هذه الطريقة.
- تتوقف خدمة تسهيل الاستخدام إما عندما يوقفها المستخدم في إعدادات الجهاز أو عند استدعاء disableSelf().
تشغيل الخدمة
قبل إطلاق الخدمة باستخدام "استوديو Android"، عليك التأكّد من ضبط إعدادات "التشغيل" بشكل صحيح.
عدّل تهيئة التشغيل (استخدم "Run" من القائمة العلوية وانتقل إلى "Edit Configurations") ثم، باستخدام القائمة المنسدلة، قم بتغيير خيار التشغيل من "النشاط الافتراضي" إلى "لا شيء".
من المفترض أن يصبح بإمكانك الآن إطلاق الخدمة باستخدام "استوديو Android".
اضغط على الرمز الأخضر تشغيل من شريط القائمة باتجاه أعلى الشاشة. بعد ذلك، انتقل إلى الإعدادات > إمكانية الوصول وتفعيل خدمة شريط الإجراءات العالمية (Global Action Bar Service).
ومن المفترض أن تظهر الأزرار الأربعة التي تشكِّل واجهة مستخدم الخدمة متراكبة فوق المحتوى المعروض على الشاشة.
ستضيف الآن وظيفة إلى الأزرار الأربعة، بحيث يمكن للمستخدم لمسها لتنفيذ إجراءات مفيدة.
5- ضبط زر التشغيل
أضف طريقة configurePowerButton() إلى configurePowerButton():
private void configurePowerButton() {
Button powerButton = (Button) mLayout.findViewById(R.id.power);
powerButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
performGlobalAction(GLOBAL_ACTION_POWER_DIALOG);
}
});
}
للوصول إلى قائمة زر التشغيل، تستخدم configurePowerButton() طريقة performGlobalAction() المتاحة من خلال AccessibilityService. الرمز الذي أضفته للتو بسيط: يؤدي النقر على الزر إلى تشغيل onClickListener(). يستدعي هذا الإجراء performGlobalAction(GLOBAL_ACTION_POWER_DIALOG) ويعرض مربّع حوار التشغيل للمستخدم.
تجدر الإشارة إلى أنّ الإجراءات العامة لا ترتبط بأيّ مشاهدات. يعد الضغط على زر الرجوع وزر الصفحة الرئيسية وزر الأماكن الأخيرة أمثلة أخرى على الإجراءات العامة.
الآن أضف configurePowerButton() إلى نهاية طريقة configurePowerButton():
@Override
protected void onServiceConnected() {
...
configurePowerButton();
}
اضغط على الرمز الأخضر تشغيل من شريط القائمة باتجاه أعلى الشاشة. بعد ذلك، انتقل إلى الإعدادات > إمكانية الوصول وبدء خدمة شريط الإجراءات العالمية.
اضغط على زر التشغيل لعرض مربّع حوار التشغيل.
6- ضبط زر مستوى الصوت
أضِف طريقة configureVolumeButton() إلى configureVolumeButton():
private void configureVolumeButton() {
Button volumeUpButton = (Button) mLayout.findViewById(R.id.volume_up);
volumeUpButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
AudioManager audioManager = (AudioManager) getSystemService(AUDIO_SERVICE);
audioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC,
AudioManager.ADJUST_RAISE, AudioManager.FLAG_SHOW_UI);
}
});
}
تضيف الطريقة configureVolumeButton() الإجراء configureVolumeButton() الذي يتم تشغيله عندما يضغط المستخدم على زر التحكّم بمستوى الصوت. داخل برنامج المستمع هذا، تستخدم configureVolumeButton() AudioManager لضبط مستوى صوت البث
يُرجى العِلم أنّه يمكن لأي مستخدم التحكّم في مستوى الصوت (وليس من الضروري أن تكون خدمة تسهيل الاستخدام لإجراء ذلك).
الآن أضِف configureVolumeButton() إلى نهاية طريقة configureVolumeButton():
@Override
protected void onServiceConnected() {
...
configureVolumeButton();
}
اضغط على الرمز الأخضر تشغيل من شريط القائمة باتجاه أعلى الشاشة. بعد ذلك، انتقل إلى الإعدادات > إمكانية الوصول وبدء خدمة شريط الإجراءات العالمية
اضغط على زر مستوى الصوت لتغيير مستوى الصوت.
يمكن الآن للمستخدم الافتراضي الذي يتعذر عليه الوصول إلى عناصر التحكّم في مستوى الصوت على جانب الجهاز استخدام خدمة شريط الإجراءات العالمية لتغيير (زيادة) مستوى الصوت.
7. تهيئة زر التمرير
يتضمن هذا القسم ترميز طريقتين. تعثر الطريقة الأولى على عقدة قابلة للتمرير، والطريقة الثانية تنفذ إجراء التمرير نيابةً عن المستخدم.
أضِف الطريقة findScrollableNode إلى findScrollableNode
private AccessibilityNodeInfo findScrollableNode(AccessibilityNodeInfo root) {
Deque<AccessibilityNodeInfo> deque = new ArrayDeque<>();
deque.add(root);
while (!deque.isEmpty()) {
AccessibilityNodeInfo node = deque.removeFirst();
if (node.getActionList().contains(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD)) {
return node;
}
for (int i = 0; i < node.getChildCount(); i++) {
deque.addLast(node.getChild(i));
}
}
return null;
}
لا يمكن لخدمة تسهيل الاستخدام الوصول إلى طرق العرض الفعلية التي تظهر على الشاشة. بدلاً من ذلك، فإنّها تظهر انعكاسًا لما يظهر على الشاشة على شكل شجرة مكوّنة من عناصر AccessibilityNodeInfo. تحتوي هذه العناصر على معلومات حول العرض الذي تمثله (موقع العرض، وأي نص مرتبط بالعرض، والبيانات الوصفية التي تمت إضافتها لإمكانية الوصول، والإجراءات المدعومة بواسطة العرض، وما إلى ذلك). تُجري الطريقة findScrollableNode() عملية اجتياز ذات اتساع أول لهذه الشجرة، بدءًا من العقدة الجذر. في حال العثور على عقدة قابلة للتمرير (أي عقدة تتوافق مع إجراء AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD)
، سيتم عرضها، وإلا سيتم عرضها فارغة.
أضف الآن طريقة configureScrollButton() إلى configureScrollButton():
private void configureScrollButton() {
Button scrollButton = (Button) mLayout.findViewById(R.id.scroll);
scrollButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
AccessibilityNodeInfo scrollable = findScrollableNode(getRootInActiveWindow());
if (scrollable != null) {
scrollable.performAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD.getId());
}
}
});
}
وتُنشئ هذه الطريقة دالة onClickListener() التي يتم تنشيطها عند النقر على زر التمرير. يحاول العثور على عقدة قابلة للتمرير، وفي حال نجاحها، تنفذ إجراء التمرير.
أضف الآن configureScrollButton() إلى configureScrollButton():
@Override
protected void onServiceConnected() {
...
configureScrollButton();
}
اضغط على الرمز الأخضر تشغيل من شريط القائمة باتجاه أعلى الشاشة. بعد ذلك، انتقل إلى الإعدادات > إمكانية الوصول وبدء خدمة شريط الإجراءات العالمية
اضغط على زر الرجوع للانتقال إلى الإعدادات > تسهيل الاستخدام: يمكن التمرير بين العناصر في نشاط إعدادات تسهيل الاستخدام، ويؤدي لمس زر "التمرير" إلى تنفيذ إجراء الانتقال. يمكن الآن للمستخدم الافتراضي الذي لا يستطيع تنفيذ إجراءات التمرير بسهولة استخدام زر "التمرير" لتمرير قائمة العناصر.
8. تهيئة زر التمرير السريع
أضف طريقة configureSwipeButton() إلى configureSwipeButton():
private void configureSwipeButton() {
Button swipeButton = (Button) mLayout.findViewById(R.id.swipe);
swipeButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Path swipePath = new Path();
swipePath.moveTo(1000, 1000);
swipePath.lineTo(100, 1000);
GestureDescription.Builder gestureBuilder = new GestureDescription.Builder();
gestureBuilder.addStroke(new GestureDescription.StrokeDescription(swipePath, 0, 500));
dispatchGesture(gestureBuilder.build(), null, null);
}
});
}
تستخدم الطريقة configureSwipeButton() واجهة برمجة تطبيقات جديدة تمت إضافتها في N وهي تؤدي إيماءات بالنيابة عن المستخدم. يستخدم الرمز كائن GestureDescription لتحديد مسار الإيماءة التي سيتم تنفيذها (يتم استخدام قيم رموز برمجية ثابتة في هذا الدرس التطبيقي حول الترميز)، ثم يرسل إيماءة التمرير السريع نيابةً عن المستخدم باستخدام طريقة AccessibilityService dispatchGesture().
أضِف الآن configureSwipeButton() إلى configureSwipeButton():
@Override
protected void onServiceConnected() {
...
configureSwipeButton();
}
اضغط على الرمز الأخضر تشغيل من شريط القائمة باتجاه أعلى الشاشة. بعد ذلك، انتقل إلى الإعدادات > إمكانية الوصول وبدء خدمة شريط الإجراءات العالمية
أسهل طريقة لاختبار وظيفة التمرير السريع هي فتح تطبيق الخرائط المثبت على هاتفك. بعد تحميل الخريطة، يؤدي لمس زر "التمرير السريع" إلى تمرير الشاشة إلى اليسار.
9. ملخّص
تهانينا! لقد أنشأت خدمة إمكانية وصول بسيطة وفعالة.
يمكنك تمديد هذه الخدمة بطرق مختلفة. على سبيل المثال:
- اجعل شريط الإجراءات قابلاً للتحريك (يبقى فقط أعلى الشاشة في الوقت الحالي).
- اسمح للمستخدم برفع مستوى الصوت أو خفضه معًا.
- يسمح للمستخدم بالتمرير السريع إلى اليسار واليمين.
- يمكنك إتاحة استخدام إيماءات إضافية يمكن لشريط الإجراءات الاستجابة لها.
يتناول هذا الدرس التطبيقي حول الترميز مجموعة فرعية صغيرة فقط من الوظائف التي توفّرها واجهات برمجة التطبيقات المخصّصة لتسهيل الاستخدام. تغطي واجهة برمجة التطبيقات أيضًا ما يلي (قائمة جزئية):
- دعم نوافذ متعددة.
- إتاحة أحداث AccessibilityEvent وعند تغيُّر واجهة المستخدم، يتم إشعار خدمات تسهيل الاستخدام بهذه التغييرات باستخدام كائنات AccessibilityEvent. ويمكن للخدمة بعد ذلك الاستجابة للتغييرات على واجهة المستخدم حسبما تقتضي الحاجة.
- إمكانية التحكّم في التكبير
يساعدك هذا الدرس التطبيقي في البدء بكتابة خدمة مخصّصة لتسهيل الاستخدام. إذا كنت تعرف مستخدمًا يعاني من مشكلات محددة في إمكانية الوصول وتريد معالجتها، فيمكنك الآن إنشاء خدمة لمساعدة هذا المستخدم.