الدمج والإزالة

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

دمج الدلالات

عند تطبيق مُعدِّل clickable على عنصر مكوّن رئيسي، تدمج ميزة "الإنشاء" تلقائيًا جميع العناصر الفرعية ضمنه. لفهم كيفية استخدام مكونات Compose Material وFoundation التفاعلية لاستراتيجيات الدمج تلقائيًا، اطّلِع على قسم العناصر التفاعلية.

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

على سبيل المثال، يمكنك إنشاء عنصر مكوّن يعرض صورة أفاتار المستخدم واسمه وبعض المعلومات الإضافية:

مجموعة من عناصر واجهة المستخدِم، بما في ذلك اسم المستخدِم يتم اختيار الاسم.
الشكل 1. مجموعة من عناصر واجهة المستخدِم، بما في ذلك اسم المستخدِم يتم اختيار الاسم.

يمكنك تفعيل ميزة "الإنشاء" لدمج هذه العناصر باستخدام المَعلمة mergeDescendants في مُعدِّل الدلالات. بهذه الطريقة، تتعامل خدمات تسهيل الاستخدام مع العنصر ككيان واحد، ويتم دمج جميع سمات الدلالات للعناصر المشتقة:

@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")
        }
    }
}

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

مجموعة من عناصر واجهة المستخدِم، بما في ذلك اسم المستخدِم يتم اختيار جميع العناصر معًا.
الشكل 2. مجموعة من عناصر واجهة المستخدِم، بما في ذلك اسم المستخدِم يتم اختيار جميع العناصر معًا.

ولكلّ خاصيّة دلالية استراتيجية دمج محدّدة. على سبيل المثال، يضيف الحقل ContentDescription جميع قيم ContentDescription الأبناء إلى قائمة. يمكنك التحقّق من استراتيجية الدمج لموقع دلالي من خلال التحقّق من تنفيذ mergePolicy في SemanticsProperties.kt. يمكن أن تتّخذ السمات قيمة الأصل أو العنصر الفرعي، أو دمج القيم في قائمة أو سلسلة، أو عدم السماح بالدمج على الإطلاق وطرح استثناء بدلاً من ذلك، أو أي استراتيجية دمج مخصّصة أخرى.

هناك سيناريوهات أخرى تتوقّع فيها دمج الدلالات الفرعية في دلالة رئيسية، ولكنّ ذلك لا يحدث. في المثال التالي، لدينا clickable عنصر قائمة رئيسي مع عناصر فرعية، ونتوقع أن يدمج العنصر الرئيسي كلّها:

عنصر قائمة يتضمّن صورة وبعض النصوص ورمز إشارة
الشكل 3. عنصر قائمة يتضمّن صورة وبعض النصوص ورمز إشارة

@Composable
private fun ArticleListItem(
    openArticle: () -> Unit,
    addToBookmarks: () -> Unit,
) {

    Row(modifier = Modifier.clickable { openArticle() }) {
        // Merges with parent clickable:
        Icon(
            painter = painterResource(R.drawable.ic_logo),
            contentDescription = "Article thumbnail"
        )
        ArticleDetails()

        // Defies the merge due to its own clickable:
        BookmarkButton(onClick = addToBookmarks)
    }
}

عندما يضغط المستخدم على clickable العنصر Row، يتم فتح المقالة. ضمن العنصر المُدمَج، هناك رمز BookmarkButton لوضع إشارة على المقالة. يظهر الزر المُدمَج هذا على أنّه غير مدمج، في حين يتم دمج باقي المحتوى المخصّص للأطفال داخل الصف:

تحتوي الشجرة المدمجة على نصوص متعددة في قائمة داخل عقدة الصف. تحتوي الشجرة غير المدمجة على عقد منفصلة لكلّ عنصر قابل للتجميع من النوع "نص".
الشكل 4. تحتوي الشجرة المدمجة على نصوص متعددة في قائمة داخل عقدة Row. تحتوي الشجرة غير المدمجة على عقد منفصلة لكل Text عنصر قابل للتجميع.

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

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

محو المعاني وضبطها

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

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

يُرجى العلم أنّه عند محو البيانات باستخدام دالة lambda فارغة، لا يتم إرسال الدلالات المحوّاة إلى أي مستخدِم يستخدم هذه المعلومات، مثل تسهيل الاستخدام أو الملء التلقائي أو الاختبار. عند استبدال المحتوى باستخدام clearAndSetSemantics{/*semantic information*/}، تحلّ الدلالات الجديدة محل كل الدلالات السابقة للعنصر ونسله.

