الخطوات الرئيسية لتحسين إمكانية الوصول في ComposeAllowed

لمساعدة الأشخاص الذين يعانون من احتياجات إمكانية الوصول على استخدام تطبيقك بنجاح، قم بتصميم تطبيقك لتلبية المتطلبات الرئيسية لإمكانية الوصول.

مراعاة الحدّ الأدنى لأحجام مساحات اللمس

إنّ أي عنصر على الشاشة يمكن لشخص ما النقر عليه أو لمسه أو التفاعل معه يجب أن يكون كبيرًا بما يكفي للتفاعل الموثوق به. عند تحديد حجم هذه العناصر، احرص على ضبط الحدّ الأدنى للحجم على 48 بكسل مستقل الكثافة لاتّباع إرشادات تسهيل الاستخدام ذات التصميم المتعدد الأبعاد بشكل صحيح.

يمكنك ضبط الحد الأدنى للحجم داخليًا في مكوّنات التصميم المتعدد الأبعاد، مثل Checkbox وRadioButton وSwitch Slider وSurface. على سبيل المثال، عندما يتم ضبط معلَمة onCheckedChange الخاصة بـ Checkbox على قيمة غير فارغة، يتضمّن مربّع الاختيار مساحة متروكة ليكون العرض والارتفاع 48 وحدة بكسل مستقلة الكثافة (dp) على الأقل.

@Composable
private fun CheckableCheckbox() {
    Checkbox(checked = true, onCheckedChange = {})
}

عند ضبط المعلَمة onCheckedChange على قيمة فارغة، لا يتم تضمين المساحة المتروكة لأنّه لا يمكن التفاعل مع المكوِّن مباشرةً.

@Composable
private fun NonClickableCheckbox() {
    Checkbox(checked = true, onCheckedChange = null)
}

الشكل 1. مربّع اختيار بدون مساحة متروكة

عند تنفيذ عناصر التحكّم في الاختيار، مثل Switch أو RadioButton أو Checkbox، عادةً ما تُرفع السلوك القابل للنقر إلى حاوية رئيسية، وتضبط معاودة الاتصال للنقرات على العنصر القابل للإنشاء على null، وتضيف عنصر تعديل toggleable أو selectable إلى العنصر الرئيسي القابل للإنشاء.

@Composable
private fun CheckableRow() {
    MaterialTheme {
        var checked by remember { mutableStateOf(false) }
        Row(
            Modifier
                .toggleable(
                    value = checked,
                    role = Role.Checkbox,
                    onValueChange = { checked = !checked }
                )
                .padding(16.dp)
                .fillMaxWidth()
        ) {
            Text("Option", Modifier.weight(1f))
            Checkbox(checked = checked, onCheckedChange = null)
        }
    }
}

عندما يكون حجم الجزء القابل للإنشاء القابل للنقر أصغر من الحد الأدنى لحجم ناحية اللمس المستهدفة، سيستمر تطبيق Compose في زيادة حجم مساحة اللمس. ويتم ذلك من خلال توسيع حجم مساحة اللمس المستهدفة خارج حدود العنصر القابل للإنشاء.

يحتوي المثال التالي على رمز Box صغير جدًا قابل للنقر. يتم تلقائيًا توسيع منطقة اللمس المستهدفة خارج حدود Box، لذلك سيؤدي النقر بجانب Box إلى تشغيل حدث النقر.

@Composable
private fun SmallBox() {
    var clicked by remember { mutableStateOf(false) }
    Box(
        Modifier
            .size(100.dp)
            .background(if (clicked) Color.DarkGray else Color.LightGray)
    ) {
        Box(
            Modifier
                .align(Alignment.Center)
                .clickable { clicked = !clicked }
                .background(Color.Black)
                .size(1.dp)
        )
    }
}

لمنع التداخل المحتمل بين مناطق اللمس في عناصر مختلفة للإنشاء، استخدِم دائمًا حدًا أدنى كبيرًا بما يكفي لحجم العنصر القابل للإنشاء. في المثال، يعني ذلك استخدام أداة التعديل sizeIn لضبط الحدّ الأدنى لحجم المربّع الداخلي:

@Composable
private fun LargeBox() {
    var clicked by remember { mutableStateOf(false) }
    Box(
        Modifier
            .size(100.dp)
            .background(if (clicked) Color.DarkGray else Color.LightGray)
    ) {
        Box(
            Modifier
                .align(Alignment.Center)
                .clickable { clicked = !clicked }
                .background(Color.Black)
                .sizeIn(minWidth = 48.dp, minHeight = 48.dp)
        )
    }
}

إضافة تصنيفات النقرات

يمكنك استخدام تصنيف النقر لإضافة معنى دلالي لسلوك النقر في عنصر قابل للإنشاء. تصف تسميات النقر ما يحدث عندما يتفاعل المستخدم مع العنصر القابل للإنشاء. تستخدم خدمات إمكانية الوصول تصنيفات النقر للمساعدة في وصف التطبيق للمستخدمين ذوي الاحتياجات الخاصة.

اضبط تصنيف النقرة من خلال ضبط مَعلمة في أداة تعديل clickable:

@Composable
private fun ArticleListItem(openArticle: () -> Unit) {
    Row(
        Modifier.clickable(
            // R.string.action_read_article = "read article"
            onClickLabel = stringResource(R.string.action_read_article),
            onClick = openArticle
        )
    ) {
        // ..
    }
}

بدلاً من ذلك، إذا لم تتمكّن من الوصول إلى المعدِّل القابل للنقر، اضبط تصنيف النقر في معدِّل الدلالات:

@Composable
private fun LowLevelClickLabel(openArticle: () -> Boolean) {
    // R.string.action_read_article = "read article"
    val readArticleLabel = stringResource(R.string.action_read_article)
    Canvas(
        Modifier.semantics {
            onClick(label = readArticleLabel, action = openArticle)
        }
    ) {
        // ..
    }
}

وصف العناصر المرئية

عند تحديد Image أو Icon قابل للإنشاء، لا تتوفر طريقة تلقائية لإطار عمل Android لفهم ما يعرضه التطبيق. تحتاج إلى تمرير وصف نصي للعنصر المرئي.

تخيل شاشة حيث يمكن للمستخدم مشاركة الصفحة الحالية مع الأصدقاء. تحتوي هذه الشاشة على أيقونة مشاركة قابلة للنقر:

شريط من الأيقونات القابلة للنقر، مع

استنادًا إلى الرمز فقط، لا يمكن لإطار عمل Android وصفه لمستخدم من ذوي العجز البصري. يحتاج إطار عمل Android إلى وصف نصي إضافي للأيقونة.

تصف المَعلمة contentDescription عنصرًا مرئيًا. استخدِم سلسلة مترجَمة، كما هي مرئية للمستخدم

@Composable
private fun ShareButton(onClick: () -> Unit) {
    IconButton(onClick = onClick) {
        Icon(
            imageVector = Icons.Filled.Share,
            contentDescription = stringResource(R.string.label_share)
        )
    }
}

بعض العناصر المرئية زخرفية بحتة وقد لا ترغب في توصيلها للمستخدم. عند ضبط المعلَمة contentDescription على null، تشير إلى إطار عمل Android أن هذا العنصر ليس له إجراءات أو حالة مرتبطة.

@Composable
private fun PostImage(post: Post, modifier: Modifier = Modifier) {
    val image = post.imageThumb ?: painterResource(R.drawable.placeholder_1_1)

    Image(
        painter = image,
        // Specify that this image has no semantic meaning
        contentDescription = null,
        modifier = modifier
            .size(40.dp, 40.dp)
            .clip(MaterialTheme.shapes.small)
    )
}

الأمر متروك لك لتحديد ما إذا كان العنصر المرئي بحاجة إلى إضافة contentDescription. اسأل نفسك عما إذا كان العنصر ينقل المعلومات التي سيحتاجها المستخدم لأداء مهمته. إذا لم يكن الأمر كذلك، فمن الأفضل ترك الوصف.

دمج العناصر

