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

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

دمج الدلالات

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

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

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

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

يمكنك تفعيل Compose لدمج هذه العناصر باستخدام المَعلمة 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 لوضع إشارة مرجعية على المقالة. يظهر هذا الزر المتداخل على أنّه غير مدمج، بينما يظهر باقي محتوى العناصر الفرعية داخل الصف على أنّه مدمج:

تحتوي الشجرة المدمجة على نصوص متعددة في قائمة داخل عقدة الصف. تحتوي الشجرة غير المدمجة على عُقد منفصلة لكل دالة مركّبة من النوع Text.
الشكل 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.

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

@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 هنا إخفاء العلامة المائية والزخرفة عن خدمات تسهيل الاستخدام، مع الحفاظ على دلالاتها لحالات الاستخدام الأخرى، مثل الاختبار.

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

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

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