في ما يلي مثال على مكوّن تبديل مخصّص، يمثّله صف قابل للتفاعل يتضمّن رمزًا ونصًا:

// Developer might intend this to be a toggleable.
// Using `clearAndSetSemantics`, on the Row, a clickable modifier is applied,
// a custom description is set, and a Role is applied.

@Composable
fun FavoriteToggle() {
    val checked = remember { mutableStateOf(true) }
    Row(
        modifier = Modifier
            .toggleable(
                value = checked.value,
                onValueChange = { checked.value = it }
            )
            .clearAndSetSemantics {
                stateDescription = if (checked.value) "Favorited" else "Not favorited"
                toggleableState = ToggleableState(checked.value)
                role = Role.Switch
            },
    ) {
        Icon(
            imageVector = Icons.Default.Favorite,
            contentDescription = null // not needed here

        )
        Text("Favorite?")
    }
}

على الرغم من أنّ الرمز والنص يتضمّنان بعض المعلومات الدلالية، إلا أنّهما معًا لا يشيران إلى أنّ هذا المكوّن قابل للتبديل. لا يكفي الدمج لأنّه يجب تقديم معلومات إضافية عن المكوّن.

بما أنّ المقتطف أعلاه ينشئ مكوّن تبديل مخصّصًا، عليك إضافة إمكانية التبديل، بالإضافة إلى سمات stateDescription، toggleableState، وrole. بهذه الطريقة، تصبح حالة المكوّن والإجراء المرتبط به متوفّرين. على سبيل المثال، يُعلِن تطبيق TalkBack عن "النقر مرّتين للتبديل" بدلاً من "النقر مرّتين للتفعيل".

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

عند استخدام clearAndSetSemantics، يُرجى مراعاة ما يلي:

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

إخفاء الدلالات

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

في الأمثلة التالية، ستجد مكوّنات قد تحتاج إلى إخفائها: watermark مكرّر يمتد على مكوّن، وحرف يُستخدَم ل فصل المعلومات بشكل زخرفي.

@Composable
fun WatermarkExample(
    watermarkText: String,
    content: @Composable () -> Unit,
) {
    Box {
        WatermarkedContent()
        // Mark the watermark as hidden to accessibility services.
        WatermarkText(
            text = watermarkText,
            color = Color.Gray.copy(alpha = 0.5f),
            modifier = Modifier
                .align(Alignment.BottomEnd)
                .semantics { hideFromAccessibility() }
        )
    }
}

@Composable
fun DecorativeExample() {
    Text(
        modifier =
        Modifier.semantics {
            hideFromAccessibility()
        },
        text = "A dot character that is used to decoratively separate information, like •"
    )
}

يضمن استخدام hideFromAccessibility هنا إخفاء watermark وdecoration عن خدمات تسهيل الاستخدام، مع الحفاظ على دلالاتهما في حالات الاستخدام الأخرى، مثل الاختبار.

تقسيم حالات الاستخدام

في ما يلي ملخّص لحالات الاستخدام لفهم كيفية معرفة الفرق بوضوح بين واجهات برمجة التطبيقات السابقة:

  • عندما لا يكون المحتوى مخصّصًا لاستخدامه من قِبل خدمات تسهيل الاستخدام:
    • استخدِم hideFromAccessibility عندما يكون المحتوى تزيينيًا أو متكرّرًا، ولكن يجب اختباره.
    • استخدِم clearAndSetSemantics{} مع دالة lambda فارغة عندما تحتاج إلى محو دلالات العناصر الرئيسية والعناصر الثانوية لجميع الخدمات.
    • استخدِم clearAndSetSemantics{/*content*/} مع المحتوى داخل دالة lambda عندما تحتاج إلى ضبط الدلالات الخاصة بالمكوّن يدويًا.
  • الحالات التي يجب فيها التعامل مع المحتوى ككيان واحد، ويحتاج إلى جميع معلومات الأطفال المكونة له ليكون كاملاً:
    • استخدِم دمج العناصر الدلالية للسلالات.
جدول يعرض حالات استخدام مختلفة لواجهات برمجة التطبيقات
الشكل 5. جدول يعرض حالات استخدام مختلفة لواجهات برمجة التطبيقات