تتيح خدمات تسهيل الاستخدام مثل TalkBack و"الوصول عبر مفتاح تحكّم" للمستخدمين نقل التركيز بين العناصر الظاهرة على الشاشة. من المهم أن يتم التركيز على العناصر على الدقة الصحيحة. عندما يتم التركيز على كل عنصر قابل للإنشاء منخفض المستوى في شاشتك بشكل مستقل، يتعين على المستخدمين التفاعل كثيرًا للتنقل عبر الشاشة. إذا تم دمج العناصر معًا بشكل كبير جدًا، فقد لا يفهم المستخدمون العناصر التي تنتمي معًا

عند تطبيق عنصر تعديل clickable على عنصر قابل للإنشاء، يدمج Compose تلقائيًا جميع العناصر التي يحتوي عليها العنصر القابل للإنشاء. ينطبق ذلك أيضًا على ListItem، فالعناصر داخل القائمة التي يتم دمجها معًا، وخدمات تسهيل الاستخدام تعرضها كعنصر واحد.

من الممكن أن يكون لديك مجموعة من العناصر القابلة للإنشاء التي تشكّل مجموعة منطقية، ولكن هذه المجموعة غير قابلة للنقر أو تكون جزءًا من عنصر قائمة. ستظل ترغب في أن تعرضها خدمات إمكانية الوصول كعنصر واحد. على سبيل المثال، تخيل عنصرًا قابلاً للإنشاء يعرض الصورة الرمزية للمستخدم واسمه وبعض المعلومات الإضافية:

يشير ذلك المصطلح إلى مجموعة من عناصر واجهة المستخدم تتضمّن اسم المستخدم. تم اختيار الاسم.

يمكنك تفعيل سياسة ComposeAllowed لدمج هذه العناصر باستخدام المَعلمة mergeDescendants في المعدِّل semantics. بهذه الطريقة، تختار خدمات تسهيل الاستخدام العنصر المدمج فقط، ويتم دمج جميع الخصائص الدلالية للعناصر التابعة.

@Composable
private fun PostMetadata(metadata: Metadata) {
    // Merge elements below for accessibility purposes
    Row(modifier = Modifier.semantics(mergeDescendants = true) {}) {
        Image(
            imageVector = Icons.Filled.AccountCircle,
            contentDescription = null // decorative
        )
        Column {
            Text(metadata.author.name)
            Text("${metadata.date} • ${metadata.readTimeMinutes} min read")
        }
    }
}

تركز خدمات تسهيل الاستخدام الآن على الحاوية بأكملها في آنٍ واحد ودمج محتواها:

يشير ذلك المصطلح إلى مجموعة من عناصر واجهة المستخدم تتضمّن اسم المستخدم. يتم تحديد جميع العناصر معًا.

إضافة إجراءات مخصّصة

ألقِ نظرة على عنصر القائمة التالي:

عنصر قائمة نموذجي، يحتوي على عنوان المقالة والمؤلف ورمز الإشارة المرجعية.

عند استخدام قارئ شاشة مثل TalkBack لسماع ما يتم عرضه على الشاشة، يتم أولاً اختيار العنصر بأكمله، ثم رمز الإشارة المرجعية.

عنصر القائمة، مع تحديد جميع العناصر معًا.

عنصر القائمة، مع اختيار رمز الإشارة المرجعية فقط

في قائمة طويلة، قد يصبح هذا متكررًا جدًا. النهج الأفضل هو تحديد إجراء مخصص يسمح للمستخدم بوضع إشارة مرجعية على العنصر. ضَع في اعتبارك أنّه عليك أيضًا إزالة سلوك رمز الإشارة نفسها بشكل صريح للتأكّد من عدم اختياره من قِبل خدمة تسهيل الاستخدام. ويتم ذلك باستخدام المعدِّل clearAndSetSemantics:

@Composable
private fun PostCardSimple(
    /* ... */
    isFavorite: Boolean,
    onToggleFavorite: () -> Boolean
) {
    val actionLabel = stringResource(
        if (isFavorite) R.string.unfavorite else R.string.favorite
    )
    Row(
        modifier = Modifier
            .clickable(onClick = { /* ... */ })
            .semantics {
                // Set any explicit semantic properties
                customActions = listOf(
                    CustomAccessibilityAction(actionLabel, onToggleFavorite)
                )
            }
    ) {
        /* ... */
        BookmarkButton(
            isBookmarked = isFavorite,
            onClick = onToggleFavorite,
            // Clear any semantics properties set on this node
            modifier = Modifier.clearAndSetSemantics { }
        )
    }
}

وصف حالة عنصر

يمكن لعنصر قابل للإنشاء تحديد stateDescription للدلالات التي يستخدمها إطار عمل Android لقراءة حالة العنصر القابل للإنشاء. على سبيل المثال، يمكن أن تكون حالة العنصر القابل للإنشاء "محددة" أو "غير محددة". في بعض الحالات، قد ترغب في إلغاء تسميات الوصف للحالة التلقائية التي تستخدمها Compose. يمكنك إجراء ذلك من خلال تحديد تصنيفات وصف الحالة بشكل صريح قبل تحديد عنصر قابل للإنشاء على أنّه قابل للتبديل:

@Composable
private fun TopicItem(itemTitle: String, selected: Boolean, onToggle: () -> Unit) {
    val stateSubscribed = stringResource(R.string.subscribed)
    val stateNotSubscribed = stringResource(R.string.not_subscribed)
    Row(
        modifier = Modifier
            .semantics {
                // Set any explicit semantic properties
                stateDescription = if (selected) stateSubscribed else stateNotSubscribed
            }
            .toggleable(
                value = selected,
                onValueChange = { onToggle() }
            )
    ) {
        /* ... */
    }
}

تحديد العناوين

تعرض التطبيقات أحيانًا الكثير من المحتوى على شاشة واحدة في حاوية يمكن التمرير بينها. على سبيل المثال، يمكن أن تعرض الشاشة المحتوى الكامل لمقالة يقرأها المستخدم:

لقطة شاشة لمشاركة مدوّنة يظهر فيها نص المقالة في حاوية يمكن التنقّل فيها

يواجه المستخدمون ذوو احتياجات إمكانية الوصول صعوبة في التنقل في مثل هذه الشاشة. للمساعدة في التنقل، حدد العناصر التي هي عناوين. في المثال السابق، يمكن تعريف كل عنوان قسم فرعي كعنوان لإمكانية الوصول. تسمح بعض خدمات تسهيل الاستخدام، مثل TalkBack، للمستخدمين بالانتقال مباشرةً من العنوان إلى العنوان.

في علامة التبويب Compose، أنت تشير إلى أنّ العنصر القابل للإنشاء هو عنوان من خلال تحديد خاصية semantics الخاصة به:

@Composable
private fun Subsection(text: String) {
    Text(
        text = text,
        style = MaterialTheme.typography.headlineSmall,
        modifier = Modifier.semantics { heading() }
    )
}

التعامل مع العناصر المخصّصة القابلة للإنشاء

عندما تستبدل مكونات Material معينة في تطبيقك بإصدارات مخصصة، عليك أن تضع اعتبارات سهولة الوصول في الاعتبار.

لنفترض أنّك ستستبدل المادة Checkbox بالتنفيذ الخاص بك. يمكنك نسيان إضافة المعدِّل triStateToggleable الذي يعالج خصائص تسهيل الاستخدام لهذا المكوِّن.

كقاعدة إرشادية، انظر إلى تنفيذ المكوِّن في مكتبة Material ومحاكاة أي سلوك يمكنك العثور عليه بشأن سهولة الوصول. بالإضافة إلى ذلك، عليك الاستفادة بشكل كبير من معدِّلات الأساس، بدلاً من معدِّلات مستوى واجهة المستخدم، حيث تشمل اعتبارات سهولة الوصول طريقة مبتكرة.

اختبر تنفيذ المكونات المخصصة باستخدام خدمات إمكانية الوصول متعددة للتحقق من سلوكها.

مراجع إضافية